beampy.user_interface

"""
The user_interface module is the main file of the beampy module.
It contain the UserInterface class used to computed and displayed the bpm
results onto the interface.

This module was at first developed by Marcel Soubkovsky for the implementation
of the array of guides, of one gaussian beam and of the plotting methods.
Then, continued by Jonathan Peltier.
"""
import sys
import os
import webbrowser
import time
from math import pi
import numpy as np

from PyQt5.QtWidgets import (QApplication, QMainWindow, QFileDialog)
from PyQt5.QtCore import (pyqtSlot, Qt, QSize)
from PyQt5.QtGui import QIcon

from matplotlib.figure import Figure
from matplotlib.backends.backend_qt4agg import (
    FigureCanvasQTAgg as FigureCanvas,
    NavigationToolbar2QT as NavigationToolbar)
from matplotlib.patches import Polygon

# interface.py generated from interface.ui made using Qt designer
from beampy.interface import Ui_MainWindow
from beampy.bpm import Bpm  # Bpm class with all the BPM methods

# ! To translate from .ui to .py -> pyuic5 -x interface.ui -o interface.py

# Check out if doubts on the interface:
# http://blog.rcnelson.com/building-a-matplotlib-gui-with-qt-designer-part-1/


class UserInterface(QMainWindow, Ui_MainWindow):
    """
    This class connect the :class:`.Bpm` class from the :mod:`.bpm` module
    with the :meth:`.setupUi` method from the :mod:`.interface` module.
    The interface seems to close itself when openning from spyder 3.5.
    In that case, open the interface in an external console.
    """
    def __init__(self):
        QMainWindow.__init__(self)
        self.setupUi(self)  # Linked to Ui_MainWindow through interface.py

        # Prepare variables before assigning values to them
        self.width = []
        self.offset_guide = []
        self.guide_length = []
        self.offset_guide_z = []
        self.delta_no = []
        self.no_imag = []
        self.alpha = []
        self.loss_check = []
        self.full_index = []
        self.shape_gauss_check = []
        self.gauss_pow = np.array([], dtype=int)
        self.shape_squared_check = []
        self.nbr_p = np.array([], dtype=int)
        self.p = []
        self.curve = []
        self.half_delay = []
        self.distance_factor = []
        self.tab_index = []
        self.previous_guide = 0

        self.fwhm = []
        self.offset_light = []
        self.irrad = []
        self.irrad_significand = []
        self.irrad_exponent = []  # Warning: if exponent is set to int, will
        # cause a error when using 10**exponent because outside int limit and
        # goes in the negative values
        self.offset_check = []
        self.gaussian_check = []
        self.square_check = []
        self.mode_check = []
        self.all_modes_check = []
        self.mode = np.array([], dtype=int)
        self.mode_guide_ref = np.array([], dtype=int)
        self.offset_light_peak = np.array([], dtype=int)
        self.airy_check = []
        self.airy_zero = np.array([], dtype=int)
        self.lobe_size = []
        self.previous_beam = 0

        self.on_click_create_guide()  # Initialized variables with buttons
        self.on_click_create_light()  # Initialized variables with buttons

        self.calculate_guide()  # Compute waveguides
        self.calculate_light()  # Compute light

        self.addmpl('guide')  # Display waveguides
        self.addmpl('light')  # Display light

        self.show_estimate_time()

        # Initialized compute graphics
        self.canvas_propag = FigureCanvas(Figure())
        self.plot_compute.addWidget(self.canvas_propag)
        self.toolbar_propag = FigureCanvas(Figure())
        self.plot_compute.addWidget(self.toolbar_propag)
        self.toolbar_propag.close()
        self.canvas_pow = FigureCanvas(Figure())
        self.verticalLayout_compute_plot.addWidget(self.canvas_pow)
        self.toolbar_pow = FigureCanvas(Figure())
        self.verticalLayout_compute_plot.addWidget(self.toolbar_pow)
        self.canvas_end = FigureCanvas(Figure())
        self.plot_compute.addWidget(self.canvas_end)
        self.toolbar_end = FigureCanvas(Figure())
        self.plot_compute.addWidget(self.toolbar_end)

        self.filename = None

        self.connect_buttons()
        self.create_menu()

        QApplication.setOverrideCursor(Qt.ArrowCursor)
        QApplication.restoreOverrideCursor()

    def connect_buttons(self):
        """
        Connect the interface buttons to their corresponding functions:

        :meth:`.on_click_guide`, :meth:`.on_click_curved`,
        :meth:`.on_click_light`, :meth:`.on_click_compute`,
        :meth:`.on_click_create_light`, :meth:`.on_click_delete_light`,
        :meth:`.save_light`, :meth:`.get_guide`,
        :meth:`.get_light`, :meth:`.get_compute`,
        :meth:`.show_estimate_time`, :meth:`.check_modes_display`.
        """
        self.calculateButton_guide.clicked.connect(self.on_click_guide)
        self.calculateButton_light.clicked.connect(self.on_click_light)
        self.calculateButton_compute.clicked.connect(self.on_click_compute)
        self.pushButton_create_beam.clicked.connect(self.on_click_create_light)
        self.pushButton_delete_beam.clicked.connect(self.on_click_delete_light)
        self.pushButton_save_beam.clicked.connect(self.save_light)
        self.pushButton_create_guide.clicked.connect(
            self.on_click_create_guide)
        self.pushButton_delete_guide.clicked.connect(
            self.on_click_delete_guide)
        self.pushButton_save_guide.clicked.connect(self.save_guide)
        self.pushButton_cancel_guide.clicked.connect(self.get_guide)
        self.pushButton_cancel_light.clicked.connect(self.get_light)
        self.pushButton_cancel_compute.clicked.connect(self.get_compute)
        self.pushButton_estimate_time.clicked.connect(self.show_estimate_time)
        self.pushButton_mode_number.clicked.connect(self.check_modes_display)
        self.pushButton_power.clicked.connect(self.display_power)

    def create_menu(self):
        """
        Create a menu to open, save a file, or exit the app.

        Notes
        -----
        This method connect the following methods and function to the
        menu buttons:

        :meth:`.open_file_name`, :meth:`.save_quick`, :meth:`.save_file_name`,
        :func:`.open_doc`.
        """
        folder = __file__  # Module name
        # Replaces characters only when called from outer files
        folder = folder.replace("\\", "/")
        folder = folder.split("/")
        folder = folder[:-1]  # Remove the file name
        folder2 = str()

        for line in folder:
            folder2 = folder2+"/"+line

        folder = folder2[1:]+"/"

        icon = QIcon()
        icon.addFile(folder+'icons/beampy-logo.png', QSize(256, 256))
        self.setWindowIcon(icon)

        menubar = self.menuBar()

        file = menubar.addMenu('File')

        action = file.addAction('Open')
        action.triggered.connect(self.open_file_name)
        action.setShortcut('Ctrl+O')
        icon = QIcon()
        icon.addFile(folder+'icons/document-open.png', QSize(22, 22))
        action.setIcon(icon)

        action = file.addAction('Save')
        action.triggered.connect(self.save_quick)
        action.setShortcut('Ctrl+S')
        icon = QIcon()
        icon.addFile(folder+'icons/document-save.png', QSize(22, 22))
        action.setIcon(icon)

        action = file.addAction('Save as')
        action.triggered.connect(self.save_file_name)
        action.setShortcut('Ctrl+Shift+S')
        icon = QIcon()
        icon.addFile(folder+'icons/document-save-as.png', QSize(22, 22))
        action.setIcon(icon)

        action = file.addAction('Exit')  # Clean exit for spyder
        action.setShortcut('Ctrl+Q')
        action.triggered.connect(QApplication.quit)
        icon = QIcon()
        icon.addFile(folder+'icons/application-exit.png', QSize(22, 22))
        action.setIcon(icon)

        file = menubar.addMenu('Help')

        action = file.addAction('Documentation')
        action.triggered.connect(open_doc)
        icon = QIcon()
        icon.addFile(folder+'icons/help-about.png', QSize(22, 22))
        action.setIcon(icon)

    def calculate_guide(self):
        """
        Initialized the :class:`.Bpm` class and creates the guides.

        Notes
        -----
        Creats many variables, including :

        - peaks : Central position of each waveguides [waveguide,z].
        - dn : Difference of reefractive index [z,x].

        This method calls the following methods from :class:`.Bpm`:

        :meth:`.create_x_z`, :meth:`.squared_guide`, :meth:`.gauss_guide`,
        :meth:`.create_guides`, :meth:`.create_curved_guides`.
        """
        print("Waveguides creation")
        t_guide_start = time.process_time()

        self.save_guide()  # Get all waveguide values

        # Create the Bpm class (overwrite existing one)
        self.bpm = Bpm(self.no, self.lo,
                       self.length_z, self.dist_z, self.nbr_z_disp,
                       self.length_x, self.dist_x)

        # windows variables
        [self.length_z, self.nbr_z, self.nbr_z_disp,
         self.length_x, self.nbr_x, self.x] = self.bpm.create_x_z()

        self.doubleSpinBox_length_x.setValue(self.length_x)
        self.doubleSpinBox_length_z.setValue(self.length_z)
        self.spinBox_nbr_z_disp.setValue(self.nbr_z_disp)

        if (self.nbr_x * self.nbr_z_disp) > (5000 * 1000):
            print("Error: if you want to have more points,")
            print("change the condition nbr_x * nbr_z_disp in calculate_guide")
            raise RuntimeError("Too many points:", self.nbr_x*self.nbr_z_disp)

        if (self.nbr_x * self.nbr_z) > (10000 * 15000) or (
                self.nbr_z > 40000 or self.nbr_x > 40000):
            print("Error: if you want to have more points,")
            print("change the condition nbr_x * nbr_z in calculate_guide")
            raise RuntimeError("Too many points:", self.nbr_x*self.nbr_z)

        nbr_guide = self.comboBox_guide.count()  # Total waveguide count

        self.peaks = np.zeros((0, self.nbr_z))
        self.dn = np.zeros((self.nbr_z, self.nbr_x))

        for i in range(nbr_guide):
            # Waveguide shape choice
            if self.shape_squared_check[i]:  # Squared waveguides
                shape = self.bpm.squared_guide(self.width[i])
            elif self.shape_gauss_check[i]:  # Gaussian waveguides
                shape = self.bpm.gauss_guide(self.width[i],
                                             gauss_pow=self.gauss_pow[i])

            # Topology waveguides choice
            if self.tab_index[i] == 0:  # array of waveguides
                length_max = self.length_x - self.dist_x

                if self.nbr_p[i]*self.p[i] > length_max:
                    # give info about possible good values
                    print("nbr_p*p > length_x: ")
                    print(self.nbr_p[i]*self.p[i], ">", length_max)

                    print("p_max=", round(length_max/self.nbr_p[i], 3))

                    if int(length_max / self.p[i]) == length_max / self.p[i]:
                        print("nbr_p_max=", int(length_max / self.p[i])-1)
                    else:
                        print("nbr_p_max=", int(length_max / self.p[i]))

                    self.nbr_p[i] = 0

                [peaks, dn] = self.bpm.create_guides(
                    shape, self.full_index[i],
                    self.nbr_p[i], self.p[i],
                    offset_guide=self.offset_guide[i],
                    z=[self.offset_guide_z[i],
                       self.offset_guide_z[i]+self.guide_length[i]])

            elif self.tab_index[i] == 1:  # curved waveguides
                curve = self.curve[i] * 1E-8  # curvature factor
                [peaks, dn] = self.bpm.create_curved_guides(
                    shape, self.width[i], self.full_index[i],
                    curve, self.half_delay[i], self.distance_factor[i],
                    offset_guide=self.offset_guide[i])

            if self.delta_no[i] > self.no/10:
                print("Careful: index variation too high for waveguide %i" % i,
                      "\t%f > %f/10" % (self.delta_no[i], self.no), sep="\n")

            self.peaks = np.append(self.peaks, peaks, 0)
            self.dn = np.add(self.dn, dn)
            self.dn_first = np.array(self.dn[0])

            # Display Waveguides
            self.z_disp = np.linspace(0,
                                      self.length_z/1000,
                                      self.nbr_z_disp+1)
            # Note that z_disp can be wrong by 0.4% if the space between the
            # displayed points is not a multiple of length_z

            self.xv, self.zv = np.meshgrid(self.x, self.z_disp)
            self.dn_disp = np.linspace(0,
                                       self.nbr_z-1,
                                       self.nbr_z_disp+1, dtype=int)

        self.pushButton_power.setDisabled(True)
        # only display available settings
        if (self.nbr_p.sum() == 0 or self.p.sum() == 0
                or self.guide_length.sum() == 0):
            # If one of the waveguides has width=0, can chose invisible
            # waveguide has beam reference and display power=0. Not a big deal
            self.spinBox_offset_light_peak.setDisabled(True)
            self.spinBox_guide_nbr_ref.setDisabled(True)
            self.spinBox_guide_nbr_ref.setMaximum(0)
            self.doubleSpinBox_offset_light.setEnabled(True)
            self.checkBox_offset_light.setChecked(False)
            self.checkBox_offset_light.setDisabled(True)
            self.offset_check *= 0
            self.checkBox_power.setDisabled(True)

        else:
            self.checkBox_offset_light.setEnabled(True)
            self.spinBox_offset_light_peak.setMaximum(self.peaks.shape[0]-1)
            self.spinBox_guide_nbr_ref.setMaximum(self.peaks.shape[0]-1)
            self.checkBox_power.setEnabled(True)

#        Define new min/max for light and looses, based on selected guides
        self.doubleSpinBox_lobe_size.setMaximum(self.length_x-self.dist_x)
        # If want to use min/max for offset: multiple beams will have issue if
        # the windows size change (min/max will only be for the displayed beam)
#        self.doubleSpinBox_offset_light.setMinimum(self.x[0])
#        self.doubleSpinBox_offset_light.setMaximum(self.x[-1])

        self.calculate_guide_done = True
        t_guide_end = time.process_time()
        print('Waveguides creation time: ', t_guide_end-t_guide_start)

    def calculate_light(self):
        """
        Create the choosen beams.

        Notes
        -----
        Creates the progress_pow variable.

        This method calls the following methods from the :class:`.Bpm` class:

        :meth:`.gauss_light`, :meth:`.squared_light`, :meth:`.all_modes`,
        :meth:`.mode_light`, :meth:`.airy_light`, :meth:`.init_field`.
        """
        print("Beams creation")
        t_light_start = time.process_time()

        self.save_light()  # Get all light variables
        # must have same wavelength and angle or must compute for each
        # different wavelength or angle
        nbr_light = self.comboBox_light.count()  # Number of beams
        field = np.zeros((nbr_light, self.nbr_x))

        if 1 in self.all_modes_check:  # Display only once the max mode
            self.check_modes_display()

        for i in range(nbr_light):

            # Check if offset relative to waveguide number or else in µm
            if self.offset_check[i] and self.peaks.shape[0] != 0:

                # Reduce light # if > guides #
                peaks_i = self.offset_light_peak[i]
                peaks_max = self.spinBox_offset_light_peak.maximum()

                if peaks_i > peaks_max:
                    print("beam", i, "has a non-existing waveguide position")
                    print("Change position from", peaks_i, "to", peaks_max)
                    self.offset_light_peak[i] = peaks_max

                temp = np.array(self.peaks[self.offset_light_peak[i]])
                temp = temp[temp != np.array(None)]

                if len(temp) != 0:
                    offset_light = temp[0]
                else:
                    print("There is no waveguide n°",
                          self.offset_light_peak[i],
                          "The beam n°", i, "is disabled")
                    continue

            else:
                offset_light = self.offset_light[i]

            guide_index = self.find_guide_number(self.mode_guide_ref[i])

            if guide_index is None and (self.mode_check[i]
                                        or self.all_modes_check[i]):
                print("skipping the beam", i, "because the waveguide",
                      self.mode_guide_ref[i], "doesn't exist")
                continue

            #  Compute lights
            if self.gaussian_check[i]:
                field_i = self.bpm.gauss_light(
                    self.fwhm[i], offset_light=offset_light)

            elif self.square_check[i]:
                field_i = self.bpm.squared_light(
                    self.fwhm[i], offset_light=offset_light)

            elif self.all_modes_check[i]:

                field_i = self.bpm.all_modes(
                    self.width[guide_index], self.delta_no[guide_index],
                    offset_light=offset_light)[0]

            elif self.mode_check[i]:

                try:
                    field_i = self.bpm.mode_light(
                        self.width[guide_index], self.delta_no[guide_index],
                        self.mode[i], offset_light=offset_light)[0]

                except ValueError as ex:  # Say that no mode exist
                    print(ex, "for the beam", i)
                    continue  # Go to the next field

            elif self.airy_check[i]:
                [field_i, last_zero_pos] = self.bpm.airy_light(
                    self.lobe_size[i], self.airy_zero[i],
                    offset_light=offset_light)
                self.spinBox_airy_zero.setValue(last_zero_pos)  # Corrected val

            field[i] = field_i

        [self.progress_pow] = self.bpm.init_field(
            field, self.theta_ext, self.irrad)

#        self.pushButton_power.setDisabled(True)

        t_light_end = time.process_time()
        print('Beams creation time: ', t_light_end-t_light_start)

    def calculate_propagation(self):
        """
        Calculate the propagation based on the input light and guides shapes.

        Notes
        -----
        Creates the progress_pow variable.

        Calls the following methods from :class:`.Bpm`:
        :meth:`.losses_position`, meth`.main_compute`.
        """
        print("Propagation computation")
        t_compute_start = time.process_time()
        self.calculate_guide_done = False

        self.show_estimate_time()  # do also save_compute() needed here

        if self.kerr_check:
            if self.n2_check:
                n2 = self.n2
                chi3 = None
            else:
                n2 = None
                chi3 = self.chi3
        else:
            n2 = None
            chi3 = None

        [self.progress_pow] = self.main_compute(
            self.dn, n2=n2, chi3=chi3, kerr_loop=self.kerr_loop,
            variance_check=self.variance_check, disp_progress=False)

        self.progressBar_compute.setValue(0)

        if (self.nbr_p.sum() != 0 and self.p.sum() != 0
                and self.guide_length.sum() != 0):
            self.pushButton_power.setEnabled(True)

        t_compute_end = time.process_time()
        print('Computation time: ', t_compute_end-t_compute_start)

    def main_compute(self, dn, n2=None, chi3=None, kerr_loop=1,
                     variance_check=False, disp_progress=True):
        """
        main method used to compute propagation.

        Parameters
        ----------
        n2 : float, optional
            Nonlinear refractive index responsable for the optical Kerr effect
            in m^2/W. None by default.
        chi3 : float, optional
            Value of the third term of the electric susceptibility tensor
            in m^2/V^2. None by default.
        kerr : bool, optional
            Activate the kerr effect. False by default.
        kerr_loop : int, optional
            Number of corrective loop for the Kerr effect. 1 by default.
        variance_check : bool, optional
            Check if the kerr effect converge fast enought. False by default.
        alpha : float, optional
            Absorption per µm. 0 by default
        lost_beg : array-like, optional
            Left indices position of the selected waveguide over z.
            None by default.
        lost_end : array-like, optional
            Right indices position of the selected waveguide over z.
            None by default.

        Returns
        -------
        progress_pow : array
            Intensity values (:math:`W/m^2`) over x (µm) and z (µm).

        Notes
        -----

        This method creates the following variables within the class
        :class:`Bpm`:
        nl_mat: Refractive index modulation.

        This method uses the :class:`Bpm` class variables:
        phase_mat, field, i, nbr_z, pas, current_power, dist_z, length_z,
        nbr_lost, dn, nl_mat, epnc and uses the :meth:`bpm_compute`,
        :meth:`kerr_effect`.

        This method change the values of the :class:`Bpm` class variables:
        field and if kerr, dn and nl_mat.
        """
        # Refractive index modulation
        self.bpm.nl_mat = self.bpm.ko * self.bpm.dist_z * dn

        index = 0
        self.bpm.i = 0
        #  from i=0 to i=final-1 because don't use last dn
        for i in range(self.bpm.nbr_z-1):
            self.bpm.i = i
            # Compute non-linear and linear propagation for every z
            self.bpm.bpm_compute(dn, n2=n2, chi3=chi3, kerr_loop=kerr_loop,
                                 variance_check=variance_check)

            # Display condition: if i+1 is a multiple of pas: i+1 % pas = 0
            # = False, so must use if not to have True
            # last condition to have last point if not a multiple of pas
            if (not (self.bpm.i + 1) % self.bpm.pas
                    or self.bpm.i+1 == self.bpm.nbr_z-1):
                index += 1
                self.bpm.progress_pow[index, :] = np.array(
                        [self.bpm.current_power])

                current = (self.bpm.i+1)*self.bpm.dist_z/1e3
                final = self.bpm.length_z/1e3
                self.progressBar_compute.setValue(current/final*100)

                if disp_progress:
                    print(current, "/", final, 'mm')

        return [self.bpm.progress_pow]

    def show_estimate_time(self):
        """
        Display - on the interface - the estimate time needed to compute the
        propagation, based on linearized experimental values.
        The estimation takes into account the looses, Kerr, and control
        parameters.
        """
        self.save_compute()
        estimation = round(
            16.6/5e7*self.nbr_z*self.nbr_x  # usual propagation
            * (1 + 0.62*self.kerr_check*(self.kerr_loop))  # with kerr
            + 3.8e-8*self.nbr_z*self.nbr_x*self.kerr_check*self.variance_check,
            1)

        self.estimate_time_display.display(estimation)

    def find_guide_number(self, guide_number):
        """
        Return the waveguide group number for a given waveguide number.


        Parameters
        ----------
        guide_number : int
            Number of a waveguide

        Returns
        -------
        guide_group_number : int
            Number of the waveguide group
        """
        nbr_guide = np.zeros(len(self.nbr_p))

        for i in range(len(self.nbr_p)):

            if self.tab_index[i] == 0:
                nbr_guide[i] = self.nbr_p[i]

            elif self.tab_index[i] == 1:
                nbr_guide[i] = 3

        cumul = np.cumsum(nbr_guide)
        guide_list_pos = np.where(cumul >= (guide_number+1))[0]

        if len(guide_list_pos) != 0:
            guide_pos = guide_list_pos[0]
            return guide_pos
        else:
            return None

    def check_modes_display(self):
        """
        Display on the interface the last mode that can propagated into a
        squared waveguide.
        """
        guide_index = self.find_guide_number(
            self.spinBox_guide_nbr_ref.value())
        if guide_index is None:
            print("Error: trying to check the mode of a unexisting waveguide")
        else:
            mode_max = self.bpm.check_modes(
                self.width[guide_index], self.delta_no[guide_index])
            self.mode_number.display(mode_max)

    def addmpl(self, tab='guide', pow_index=0):
        """
        Add the selected plots on the waveguide, light or compute window.

        Parameters
        ----------
        tab : str, optional
            'guide' or 'light'.
            'guide' by default.
        pow_index : int, optional
            Add the first waveguide and light step if 0 or the last step if -1.
            Also display the propagation over (x,z) and guide power if -1 is
            choosen.
            0 by default.
        """
        pow_index_guide = pow_index

        if pow_index < 0:
            pow_index_guide -= 1  # Display the -2 waveguide for the -1 beam

        x_min = self.x[0]
        x_max = self.x[-1]

        temp = self.peaks.reshape(self.peaks.shape[0]*self.peaks.shape[1])
        temp = temp[temp != np.array(None)]
        if len(temp) == 0:
            temp = np.array([self.x[0], self.x[-1]])

        if (0 in self.tab_index  # If array of guides
                and self.nbr_p.sum() != 0 and self.p.sum() != 0  # If exists
                and temp.min() >= self.x[0]  # If in the windows
                and temp.max() <= self.x[-1]):
            x_min = np.min(self.offset_guide - self.nbr_p*self.p)
            x_max = np.max(self.offset_guide + self.nbr_p*self.p)
            no_array = False
        else:
            no_array = True

        if (1 in self.tab_index  # If curved guides
                and temp.min() >= self.x[0]  # If guides in the windows
                and temp.max() <= self.x[-1]):
            x_min_bis = temp.min() - self.width.max()
            x_max_bis = temp.max() + self.width.max()

            if x_min_bis < x_min or no_array:
                x_min = x_min_bis

            if x_max_bis > x_max or no_array:
                x_max = x_max_bis

        if tab == 'guide':
            fig = Figure()
            # BUG: if click on menu, change size until overlap or reduce to
            # oblivion. set_tight_layout(true) is the cause.
#            fig.set_tight_layout(True)  # Prevent axes to be cut when resizing
            if np.sum(self.full_index.imag) == 0:
                ax1 = fig.add_subplot(111)
            else:

                ax1 = fig.add_subplot(121)
                ax2 = fig.add_subplot(122)
                ax2.set_title("Imaginary part of the refractive index")
                ax2.set_xlim(x_min, x_max)
            ax1.set_title("Waveguide shape over x and z")
            ax1.set_xlabel('x (µm)')
            ax1.set_ylabel('z (mm)')

            ax1.set_xlim(x_min, x_max)

            # note that a colormesh pixel is based on 4 points
            graph = ax1.pcolormesh(self.xv,
                                   self.zv,
                                   self.dn[self.dn_disp].real,
                                   cmap='gray')
            fig.colorbar(graph, ax=ax1)

            if np.sum(self.full_index.imag) != 0:
                z_max = abs(self.dn[self.dn_disp].imag).max()
                graph2 = ax2.pcolormesh(self.xv,
                                        self.zv,
                                        self.dn[self.dn_disp].imag,
                                        cmap='seismic',
                                        vmin=-z_max, vmax=z_max)
                fig.colorbar(graph2, ax=ax2)

            self.canvas_guide_xz = FigureCanvas(fig)
            self.plot_guide.addWidget(self.canvas_guide_xz)

            self.toolbar_guide_xz = NavigationToolbar(self.canvas_guide_xz,
                                                      self.canvas_guide_xz,
                                                      coordinates=True)
            self.plot_guide.addWidget(self.toolbar_guide_xz)
            fig = Figure()
#            fig.set_tight_layout(True)
            ax1 = fig.add_subplot(111)
            ax1.set_title("Input index profil")
            ax1.set_xlabel('x (µm)')
            ax1.set_ylabel(r'$\Delta_n$')

            if self.nbr_p.sum() != 0:
                verts = [(self.x[0], 0),
                         *zip(self.x, self.dn[pow_index_guide, :].real),
                         (self.x[-1], 0)]
                poly = Polygon(verts, facecolor='0.9', edgecolor='0.5')
                ax1.add_patch(poly)

            ax1.set_xlim(x_min, x_max)

            if max(self.dn[0, :].real) > max(self.dn[-1, :].real):
                ax1.set_ylim(0,
                             max(self.dn[0, :].real)*1.1 + 1E-20)
            else:
                ax1.set_ylim(0,
                             max(self.dn[-1, :].real)*1.1 + 1E-20)

            ax1.plot(self.x, self.dn[pow_index_guide].real, 'k')

            self.canvas_guide_x = FigureCanvas(fig)
            self.plot_guide.addWidget(self.canvas_guide_x)

            self.toolbar_guide_x = NavigationToolbar(self.canvas_guide_x,
                                                     self.canvas_guide_x,
                                                     coordinates=True)
            self.plot_guide.addWidget(self.toolbar_guide_x)

        elif tab == 'light':
            fig = Figure()
#            fig.set_tight_layout(True)
            ax1 = fig.add_subplot(111)
            ax1.set_title("Light injection")
            ax1.set_xlabel('x (µm)')
            ax2 = ax1.twinx()

            for tl in ax1.get_yticklabels():
                tl.set_color('k')

            for tl in ax2.get_yticklabels():
                tl.set_color('#1f77b4')

            ax1.set_ylabel(r'$\Delta_n$')
            ax2.set_ylabel('Irradiance ($GW.cm^{-2}$)')

            if self.nbr_p.sum() != 0:
                if pow_index_guide == 0:
                    verts = [(self.x[0], 0),
                             *zip(self.x, self.dn_first.real),
                             (self.x[-1], 0)]
                else:
                    verts = [(self.x[0], 0),
                             *zip(self.x, self.dn[pow_index_guide, :].real),
                             (self.x[-1], 0)]
                poly = Polygon(verts, facecolor='0.9', edgecolor='0.5')
                ax1.add_patch(poly)

            ax1.set_xlim(x_min, x_max)

            if pow_index_guide == 0:
                ax1.set_ylim(
                        0,  max(1.1*self.dn_first.real) + 1E-20)
            else:
                ax1.set_ylim(
                        0,  max(1.1*self.dn[pow_index_guide, :].real) + 1E-20)

            if max(self.progress_pow[0]) != 0:
                ax2.set_ylim(0, 1.1e-13*max(self.progress_pow[0]))

            if pow_index_guide == 0:
                ax1.plot(self.x, self.dn_first.real, 'k')
            else:
                ax1.plot(self.x, self.dn[pow_index_guide].real, 'k')
            ax2.plot(self.x, 1e-13*self.progress_pow[pow_index], '#1f77b4')

            # Display light at the beginning of guides
            if pow_index == 0:
                self.canvas_light = FigureCanvas(fig)
                self.plot_light.addWidget(self.canvas_light)

                self.toolbar_light = NavigationToolbar(self.canvas_light,
                                                       self.canvas_light,
                                                       coordinates=True)
                self.plot_light.addWidget(self.toolbar_light)

            if pow_index < 0:
                ax1.set_title("Light at the output")
                self.canvas_end = FigureCanvas(fig)
                self.plot_compute.addWidget(self.canvas_end)

                self.toolbar_end = NavigationToolbar(self.canvas_end,
                                                     self.canvas_end,
                                                     coordinates=True)
                self.plot_compute.addWidget(self.toolbar_end)

                # Display light propagation into guides
                fig = Figure()
#                fig.set_tight_layout(True)
                ax1 = fig.add_subplot(111)
                ax1.set_title("Light propagation")
                ax1.set_xlabel('x (µm)')
                ax1.set_ylabel('z (mm)')

                ax2.set_xlim(x_min, x_max)

                graph = ax1.pcolormesh(self.xv,
                                       self.zv,
                                       1e-13*self.progress_pow)
#                fig.colorbar(graph, ax=ax1)
                self.canvas_propag = FigureCanvas(fig)
                self.plot_compute.addWidget(self.canvas_propag)

                self.toolbar_propag = NavigationToolbar(self.canvas_propag,
                                                        self.canvas_propag,
                                                        coordinates=True)
                self.plot_compute.addWidget(self.toolbar_propag)

                self.display_power()

    def display_power(self):
        """Display the power in each waveguide."""
        if (not self.checkBox_power.isChecked()
                or self.nbr_p.sum() == 0 or self.p.sum() == 0):
            return None

        t_power_start = time.process_time()
        fig = Figure()
#                    fig.set_tight_layout(True)
        ax1 = fig.add_subplot(111)
        ax1.set_title("Power in waveguides")
        ax1.set_ylim(-0.05, 1.05)
        ax1.set_xlabel('z (mm)')
        ax1.set_ylabel('Power (a.u)')

        dot = [1, 2]
        sp1 = [0, 5]
        line = [1, 3, 6]
        sp2 = [1, 2, 4]
        dashes = [0]*len(dot)*len(sp1)*len(line)*len(sp2)

        for i, val1 in enumerate(dot):
            for j, val2 in enumerate(sp1):
                for k, val3 in enumerate(line):
                    for l, val4 in enumerate(sp2):
                        dashes[i*len(sp1)*len(line)*len(sp2)
                               + j*len(line)*len(sp2)
                               + k*len(sp2)+l
                               ] = (val1, val2, val3, val4)
        dashes.insert(0, (1, 0, 1, 0))

        x_beg = np.array([[None]*self.nbr_z]*self.peaks.shape[0])
        x_end = np.array([[None]*self.nbr_z]*self.peaks.shape[0])
        P = np.zeros((self.peaks.shape[0], self.nbr_z_disp+1))

        num_gd = 0
        for i, n in enumerate(self.nbr_p):
            if self.tab_index[i] == 0:  # Array of waveguide
                for _ in range(n):
                    [x_beg[num_gd, :],
                     x_end[num_gd, :]] = self.bpm.guide_position(
                         self.peaks, num_gd, self.p[i])
                    num_gd += 1
                if n == 0:  # needed if no waveguide
                    num_gd += 1

            elif self.tab_index[i] == 1:  # Curved waveguide
                # Choose precision at the end for right waveguide
                # and choose safety when guides overlapse
                if self.peaks[num_gd+2, -1] <= self.x[-1]:
                    # accurate at end but may overlap before
                    p0 = (self.peaks[num_gd+2, -1]
                          - self.peaks[num_gd+1, -1])
                else:
                    # accurate in middle but miss evanescente part
                    p0 = self.width[i] * self.distance_factor[i]

                for j in range(3):
                    [x_beg[num_gd, :],
                     x_end[num_gd, :]] = self.bpm.guide_position(
                          self.peaks, num_gd, p0)
                    num_gd += 1

        for i in range(self.peaks.shape[0]):
            P[i, :] = self.bpm.power_guide(x_beg[i, :],
                                           x_end[i, :])
            # plot each power with a different style
            ax1.plot(self.z_disp, P[i, :],
                     dashes=dashes[i % len(dashes)],
                     label='P'+str(i))

        self.canvas_pow = FigureCanvas(fig)
        self.verticalLayout_compute_plot.addWidget(self.canvas_pow)

        self.toolbar_pow = NavigationToolbar(self.canvas_pow,
                                             self.canvas_pow,
                                             coordinates=True)
        self.verticalLayout_compute_plot.addWidget(self.toolbar_pow)
        if self.peaks.shape[0] > 10:
            ax1.legend(loc="upper right")  # Fast if many plot
        else:
            ax1.legend()  # Best if not too many plot
        ax1.grid()
        t_power_end = time.process_time()
        print('Power time: ', t_power_end-t_power_start)

    def rmmpl(self, tab, pow_index=0):
        """
        Remove the selected plots

        Parameters
        ----------
        tab : str
            'guide' or 'light'.
        pow_index : int, optional
            Remove the first light step if 0 or the last step if -1.
            0 by default.
        """
        if tab == 'guide':
            self.plot_guide.removeWidget(self.canvas_guide_xz)
            self.plot_guide.removeWidget(self.canvas_guide_x)
            self.canvas_guide_xz.close()
            self.canvas_guide_x.close()

            self.plot_guide.removeWidget(self.toolbar_guide_xz)
            self.plot_guide.removeWidget(self.toolbar_guide_x)
            self.toolbar_guide_xz.close()
            self.toolbar_guide_x.close()

        elif tab == 'light':
            if pow_index == 0:
                self.plot_light.removeWidget(self.canvas_light)
                self.canvas_light.close()
                self.plot_light.removeWidget(self.toolbar_light)
                self.toolbar_light.close()

            if pow_index < 0:
                self.plot_compute.removeWidget(self.canvas_propag)
                self.canvas_propag.close()
                self.plot_compute.removeWidget(self.toolbar_propag)
                self.toolbar_propag.close()

                self.plot_compute.removeWidget(self.canvas_end)
                self.canvas_end.close()
                self.plot_compute.removeWidget(self.toolbar_end)
                self.toolbar_end.close()

                self.verticalLayout_compute_plot.removeWidget(self.canvas_pow)
                self.canvas_pow.close()
                self.verticalLayout_compute_plot.removeWidget(self.toolbar_pow)
                self.toolbar_pow.close()

    def save_guide(self, guide_selec=False):
        """
        Save the interface variables into the guides variables.
        """
        # if more than one waveguide and if no waveguide selected manually
        if str(guide_selec) == 'False':
            guide_selec = int(self.comboBox_guide.currentIndex())  # Choice

        self.length_z = self.doubleSpinBox_length_z.value()
        self.dist_z = self.doubleSpinBox_dist_z.value()
        self.nbr_z_disp = self.spinBox_nbr_z_disp.value()
        self.length_x = self.doubleSpinBox_length_x.value()
        self.dist_x = self.doubleSpinBox_dist_x.value()
        self.no = self.doubleSpinBox_n.value()
        self.lo = self.doubleSpinBox_lo.value()

        self.width[guide_selec] = self.doubleSpinBox_width.value()
        self.offset_guide[
            guide_selec] = self.doubleSpinBox_offset_guide.value()
        self.guide_length[
            guide_selec] = self.doubleSpinBox_guide_length.value()
        self.offset_guide_z[
            guide_selec] = self.doubleSpinBox_offset_guide_z.value()
        self.delta_no[guide_selec] = self.doubleSpinBox_dn.value()
        self.no_imag[guide_selec] = self.doubleSpinBox_n_imag.value()
        self.alpha[guide_selec] = self.doubleSpinBox_lost.value()
        self.loss_check[guide_selec] = self.checkBox_n_imag.isChecked()
        delta_no = self.delta_no[guide_selec]
        no_imag = self.no_imag[guide_selec]
        alpha = self.alpha[guide_selec]/1000  # unit conversion mm-1 -> µm-1
        no_imag2 = alpha / (2*pi/self.lo)
        loss_check = self.loss_check[guide_selec]
        self.full_index[guide_selec] = (delta_no + 1j*loss_check*no_imag
                                        + 1j*(1-loss_check)*no_imag2)

        self.shape_gauss_check[guide_selec] = float(
            self.radioButton_gaussian.isChecked())
        self.gauss_pow[guide_selec] = int(self.spinBox_gauss_pow.value())
        self.shape_squared_check[guide_selec] = float(
            self.radioButton_squared.isChecked())
        self.nbr_p[guide_selec] = self.spinBox_nb_p.value()
        self.p[guide_selec] = self.doubleSpinBox_p.value()
        self.curve[guide_selec] = self.doubleSpinBox_curve.value()
        self.half_delay[guide_selec] = self.doubleSpinBox_half_delay.value()
        self.distance_factor[
            guide_selec] = self.doubleSpinBox_distance_factor.value()

        self.tab_index[
            guide_selec] = self.tabWidget_morphology_guide.currentIndex()
#        print("Guide variables saved")

    def get_guide(self):
        """
        Set the saved values of the waveguide variables onto the interface.
        """
        self.doubleSpinBox_length_z.setValue(self.length_z)
        self.doubleSpinBox_dist_z.setValue(self.dist_z)
        self.spinBox_nbr_z_disp.setValue(self.nbr_z_disp)
        self.doubleSpinBox_length_x.setValue(self.length_x)
        self.doubleSpinBox_dist_x.setValue(self.dist_x)
        self.doubleSpinBox_n.setValue(self.no)
        self.doubleSpinBox_lo.setValue(self.lo)

        guide_selec = int(self.comboBox_guide.currentIndex())  # choice

        if self.previous_guide != guide_selec:
            self.save_guide(self.previous_guide)

        if self.comboBox_guide.count() >= 1:  # if more than one beams
            guide_selec = int(self.comboBox_guide.currentIndex())  # choice
        else:  # Not supposed to happen
            raise ValueError("Can't have no waveguide variables")

        self.doubleSpinBox_width.setValue(self.width[guide_selec])
        self.doubleSpinBox_offset_guide.setValue(
            self.offset_guide[guide_selec])
        self.doubleSpinBox_guide_length.setValue(
            self.guide_length[guide_selec])
        self.doubleSpinBox_offset_guide_z.setValue(
            self.offset_guide_z[guide_selec])
        self.doubleSpinBox_dn.setValue(self.delta_no[guide_selec])
        self.doubleSpinBox_n_imag.setValue(self.no_imag[guide_selec])
        self.doubleSpinBox_lost.setValue(self.alpha[guide_selec])
        self.checkBox_n_imag.setChecked(self.loss_check[guide_selec])
        self.doubleSpinBox_n_imag.setEnabled(self.loss_check[guide_selec])
        self.doubleSpinBox_lost.setDisabled(self.loss_check[guide_selec])

        self.radioButton_gaussian.setChecked(
            self.shape_gauss_check[guide_selec])
        self.spinBox_gauss_pow.setValue(self.gauss_pow[guide_selec])
        self.radioButton_squared.setChecked(
            self.shape_squared_check[guide_selec])
        self.spinBox_nb_p.setValue(self.nbr_p[guide_selec])
        self.doubleSpinBox_p.setValue(self.p[guide_selec])
        self.doubleSpinBox_curve.setValue(self.curve[guide_selec])
        self.doubleSpinBox_half_delay.setValue(self.half_delay[guide_selec])
        self.doubleSpinBox_distance_factor.setValue(
            self.distance_factor[guide_selec])
        self.tabWidget_morphology_guide.setCurrentIndex(
            self.tab_index[guide_selec])
        self.spinBox_gauss_pow.setEnabled(self.shape_gauss_check[guide_selec])

        self.previous_guide = guide_selec  # Save the n° of current waveguide

    def save_light(self, beam_selec=False):
        """
        Save the interface variables into the lights variables.

        Parameters
        ----------
        beam_selec: int, bool, optional
            Number of the beam to save into the variables.
            False by default to get the currently displayed beam.
        """
        self.theta_ext = self.doubleSpinBox_theta_ext.value()

        # if more than one beams and if no beams selected manualy
        if str(beam_selec) == 'False':
            beam_selec = int(self.comboBox_light.currentIndex())  # Choice

        self.fwhm[beam_selec] = self.doubleSpinBox_fwhm.value()
        self.offset_light[beam_selec] = self.doubleSpinBox_offset_light.value()
        self.irrad_significand[beam_selec] = (
                self.doubleSpinBox_irrad_significand.value())
        self.irrad_exponent[beam_selec] = (
                self.spinBox_irrad_exponent.value())
        self.irrad[beam_selec] = (self.doubleSpinBox_irrad_significand.value()
                                  * 10**self.spinBox_irrad_exponent.value())
        self.mode[beam_selec] = self.spinBox_mode.value()
        self.mode_guide_ref[beam_selec] = self.spinBox_guide_nbr_ref.value()
        self.offset_check[beam_selec] = (
            self.checkBox_offset_light.isChecked())
        self.offset_light_peak[beam_selec] = (
            self.spinBox_offset_light_peak.value())
        self.gaussian_check[beam_selec] = (
            self.radioButton_gaussian_light.isChecked())
        self.square_check[beam_selec] = (
            self.radioButton_squared_light.isChecked())
        self.mode_check[beam_selec] = self.radioButton_mode.isChecked()
        self.all_modes_check[beam_selec] = (
            self.radioButton_all_modes.isChecked())
        self.airy_check[beam_selec] = (
            self.radioButton_airy.isChecked())
        self.airy_zero[beam_selec] = self.spinBox_airy_zero.value()
        self.lobe_size[beam_selec] = self.doubleSpinBox_lobe_size.value()

    def get_light(self):
        """
        Set the saved values of the light variables onto the interface.
        """
        beam_selec = int(self.comboBox_light.currentIndex())  # choice

        if self.previous_beam != beam_selec:
            self.save_light(self.previous_beam)

        self.doubleSpinBox_theta_ext.setValue(self.theta_ext)

        if self.comboBox_light.count() >= 1:  # if more than one beams
            beam_selec = int(self.comboBox_light.currentIndex())  # choice
        else:  # Not supposed to happen
            raise ValueError("Can't have no beam variables")

        self.doubleSpinBox_fwhm.setValue(self.fwhm[beam_selec])
        self.doubleSpinBox_offset_light.setValue(
            self.offset_light[beam_selec])
        self.doubleSpinBox_irrad_significand.setValue(
                self.irrad_significand[beam_selec])
        self.spinBox_irrad_exponent.setValue(
                self.irrad_exponent[beam_selec])
        self.spinBox_mode.setValue(self.mode[beam_selec])
        self.spinBox_guide_nbr_ref.setValue(self.mode_guide_ref[beam_selec])
        self.checkBox_offset_light.setChecked(self.offset_check[beam_selec])
        self.spinBox_offset_light_peak.setValue(
            self.offset_light_peak[beam_selec])
        self.radioButton_gaussian_light.setChecked(
            self.gaussian_check[beam_selec])
        self.radioButton_squared_light.setChecked(
            self.square_check[beam_selec])
        self.radioButton_mode.setChecked(self.mode_check[beam_selec])
        self.radioButton_all_modes.setChecked(self.all_modes_check[beam_selec])
        self.radioButton_airy.setChecked(self.airy_check[beam_selec])
        self.spinBox_airy_zero.setValue(self.airy_zero[beam_selec])
        self.doubleSpinBox_lobe_size.setValue(self.lobe_size[beam_selec])

        self.spinBox_airy_zero.setEnabled(self.airy_check[beam_selec])
        self.doubleSpinBox_lobe_size.setEnabled(self.airy_check[beam_selec])
        self.spinBox_mode.setEnabled(self.mode_check[beam_selec])
        self.spinBox_guide_nbr_ref.setEnabled(self.mode_check[beam_selec])

        self.previous_beam = beam_selec  # Save the n° of the current beam

    def save_compute(self):
        """
        Save the interface variables into the compute variables.
        """
        self.kerr_check = float(self.checkBox_kerr.isChecked())
        self.kerr_loop = self.spinBox_kerr_loop.value()
        self.n2_significand = self.doubleSpinBox_n2_significand.value()
        self.n2_exponent = self.spinBox_n2_exponent.value()
        self.n2 = self.n2_significand * 10**self.n2_exponent
        self.chi3_significand = self.doubleSpinBox_chi3_significand.value()
        self.chi3_exponent = self.spinBox_chi3_exponent.value()
        self.chi3 = self.n2_significand * 10**self.chi3_exponent
        self.variance_check = float(self.checkBox_variance.isChecked())
        self.power_check = float(self.checkBox_power.isChecked())
        self.n2_check = float(self.checkBox_n2.isChecked())

    def get_compute(self):
        """
        Set the saved values of the compute variables onto the interface.
        """
        self.checkBox_kerr.setChecked(self.kerr_check)
        self.spinBox_kerr_loop.setValue(self.kerr_loop)
        self.checkBox_n2.setChecked(self.n2_check)
        self.doubleSpinBox_n2_significand.setValue(self.n2_significand)
        self.spinBox_n2_exponent.setValue(self.n2_exponent)
        self.doubleSpinBox_chi3_significand.setValue(self.chi3_significand)
        self.spinBox_chi3_exponent.setValue(self.chi3_exponent)
        self.frame_kerr.setEnabled(self.kerr_check)
        self.checkBox_variance.setChecked(self.variance_check)
        self.checkBox_power.setChecked(self.power_check)

    @pyqtSlot()
    def on_click_guide(self):
        """
        Create and displayed the waguides.
        """
        QApplication.setOverrideCursor(Qt.WaitCursor)
        self.rmmpl('guide')
        self.rmmpl('light')
        self.calculate_guide()
        self.calculate_light()
        self.addmpl('guide')
        self.addmpl('light')
        QApplication.restoreOverrideCursor()
        self.show_estimate_time()

    @pyqtSlot()
    def on_click_light(self):
        """
        Create the light and display it with the input profil waveguides.
        """
        QApplication.setOverrideCursor(Qt.WaitCursor)
        self.rmmpl(tab='light')
        self.calculate_light()
        self.addmpl(tab='light')
        QApplication.restoreOverrideCursor()
        self.show_estimate_time()

    @pyqtSlot()
    def on_click_compute(self):
        """
        Compute the propagation using the waveguides and bemas informations.
        """
        QApplication.setOverrideCursor(Qt.WaitCursor)
        self.show_estimate_time()

        if np.max(self.progress_pow[0]) != 0:

            self.rmmpl(tab='light', pow_index=-1)

            if not self.calculate_guide_done:
                self.rmmpl('guide')
                self.calculate_guide()
                self.addmpl('guide')
                self.rmmpl(tab='light')
                self.calculate_light()
                self.addmpl(tab='light')

            self.calculate_propagation()
            self.addmpl(tab='light', pow_index=-1)

        else:
            print("no light to compute")

        QApplication.restoreOverrideCursor()

    @pyqtSlot()
    def on_click_create_guide(self):
        """Create a new waveguide with the displayed variables.
        """
        width = self.doubleSpinBox_width.value()
        offset_guide = self.doubleSpinBox_offset_guide.value()
        guide_length = self.doubleSpinBox_guide_length.value()
        offset_guide_z = self.doubleSpinBox_offset_guide_z.value()
        delta_no = self.doubleSpinBox_dn.value()
        no_imag = self.doubleSpinBox_n_imag.value()
        alpha = self.doubleSpinBox_lost.value()
        loss_check = self.checkBox_n_imag.isChecked()
        self.lo = self.doubleSpinBox_lo.value()
        no_imag2 = (alpha/1000) / (2*pi/self.lo)
        full_index = (delta_no + 1j*loss_check*no_imag
                      + 1j*(1-loss_check)*no_imag2)
        shape_gauss_check = self.radioButton_gaussian.isChecked()
        gauss_pow = int(self.spinBox_gauss_pow.value())
        shape_squared_check = self.radioButton_squared.isChecked()
        nbr_p = self.spinBox_nb_p.value()
        p = self.doubleSpinBox_p.value()
        curve = self.doubleSpinBox_curve.value()
        half_delay = self.doubleSpinBox_half_delay.value()
        distance_factor = self.doubleSpinBox_distance_factor.value()
        tab_index = self.tabWidget_morphology_guide.currentIndex()

        self.width = np.append(self.width, width)
        self.offset_guide = np.append(self.offset_guide, offset_guide)
        self.guide_length = np.append(self.guide_length, guide_length)
        self.offset_guide_z = np.append(self.offset_guide_z, offset_guide_z)
        self.delta_no = np.append(self.delta_no, delta_no)
        self.no_imag = np.append(self.no_imag, no_imag)
        self.alpha = np.append(self.alpha, alpha)
        self.loss_check = np.append(self.loss_check, loss_check)
        self.full_index = np.append(self.full_index, full_index)
        self.shape_gauss_check = np.append(self.shape_gauss_check,
                                           shape_gauss_check)
        self.gauss_pow = np.append(self.gauss_pow, gauss_pow)
        self.shape_squared_check = np.append(self.shape_squared_check,
                                             shape_squared_check)
        self.nbr_p = np.append(self.nbr_p, nbr_p)
        self.p = np.append(self.p, p)
        self.curve = np.append(self.curve, curve)
        self.half_delay = np.append(self.half_delay, half_delay)
        self.distance_factor = np.append(self.distance_factor, distance_factor)
        self.tab_index = np.append(self.tab_index, tab_index)

        nbr_guide = self.comboBox_guide.count()  # how many item left
        self.comboBox_guide.addItem("Waveguide "+str(nbr_guide))
        self.comboBox_guide.setCurrentIndex(nbr_guide)  # show new index
        self.previous_guide = nbr_guide  # save new waveguide n°

    @pyqtSlot()
    def on_click_create_light(self):
        """Create a new beam with the displayed variables.
        """
        fwhm = self.doubleSpinBox_fwhm.value()
        offset_light = self.doubleSpinBox_offset_light.value()
        irrad_significand = self.doubleSpinBox_irrad_significand.value()
        irrad_exponent = self.spinBox_irrad_exponent.value()
        irrad = irrad_significand * 10**irrad_exponent
        offset_check = self.checkBox_offset_light.isChecked()
        gaussian_check = self.radioButton_gaussian_light.isChecked()
        square_check = self.radioButton_squared_light.isChecked()
        mode_check = self.radioButton_mode.isChecked()
        all_modes_check = self.radioButton_all_modes.isChecked()
        mode = self.spinBox_mode.value()
        mode_guide_ref = self.spinBox_guide_nbr_ref.value()
        offset_light_peak = self.spinBox_offset_light_peak.value()
        airy_check = self.radioButton_airy.isChecked()
        airy_zero = self.spinBox_airy_zero.value()
        lobe_size = self.doubleSpinBox_lobe_size.value()

        self.fwhm = np.append(self.fwhm, fwhm)
        self.offset_light = np.append(self.offset_light, offset_light)
        self.irrad_significand = np.append(self.irrad_significand,
                                           irrad_significand)
        self.irrad_exponent = np.append(self.irrad_exponent, irrad_exponent)
        self.irrad = np.append(self.irrad, irrad)
        self.mode = np.append(self.mode, mode)
        self.mode_guide_ref = np.append(self.mode_guide_ref, mode_guide_ref)
        self.offset_check = np.append(self.offset_check, offset_check)
        self.offset_light_peak = np.append(self.offset_light_peak,
                                           offset_light_peak)
        self.gaussian_check = np.append(self.gaussian_check, gaussian_check)
        self.square_check = np.append(self.square_check, square_check)
        self.mode_check = np.append(self.mode_check, mode_check)
        self.all_modes_check = np.append(self.all_modes_check, all_modes_check)
        self.airy_check = np.append(self.airy_check, airy_check)
        self.airy_zero = np.append(self.airy_zero, airy_zero)
        self.lobe_size = np.append(self.lobe_size, lobe_size)

        nbr_light = self.comboBox_light.count()  # how many item left
        self.comboBox_light.addItem("Beam "+str(nbr_light))  # add new index
        self.comboBox_light.setCurrentIndex(nbr_light)  # show new index
        self.previous_beam = nbr_light  # Change the current selected beam

    @pyqtSlot()
    def on_click_delete_guide(self):
        """
        Delete the current displayed waveguide and displayed the next one if
        exist else the previous one.
        """
        nbr_guide = self.comboBox_guide.count()

        if nbr_guide > 1:  # Can't delete if remains only 1 waveguide
            guide_selec = int(self.comboBox_guide.currentIndex())  # choice
            self.width = np.delete(self.width, guide_selec)
            self.offset_guide = np.delete(self.offset_guide, guide_selec)
            self.guide_length = np.delete(self.guide_length, guide_selec)
            self.offset_guide_z = np.delete(self.offset_guide_z, guide_selec)
            self.delta_no = np.delete(self.delta_no, guide_selec)
            self.no_imag = np.delete(self.no_imag, guide_selec)
            self.alpha = np.delete(self.alpha, guide_selec)
            self.loss_check = np.delete(self.loss_check, guide_selec)
            self.full_index = np.delete(self.full_index, guide_selec)

            self.shape_gauss_check = np.delete(self.shape_gauss_check,
                                               guide_selec)
            self.gauss_pow = np.delete(self.gauss_pow, guide_selec)
            self.shape_squared_check = np.delete(self.shape_squared_check,
                                                 guide_selec)
            self.nbr_p = np.delete(self.nbr_p, guide_selec)
            self.p = np.delete(self.p, guide_selec)
            self.curve = np.delete(self.curve, guide_selec)
            self.half_delay = np.delete(self.half_delay, guide_selec)
            self.distance_factor = np.delete(self.distance_factor, guide_selec)

            nbr_guide -= 1

            self.comboBox_guide.clear()  # remove all beams number

            for i in range(nbr_guide):  # Add waveguides n°
                self.comboBox_guide.addItem("Waveguide "+str(i))

            # set same waveguide n° if not the last else reduce the index by 1
            if guide_selec == nbr_guide and guide_selec != 0:
                guide_selec -= 1

            self.comboBox_guide.setCurrentIndex(guide_selec)
            self.previous_guide = guide_selec  # Change the selected waveguide
            self.get_guide()  # Display previous or next waveguide values

    @pyqtSlot()
    def on_click_delete_light(self):
        """
        Delete the current displayed beam and displayed the next one.
        """
        nbr_light = self.comboBox_light.count()

        if nbr_light > 1:  # Can't delete if remains only 1 beam
            beam_selec = int(self.comboBox_light.currentIndex())  # choice
            self.fwhm = np.delete(self.fwhm, beam_selec)
            self.offset_light = np.delete(self.offset_light, beam_selec)
            self.irrad_significand = np.delete(self.irrad_significand,
                                               beam_selec)
            self.irrad_exponent = np.delete(self.irrad_exponent, beam_selec)
            self.irrad = np.delete(self.irrad, beam_selec)
            self.mode = np.delete(self.mode, beam_selec)
            self.mode_guide_ref = np.delete(self.mode_guide_ref, beam_selec)
            self.offset_check = np.delete(self.offset_check, beam_selec)
            self.offset_light_peak = np.delete(self.offset_light_peak,
                                               beam_selec)
            self.gaussian_check = np.delete(self.gaussian_check, beam_selec)
            self.square_check = np.delete(self.square_check, beam_selec)
            self.mode_check = np.delete(self.mode_check, beam_selec)
            self.all_modes_check = np.delete(self.all_modes_check,
                                             beam_selec)
            self.airy_check = np.delete(self.airy_check, beam_selec)
            self.airy_zero = np.delete(self.airy_zero, beam_selec)
            self.lobe_size = np.delete(self.lobe_size, beam_selec)

            nbr_light -= 1

            self.comboBox_light.clear()  # remove all beams number

            for i in range(nbr_light):  # create again with new number
                self.comboBox_light.addItem("Beam "+str(i))

            # set same beam index if not the last else reduce the index by 1
            if beam_selec == nbr_light and beam_selec != 0:
                beam_selec -= 1

            self.comboBox_light.setCurrentIndex(beam_selec)
            self.previous_beam = beam_selec  # Change the current selected beam
            self.get_light()  # Display values of the previous or next beam

    @pyqtSlot()
    def open_file_name(self):
        """
        Open a dialog window to select the file to open, and call
        :meth:`open_file` to open the file.

        Source: https://pythonspot.com/pyqt5-file-dialog/

        Notes
        -----
        This method has a try/except implemented to check if the openned file
        contains all the variables.
        """

        options = QFileDialog.Options()
        options |= QFileDialog.DontUseNativeDialog
        filename, _ = QFileDialog.getOpenFileName(self,
                                                  "Import data",
                                                  "",
                                                  "Text Files (*.txt)",
                                                  options=options)
        if filename:
            try:
                self.open_file(filename)
                self.filename = filename  # Next save will overwrite this file
                self.setWindowTitle("Beampy - "+filename)
            except KeyError as ex:
                print("missing variable", ex, "in the file.")
                print("Add it manually to remove this error.")
#            except Exception as ex:  # This execption was removed to see the
                # # error location. The program won't crash thanks to try
#                print("Unknown error when openning the file:", filename)
#                print("Error:", ex)

    def open_file(self, filename):
        """
        Set guides, beams and computes variables from a choosen file.

        Parameters
        ----------
        filename : str
            Name of the file.
        """
#        https://www.tutorialspoint.com/
#        How-to-create-a-Python-dictionary-from-text-file
        dico = {}
        f = open(filename, 'r')
        for line in f:
            (variables, *val) = line.split()  # Assume: variable_name values
#            print(variables, val)
            dico[str(variables)] = val
        f.close()

        # Waveguide variables
        self.length_z = float(dico['length_z'][0])
        self.dist_z = float(dico['dist_z'][0])
        self.nbr_z_disp = int(dico['nbr_z_disp'][0])
        self.length_x = float(dico['length_x'][0])
        self.dist_x = float(dico['dist_x'][0])
        self.no = float(dico['no'][0])
        self.lo = float(dico['lo'][0])

        self.width = np.array(dico['width'], dtype=float)
        self.offset_guide = np.array(dico['offset_guide'], dtype=float)
        self.guide_length = np.array(dico['guide_length'], dtype=float)
        self.offset_guide_z = np.array(dico['offset_guide_z'], dtype=float)
        self.delta_no = np.array(dico['delta_no'], dtype=float)
        self.no_imag = np.array(dico['no_imag'], dtype=float)
        self.alpha = np.array(dico['alpha'], dtype=float)
        self.loss_check = np.array(dico['loss_check'], dtype=float)
        no_imag2 = (self.alpha/1000) / (2*pi/self.lo)
        self.full_index = np.array(
                self.delta_no + 1j*self.loss_check*self.no_imag
                + 1j*(1-self.loss_check)*no_imag2)
        self.shape_gauss_check = np.array(
                dico['shape_gauss_check'], dtype=float)
        self.gauss_pow = np.array(dico['gauss_pow'], dtype=int)
        self.shape_squared_check = np.array(
                dico['shape_squared_check'], dtype=float)
        self.nbr_p = np.array(dico['nbr_p'], dtype=int)
        self.p = np.array(dico['p'], dtype=float)
        self.curve = np.array(dico['curve'], dtype=float)
        self.half_delay = np.array(dico['half_delay'], dtype=float)
        self.distance_factor = np.array(dico['distance_factor'], dtype=float)
        self.tab_index = np.array(dico['tab_index'], dtype=float)

        # Light variables
        self.theta_ext = float(dico['theta_ext'][0])

        self.fwhm = np.array(dico['fwhm'], dtype=float)
        self.offset_light = np.array(dico['offset_light'], dtype=float)
        self.irrad_significand = np.array(
                dico['irrad_significand'], dtype=float)
        self.irrad_exponent = np.array(dico['irrad_exponent'], dtype=float)
        self.irrad = self.irrad_significand*10**self.irrad_exponent
        self.offset_check = np.array(dico['offset_check'], dtype=float)
        self.gaussian_check = np.array(dico['gaussian_check'], dtype=float)
        self.square_check = np.array(dico['square_check'], dtype=float)
        self.mode_check = np.array(dico['mode_check'], dtype=float)
        self.all_modes_check = np.array(dico['all_modes_check'], dtype=float)
        self.mode = np.array(dico['mode'], dtype=int)
        self.mode_guide_ref = np.array(dico['mode_guide_ref'], dtype=int)
        self.offset_light_peak = np.array(
            dico['offset_light_peak'], dtype=int)
        self.spinBox_offset_light_peak.setMaximum(99)
        self.airy_check = np.array(dico['airy_check'], dtype=float)
        self.airy_zero = np.array(dico['airy_zero'], dtype=int)
        self.lobe_size = np.array(dico['lobe_size'], dtype=float)

        # Compute variables
        self.kerr_check = float(dico['kerr_check'][0])
        self.kerr_loop = int(dico['kerr_loop'][0])
        self.n2_check = float(dico['n2_check'][0])
        self.n2_significand = float(dico['n2_significand'][0])
        self.n2_exponent = float(dico['n2_exponent'][0])
        self.chi3_significand = float(dico['chi3_significand'][0])
        self.chi3_exponent = float(dico['chi3_exponent'][0])
        self.variance_check = float(dico['variance_check'][0])
        self.power_check = float(dico['power_check'][0])

        nbr_guide = len(self.width)
        self.comboBox_guide.clear()  # Remove all guides number

        nbr_light = len(self.fwhm)
        self.comboBox_light.clear()  # Remove all beams number

        for i in range(nbr_guide):  # Add waveguides n°
            self.comboBox_guide.addItem("Waveguide "+str(i))

        for i in range(nbr_light):  # Add beams n°
            self.comboBox_light.addItem("Beam "+str(i))

        self.previous_guide = 0  # Will show the first waveguide
        self.previous_beam = 0  # Will show the first beam

        self.get_guide()  # Set waveguide values
        self.get_light()  # Set lights values
        self.get_compute()  # Set compute values

        self.on_click_guide()

        print("file openned")

    @pyqtSlot()
    def save_quick(self):
        """
        Check if a file is already selected and if so, save into it.
        Else, call the :meth:`save_file_name` to ask a filename.
        """
        if self.filename is None:
            self.save_file_name()
        else:
            self.save_file(self.filename)

    def save_file_name(self):
        """
        Open a dialog window to select the saved file name and call
        :meth:`save_file` to save the file.
        """
        options = QFileDialog.Options()
        options |= QFileDialog.DontUseNativeDialog
        filename, _ = QFileDialog.getSaveFileName(self,
                                                  "Save data",
                                                  "",
                                                  "Text Files (*.txt)",
                                                  options=options)
        if filename:
            if filename[-4:] != '.txt':
                filename = filename + '.txt'
            self.filename = filename
            self.save_file(filename)
            self.setWindowTitle("Beampy - "+filename)

    def save_file(self, filename):
        """
        Save guides, beams and computes variables into a choosen file.

        Parameters
        ----------
        filename : str
            Name of the file.
        """
        self.save_guide()
        self.save_light()
        self.save_compute()

        f = open(filename, "w")

        # Waveguide variables
        f.write('length_z ' + str(self.length_z) + '\n')
        f.write('dist_z ' + str(self.dist_z) + '\n')
        f.write('nbr_z_disp ' + str(self.nbr_z_disp) + '\n')
        f.write('length_x ' + str(self.length_x) + '\n')
        f.write('dist_x ' + str(self.dist_x) + '\n')
        f.write('no ' + str(self.no) + '\n')
        f.write('lo ' + str(self.lo) + '\n')

        f.write('width ' + str(self.width).replace("[", "").replace("]", "")
                + '\n')
        f.write('offset_guide ' + str(
            self.offset_guide).replace("[", "").replace("]", "")
                + '\n')
        f.write('guide_length ' + str(
            self.guide_length).replace("[", "").replace("]", "")
                + '\n')
        f.write('offset_guide_z ' + str(
            self.offset_guide_z).replace("[", "").replace("]", "")
                + '\n')
        f.write('delta_no ' + str(
            self.delta_no).replace("[", "").replace("]", "")
                + '\n')
        f.write('no_imag ' + str(
            self.no_imag).replace("[", "").replace("]", "")
                + '\n')
        f.write('alpha ' + str(
            self.alpha).replace("[", "").replace("]", "")
                + '\n')
        f.write('loss_check ' + str(
            self.loss_check).replace("[", "").replace("]", "")
                + '\n')
        f.write('shape_gauss_check ' + str(
            self.shape_gauss_check).replace("[", "").replace("]", "")
                + '\n')
        f.write('gauss_pow ' + str(
            self.gauss_pow).replace("[", "").replace("]", "")
                + '\n')
        f.write('shape_squared_check ' + str(
            self.shape_squared_check).replace("[", "").replace("]", "")
                + '\n')
        f.write('nbr_p ' + str(self.nbr_p).replace("[", "").replace("]", "")
                + '\n')
        f.write('p ' + str(self.p).replace("[", "").replace("]", "")
                + '\n')
        f.write('curve ' + str(self.curve).replace("[", "").replace("]", "")
                + '\n')
        f.write('half_delay ' + str(
            self.half_delay).replace("[", "").replace("]", "")
                + '\n')
        f.write('distance_factor ' + str(
            self.distance_factor).replace("[", "").replace("]", "")
                + '\n')
        f.write('tab_index ' + str(
            self.tab_index).replace("[", "").replace("]", "")
                + '\n')

        # light variables
        f.write('theta_ext ' + str(self.theta_ext) + '\n')

        f.write('fwhm '
                + str(self.fwhm).replace("[", "").replace("]", "")
                + '\n')
        f.write('offset_light '
                + str(self.offset_light).replace("[", "").replace("]", "")
                + '\n')
        f.write('irrad_significand '
                + str(self.irrad_significand).replace("[", "").replace("]", "")
                + '\n')
        f.write('irrad_exponent '
                + str(self.irrad_exponent).replace("[", "").replace("]", "")
                + '\n')
        f.write('offset_check '
                + str(self.offset_check).replace("[", "").replace("]", "")
                + '\n')
        f.write('gaussian_check '
                + str(self.gaussian_check).replace("[", "").replace("]", "")
                + '\n')
        f.write('square_check '
                + str(self.square_check).replace("[", "").replace("]", "")
                + '\n')
        f.write('mode_check '
                + str(self.mode_check).replace("[", "").replace("]", "")
                + '\n')
        f.write('all_modes_check '
                + str(self.all_modes_check).replace("[", "").replace("]", "")
                + '\n')
        f.write('mode '
                + str(self.mode).replace("[", "").replace("]", "")
                + '\n')
        f.write('mode_guide_ref '
                + str(self.mode_guide_ref).replace("[", "").replace("]", "")
                + '\n')
        f.write('offset_light_peak '
                + str(self.offset_light_peak).replace("[", "").replace("]", "")
                + '\n')
        f.write('airy_check '
                + str(self.airy_check).replace("[", "").replace("]", "")
                + '\n')
        f.write('airy_zero '
                + str(self.airy_zero).replace("[", "").replace("]", "")
                + '\n')
        f.write('lobe_size '
                + str(self.lobe_size).replace("[", "").replace("]", "")
                + '\n')

        # compute variables
        f.write('kerr_check ' + str(self.kerr_check) + '\n')
        f.write('kerr_loop ' + str(self.kerr_loop) + '\n')
        f.write('n2_check ' + str(self.n2_check) + '\n')
        f.write('n2_significand ' + str(self.n2_significand) + '\n')
        f.write('n2_exponent ' + str(self.n2_exponent) + '\n')
        f.write('chi3_significand ' + str(self.chi3_significand) + '\n')
        f.write('chi3_exponent ' + str(self.chi3_exponent) + '\n')
        f.write('variance_check ' + str(self.variance_check) + '\n')
        f.write('power_check ' + str(self.power_check) + '\n')
        f.close()
        print("file saved")


def open_doc():
    """
    Function that open the local html documentation - describing the beampy
    modules - if exist, or the online version otherwise.
    """
    file = __file__  # Module name
    # Replaces characters only when called from outer files
    file = file.replace("\\", "/")
    file = file.split("/")
    file = file[:-2]  # Remove the folder and file name

    file2 = str()
    for line in file:
        file2 = file2+"/"+line

    file = file2[1:]+"/docs/html/index.html"
    exists = os.path.isfile(file)

    if exists:
        webbrowser.open(file, new=2)  # Open file in a new tab (new=2)
    else:
        print("The documentation can't be found localy in:", file)
        file = "https://beampy.readthedocs.io"
        print("Openning the online version at:", file)
        webbrowser.open(file, new=2)  # Open file in a new tab (new=2)


def open_app():
    """
    Function used to open the app.
    Can be called directly from beampy.
    """
    app = QApplication(sys.argv)  # Define the app
    myapp = UserInterface()  # Run the app
    myapp.show()  # Show the form
    app.exec_()  # Execute the app in a loop


if __name__ == "__main__":
    open_app()