class MatplotlibWidget(QWidget):
    
    def __init__(self, parent = None):

        QWidget.__init__(self, parent)

        self.CanvasWindow = parent

        self.canvas = FigureCanvas(Figure(dpi=DPI))
        
        # attach matplotlib canvas to layout
        vertical_layout = QVBoxLayout()
        vertical_layout.addWidget(self.canvas)
        self.setLayout(vertical_layout)
        
        # canvas setup
        self.canvas.axis = self.canvas.figure.add_subplot(111)
        self.canvas.figure.subplots_adjust(left=0, right=1, top=1, bottom=0, wspace=0, hspace=0)
        self.canvas.axis.spines['top'].set_visible(False)
        self.canvas.axis.spines['left'].set_visible(False)
        self.canvas.axis.set_xlim(AXIS_LIMIT)
        self.canvas.axis.set_ylim(AXIS_LIMIT)

        # variables
        self.pressed = False
        self.plotted = False
        self.pen = False
        self.N = 0
        self.arr_drawing_complex = []
        self.time = []
        self.arr_coordinates = []
        self.line_draw, = self.canvas.axis.plot([], [], c='#eb4034', linewidth=1)
        self.arr_drawing = np.empty((1,2))

        # listeners
        self.canvas.mpl_connect('button_press_event', self.on_press)
        self.canvas.mpl_connect('motion_notify_event', self.on_motion)
        self.canvas.mpl_connect('button_release_event', self.on_release)


    def on_press(self, event):
        if len(self.arr_drawing[1:]) > 0:
            self.animation._stop()

        if self.plotted:
            # reset variables
            self.plotted = False
            self.arr_drawing = np.empty((1,2))
            self.N = 0

            # clear and reload subplot
            self.canvas.axis.clear()
            self.canvas.axis.set_xlim(AXIS_LIMIT)
            self.canvas.axis.set_ylim(AXIS_LIMIT)
            self.canvas.draw()

        self.pressed = True
        self.pen = True



    def on_motion(self, event):
        if self.pressed:
            self.plotted = True

            # plot drawing points
            coordinates = [event.xdata, event.ydata]
            self.arr_drawing = np.append(self.arr_drawing, [coordinates], axis=0)
            self.line_draw.set_data(self.arr_drawing[1:,0],self.arr_drawing[1:,1])
            self.canvas.axis.draw_artist(self.line_draw)
            self.canvas.blit(self.canvas.axis.bbox)
            self.canvas.flush_events()


    def on_release(self, event):
        self.pressed = False
        self.line_draw.set_data([],[])

        if len(self.arr_drawing) > 0:
            self.runAnimation()


    def getSizes(self):
        arr_radius = np.array([item['amplitude'] for item in self.ft.arr_epicycles])
        rr_pix = (self.canvas.axis.transData.transform(np.vstack([arr_radius, arr_radius]).T) - self.canvas.axis.transData.transform(np.vstack([np.zeros(self.N), np.zeros(self.N)]).T))
        rpix, _ = rr_pix.T
        size_pt = (2*rpix/DPI*72)**2
        return size_pt


    def runAnimation(self):
        self.animation = animation.FuncAnimation(
                            self.canvas.figure,
                            self.animate,
                            init_func=self.init,
                            interval=ANIMATION_INTERVAL,
                            blit=True)


    def init(self):
        # set new values
        if self.pen:
            self.arr_drawing_complex = [complex(coordinates[0], coordinates[1]) for coordinates in self.arr_drawing[1:]]
        else:
            self.arr_drawing_complex = [complex(coordinates[0], coordinates[1]) for coordinates in self.arr_drawing[:]]
        
        self.N = len(self.arr_drawing_complex)
        self.CanvasWindow.horizontalSlider.setMaximum(self.N)
        self.CanvasWindow.horizontalSlider.setValue(self.N)

        # calculate fourier transform via FFT algorithm
        self.ft = FourierTransform(self.arr_drawing_complex)
        self.ft.toEpicycles()

        # calculate all points in time range from 0 to 2pi
        self.time = np.linspace(0,2*pi,endpoint = False, num=self.N)
        self.arr_coordinates = np.array([self.ft.getPoint(dt) for dt in self.time])

        # create all components responsible for showing animation
        self.circle = self.canvas.axis.scatter([],[], fc='None', ec='#9ac7e4', lw=1)
        self.circle.set_sizes(self.getSizes())
        self.line_connect, = self.canvas.axis.plot([], [], c='#9ac7e4', lw=1)
        self.line_plot, = self.canvas.axis.plot([], [], c='#4c6bd5', lw=2)
        self.line_plot_all, = self.canvas.axis.plot([], [], c='#4c6bd5', lw=0.5)

        return [self.circle, self.line_connect, self.line_plot, self.line_plot_all]


    def animate(self,i):
        s = self.CanvasWindow.horizontalSlider.value()+1

        # update components
        self.circle.set_offsets(self.arr_coordinates[:,:s][i%self.N,:-1])
        self.line_connect.set_data(self.arr_coordinates[:,:s][i%self.N,:,0],self.arr_coordinates[:,:s][i%self.N,:,1])
        self.line_plot.set_data(self.arr_coordinates[:,:s][:i%self.N+1,-1,0],self.arr_coordinates[:,:s][:i%self.N+1,-1,1])
        self.line_plot_all.set_data(self.arr_coordinates[:,:s][:,-1,0],self.arr_coordinates[:,:s][:,-1,1])

        return [self.circle, self.line_connect, self.line_plot, self.line_plot_all]
Пример #2
0
class FDTD(QMainWindow, Ui_MainWindow):
    """
    FDTD object.
    """
    def __init__(self):

        super(self.__class__, self).__init__()

        self.setupUi(self)

        # Connect to the button
        self.start_fdtd.clicked.connect(self._start_fdtd)

        # Set up a figure for the plotting canvas
        fig = Figure()
        self.fig = fig
        self.axes1 = fig.add_subplot(111)
        self.my_canvas = FigureCanvas(fig)

        # Add the canvas to the vertical layout
        self.verticalLayout.addWidget(self.my_canvas)
        self.addToolBar(QtCore.Qt.TopToolBarArea,
                        NavigationToolbar(self.my_canvas, self))

        # TE or TM
        self.mode = None

        # Angle of the incident field (degrees)
        self.incident_angle = None

        # Number of time steps
        self.number_of_time_steps = None

        # Geometry file to use
        self.geometry_file = None

        # Width of the Gaussian pulse in time steps
        self.gaussian_pulse_width = None

        # Amplitude of the Gaussian pulse
        self.gaussian_pulse_amplitude = None

        # Number of PML layers
        self.number_of_pml = None

        # Others that will be set during initialization
        self.nx = None
        self.ny = None

        self.dx = None
        self.dy = None

        self.dt = None

        self.mu_r = None
        self.eps_r = None
        self.sigma = None

        self.exi = None
        self.eyi = None
        self.dexi = None
        self.deyi = None
        self.exs = None
        self.eys = None
        self.hzs = None
        self.dhzi = None

        self.hxs = None
        self.hys = None
        self.dhxi = None
        self.dhyi = None
        self.ezs = None
        self.ezi = None
        self.dezi = None

        self.esctc = None
        self.eincc = None
        self.edevcn = None
        self.ecrlx = None
        self.ecrly = None
        self.dtmdx = None
        self.dtmdy = None
        self.hdhvcn = None

        self.amplitude_y = None
        self.amplitude_x = None

        self.geometry_file = '../Libs/rcs/fdtd.cell'

    def _start_fdtd(self):
        """
        Update the figure when the user changes an input value.
        :return:
        """
        # Get the parameters from the form
        self.mode = self._mode.currentText()
        self.incident_angle = float(self._incident_angle.text())
        self.number_of_time_steps = int(self._number_of_time_steps.text())
        self.gaussian_pulse_width = int(self._gaussian_pulse_width.text())
        self.gaussian_pulse_amplitude = float(
            self._gaussian_pulse_amplitude.text())
        self.number_of_pml = int(self._number_of_pml.text())

        # Read the geometry file and calculate material parameters
        self.initialize()

    def initialize(self):
        """
        Initialize the variables for FDTD calculations.
        :return:
        """
        # Read the geometry file
        self.read_geometry_file()

        # Initialize the fields
        if self.mode == 'TE':
            self.exi = zeros([self.nx, self.ny])
            self.eyi = zeros([self.nx, self.ny])
            self.dexi = zeros([self.nx, self.ny])
            self.deyi = zeros([self.nx, self.ny])
            self.exs = zeros([self.nx, self.ny])
            self.eys = zeros([self.nx, self.ny])
            self.hzs = zeros([self.nx, self.ny])
            self.dhzi = zeros([self.nx, self.ny])
        else:
            self.hxs = zeros([self.nx, self.ny])
            self.hys = zeros([self.nx, self.ny])
            self.dhxi = zeros([self.nx, self.ny])
            self.dhyi = zeros([self.nx, self.ny])
            self.ezs = zeros([self.nx, self.ny])
            self.ezi = zeros([self.nx, self.ny])
            self.dezi = zeros([self.nx, self.ny])

        # Initialize the other values
        self.esctc = zeros([self.nx, self.ny])
        self.eincc = zeros([self.nx, self.ny])
        self.edevcn = zeros([self.nx, self.ny])
        self.ecrlx = zeros([self.nx, self.ny])
        self.ecrly = zeros([self.nx, self.ny])
        self.dtmdx = zeros([self.nx, self.ny])
        self.dtmdy = zeros([self.nx, self.ny])
        self.hdhvcn = zeros([self.nx, self.ny])

        # Calculate the maximum time step allowed by the Courant stability condition
        self.dt = 1.0 / (c * (sqrt(1.0 / (self.dx**2) + 1.0 / (self.dy**2))))

        for i in range(self.nx):
            for j in range(self.ny):
                eps = epsilon_0 * self.eps_r[i][j]
                mu = mu_0 * self.mu_r[i][j]
                self.esctc[i][j] = eps / (eps + self.sigma[i][j] * self.dt)
                self.eincc[i][j] = self.sigma[i][j] * self.dt / (
                    eps + self.sigma[i][j] * self.dt)
                self.edevcn[i][j] = self.dt * (eps - epsilon_0) / (
                    eps + self.sigma[i][j] * self.dt)
                self.ecrlx[i][j] = self.dt / (
                    (eps + self.sigma[i][j] * self.dt) * self.dx)
                self.ecrly[i][j] = self.dt / (
                    (eps + self.sigma[i][j] * self.dt) * self.dy)
                self.dtmdx[i][j] = self.dt / (mu * self.dx)
                self.dtmdy[i][j] = self.dt / (mu * self.dy)
                self.hdhvcn[i][j] = self.dt * (mu - mu_0) / mu

        # Amplitude of incident field components
        self.amplitude_x = -self.gaussian_pulse_amplitude * sin(
            radians(self.incident_angle))
        self.amplitude_y = self.gaussian_pulse_amplitude * cos(
            radians(self.incident_angle))

        # Run the selected mode
        if self.mode == 'TE':
            self.te()
        else:
            self.tm()

    def read_geometry_file(self):
        """
        Read the FDTD geometry file.
        :return:
        """

        # Get the base path for the file
        base_path = Path(__file__).parent

        with open((base_path / self.geometry_file).resolve(), 'r') as file:

            # Header Line 1: Comment
            _ = file.readline()

            # Header Line 2: nx ny
            line = file.readline()
            line_list = line.split()
            self.nx = int(line_list[0])
            self.ny = int(line_list[1])

            # Header Line 3: Comment
            _ = file.readline()

            # Header Line 4: dx dy
            line = file.readline()
            line_list = line.split()
            self.dx = float(line_list[0])
            self.dy = float(line_list[1])

            # Set up the PML areas first
            self.nx += 2 * self.number_of_pml
            self.ny += 2 * self.number_of_pml

            self.mu_r = zeros([self.nx, self.ny])
            self.eps_r = zeros([self.nx, self.ny])
            self.sigma = zeros([self.nx, self.ny])

            # Set up the maximum conductivities
            sigma_max_x = -3.0 * epsilon_0 * c * log(1e-5) / (
                2.0 * self.dx * self.number_of_pml)
            sigma_max_y = -3.0 * epsilon_0 * c * log(1e-5) / (
                2.0 * self.dy * self.number_of_pml)

            # Create the conductivity profile
            sigma_v = [((m + 0.5) / (self.number_of_pml + 0.5))**2
                       for m in range(self.number_of_pml)]

            # Back region
            for i in range(self.nx):
                for j in range(self.number_of_pml):
                    self.mu_r[i][j] = 1.0
                    self.eps_r[i][j] = 1.0
                    self.sigma[i][j] = sigma_max_y * sigma_v[self.number_of_pml
                                                             - 1 - j]

            # Front region
            for i in range(self.nx):
                for j in range(self.ny - self.number_of_pml, self.ny):
                    self.mu_r[i][j] = 1.0
                    self.eps_r[i][j] = 1.0
                    self.sigma[i][j] = sigma_max_y * sigma_v[
                        j - (self.ny - self.number_of_pml)]

            # Left region
            for i in range(self.number_of_pml):
                for j in range(self.ny):
                    self.mu_r[i][j] = 1.0
                    self.eps_r[i][j] = 1.0
                    self.sigma[i][j] += sigma_max_x * sigma_v[
                        self.number_of_pml - 1 - i]

            # Right region
            for i in range(self.nx - self.number_of_pml, self.nx):
                for j in range(self.ny):
                    self.mu_r[i][j] = 1.0
                    self.eps_r[i][j] = 1.0
                    self.sigma[i][j] += sigma_max_x * sigma_v[
                        i - (self.nx - self.number_of_pml)]

            # Read the geometry
            for i in range(self.number_of_pml, self.nx - self.number_of_pml):
                for j in range(self.number_of_pml,
                               self.ny - self.number_of_pml):

                    line = file.readline()

                    line_list = line.split()

                    # Relative permeability first
                    self.mu_r[i][j] = float(line_list[0])

                    # Relative permittivity next
                    self.eps_r[i][j] = float(line_list[1])

                    # Finally the conductivity
                    self.sigma[i][j] = float(line_list[2])
        file.close()

    def te(self):
        """
        TE mode.
        :return:
        """
        # Start at time = 0
        t = 0.0

        # Loop over the time steps
        for n in range(self.number_of_time_steps):

            # Update the scattered electric field
            self.escattered_te(t)

            # Advance the time by 1/2 time step
            t += 0.5 * self.dt

            # Update the scattered magnetic field
            self.hscattered_te(t)

            # Advance the time by 1/2 time step
            t += 0.5 * self.dt

            # Update the canvas
            self._update_canvas()

            # Progress
            print('{} of {} time steps'.format(n + 1,
                                               self.number_of_time_steps))

    def tm(self):
        """
        TM mode.
        :return:
        """
        # Start at time = 0
        t = 0.0

        # Loop over the time steps
        for n in range(self.number_of_time_steps):

            # Update the scattered electric field
            self.escattered_tm(t)

            # Advance the time by 1/2 time step
            t += 0.5 * self.dt

            # Update the scattered magnetic field
            self.hscattered_tm(t)

            # Advance the time by 1/2 time step
            t += 0.5 * self.dt

            # Update the canvas
            self._update_canvas()

            # Progress
            print('{} of {} time steps'.format(n + 1,
                                               self.number_of_time_steps))

    def eincident_te(self, t):
        """
        Calculate the incident electric field for TE mode.
        :param t: Time (s).
        :return:
        """
        # Calculate the incident electric field and derivative
        delay = 0

        # Calculate the decay rate determined by Gaussian pulse width
        alpha = (1.0 / (self.dt * self.gaussian_pulse_width / 4.0))**2

        # Calculate the period
        period = 2.0 * self.dt * self.gaussian_pulse_width

        # Spatial delay of each cell
        x_disp = -cos(radians(self.incident_angle))
        y_disp = -sin(radians(self.incident_angle))

        if x_disp < 0:
            delay -= x_disp * (self.nx - 2.0) * self.dx

        if y_disp < 0:
            delay -= y_disp * (self.ny - 2.0) * self.dy

        for i in range(self.number_of_pml, self.nx - self.number_of_pml):
            for j in range(self.number_of_pml, self.ny - self.number_of_pml):
                distance = i * self.dx * x_disp + j * self.dy * y_disp + delay
                a = 0
                a_prime = 0
                tau = t - distance / c

                if 0 <= tau <= period:
                    a = exp(-alpha *
                            (tau - self.gaussian_pulse_width * self.dt)**2)
                    a_prime = exp(-alpha * (tau - self.gaussian_pulse_width * self.dt) ** 2) \
                              * (-2.0 * alpha * (tau - self.gaussian_pulse_width * self.dt))

                self.exi[i][j] = self.amplitude_x * a
                self.dexi[i][j] = self.amplitude_x * a_prime

                self.eyi[i][j] = self.amplitude_y * a
                self.deyi[i][j] = self.amplitude_y * a_prime

    def escattered_te(self, t):
        """
        Calculate the scattered electric field for TE mode.
        :param t: Time (s).
        :return:
        """
        # Calculate the incident electric field
        self.eincident_te(t)

        # Update the x-component electric scattered field
        for i in range(self.nx - 1):
            for j in range(self.ny - 1):
                self.exs[i][j] = self.exs[i][j] * self.esctc[i][j] - self.eincc[i][j] * self.exi[i][j] \
                                 - self.edevcn[i][j] * self.dexi[i][j] + (self.hzs[i][j] - self.hzs[i][j - 1]) \
                                 * self.ecrly[i][j]

        # Update the y-component electric scattered field
        for i in range(1, self.nx - 1):
            for j in range(self.ny - 1):
                self.eys[i][j] = self.eys[i][j] * self.esctc[i][j] - self.eincc[i][j] * self.eyi[i][j] \
                                 - self.edevcn[i][j] * self.deyi[i][j] - (self.hzs[i][j] - self.hzs[i - 1][j]) \
                                 * self.ecrlx[i][j]

    def hincident_te(self, t):
        """
        Calculate the incident magnetic field for TE mode.
        :param t: Time (s).
        :return:
        """
        # Calculate the incident magnetic field and time derivative
        delay = 0.0
        eta = sqrt(mu_0 / epsilon_0)

        # Calculate the decay rate determined by Gaussian pulse width
        alpha = (1.0 / (self.gaussian_pulse_width * self.dt / 4.0))**2

        # Calculate the period
        period = 2.0 * self.gaussian_pulse_width * self.dt

        # Spatial delay of each cell
        x_disp = -cos(radians(self.incident_angle))
        y_disp = -sin(radians(self.incident_angle))

        if x_disp < 0:
            delay -= x_disp * (self.nx - 2.0) * self.dx

        if y_disp < 0:
            delay -= y_disp * (self.ny - 2.0) * self.dy

        for i in range(self.number_of_pml, self.nx - self.number_of_pml):
            for j in range(self.number_of_pml, self.ny - self.number_of_pml):
                distance = i * self.dx * x_disp + j * self.dy * y_disp + delay
                a_prime = 0
                tau = t - distance / c

                if 0 <= tau <= period:
                    a_prime = exp(-alpha * (tau - self.gaussian_pulse_width * self.dt) ** 2) \
                              * (-2.0 * alpha * (tau - self.gaussian_pulse_width * self.dt))

                self.dhzi[i][j] = self.gaussian_pulse_amplitude * a_prime / eta

    def hscattered_te(self, t):
        """
        Calculate the scattered magnetic field for TE mode.
        :param t: Time (s).
        :return:
        """
        # Calculate the incident magnetic field
        self.hincident_te(t)

        # Update the scattered magnetic field
        for i in range(self.nx - 1):
            for j in range(self.ny - 1):
                self.hzs[i][j] = self.hzs[i][j] - (self.eys[i + 1][j] - self.eys[i][j]) * self.dtmdx[i][j] \
                                + (self.exs[i][j + 1] - self.exs[i][j]) * self.dtmdy[i][j] - self.hdhvcn[i][j] \
                                * self.dhzi[i][j]

    def eincident_tm(self, t):
        """
        Calculate the incident electric field for TM mode.
        :param t: Time (s).
        :return:
        """
        # Calculate the incident electric field and derivative
        delay = 0

        # Calculate the decay rate determined by Gaussian pulse width
        alpha = (1.0 / (self.dt * self.gaussian_pulse_width / 4.0))**2

        # Calculate the period
        period = 2.0 * self.dt * self.gaussian_pulse_width

        # Spatial delay of each cell
        x_disp = -cos(radians(self.incident_angle))
        y_disp = -sin(radians(self.incident_angle))

        if x_disp < 0:
            delay -= x_disp * (self.nx - 2.0) * self.dx

        if y_disp < 0:
            delay -= y_disp * (self.ny - 2.0) * self.dy

        for i in range(self.number_of_pml, self.nx - self.number_of_pml):
            for j in range(self.number_of_pml, self.ny - self.number_of_pml):
                distance = i * self.dx * x_disp + j * self.dy * y_disp + delay
                a = 0
                a_prime = 0
                tau = t - distance / c

                if 0 <= tau <= period:
                    a = exp(-alpha *
                            (tau - self.gaussian_pulse_width * self.dt)**2)
                    a_prime = exp(-alpha * (tau - self.gaussian_pulse_width * self.dt) ** 2) \
                              * (-2.0 * alpha * (tau - self.gaussian_pulse_width * self.dt))

                self.ezi[i][j] = self.gaussian_pulse_amplitude * a
                self.dezi[i][j] = self.gaussian_pulse_amplitude * a_prime

    def escattered_tm(self, t):
        """
        Calculate the scattered electric field for TM mode.
        :param t: Time (s).
        :return:
        """
        # Calculate the incident electric field
        self.eincident_tm(t)

        # Update the z-component electric scattered field
        for i in range(1, self.nx - 1):
            for j in range(1, self.ny - 1):
                self.ezs[i][j] = self.ezs[i][j] * self.esctc[i][j] - self.eincc[i][j] * self.ezi[i][j] \
                                 - self.edevcn[i][j] * self.dezi[i][j] + (self.hys[i][j] - self.hys[i - 1][j]) \
                                 * self.ecrlx[i][j] - (self.hxs[i][j] - self.hxs[i][j - 1]) * self.ecrly[i][j]

    def hincident_tm(self, t):
        """
        Calculate the incident magnetic field for TM mode.
        :param t: Time (s).
        :return:
        """
        # Calculate the incident magnetic field and derivative
        delay = 0
        eta = sqrt(mu_0 / epsilon_0)

        # Calculate the decay rate determined by Gaussian pulse width
        alpha = (1.0 / (self.dt * self.gaussian_pulse_width / 4.0))**2

        # Calculate the period
        period = 2.0 * self.dt * self.gaussian_pulse_width

        # Spatial delay of each cell
        x_disp = -cos(radians(self.incident_angle))
        y_disp = -sin(radians(self.incident_angle))

        if x_disp < 0:
            delay -= x_disp * (self.nx - 2.0) * self.dx

        if y_disp < 0:
            delay -= y_disp * (self.ny - 2.0) * self.dy

        for i in range(self.number_of_pml, self.nx - self.number_of_pml):
            for j in range(self.number_of_pml, self.ny - self.number_of_pml):
                distance = i * self.dx * x_disp + j * self.dy * y_disp + delay
                a = 0
                a_prime = 0
                tau = t - distance / c

                if 0 <= tau <= period:
                    a = exp(-alpha *
                            (tau - self.gaussian_pulse_width * self.dt)**2)
                    a_prime = exp(-alpha * (tau - self.gaussian_pulse_width * self.dt) ** 2) \
                              * (-2.0 * alpha * (tau - self.gaussian_pulse_width * self.dt))

                self.dhxi[i][j] = self.gaussian_pulse_amplitude * a_prime / eta
                self.dhyi[i][j] = self.gaussian_pulse_amplitude * a_prime / eta

    def hscattered_tm(self, t):
        """
        Calculate the scattered magnetic field for TM mode.
        :param t:
        :return:
        """
        # Calculate the incident magnetic field
        self.hincident_tm(t)

        # Update the X component of the magnetic scattered field
        for i in range(1, self.nx - 1):
            for j in range(self.ny - 1):
                self.hxs[i][j] = self.hxs[i][j] - (
                    self.ezs[i][j + 1] - self.ezs[i][j]) * self.dtmdx[i][j]

        for i in range(self.nx - 1):
            for j in range(1, self.ny - 1):
                self.hys[i][j] = self.hys[i][j] + (
                    self.ezs[i + 1][j] - self.ezs[i][j]) * self.dtmdx[i][j]

    def _update_canvas(self):
        # Remove the color bar
        try:
            self.cbar.remove()
        except:
            print('Initial Plot')

        # Clear the axes for the updated plot
        self.axes1.clear()

        # Calculate the total field for plotting
        etotal = zeros([self.nx, self.ny])

        if self.mode == 'TM':
            for i in range(self.nx):
                for j in range(self.ny):
                    etotal[i][j] = self.ezi[i][j] + self.ezs[i][j]
        else:
            for i in range(self.nx):
                for j in range(self.ny):
                    extotal = self.exi[i][j] + self.exs[i][j]
                    eytotal = self.eyi[i][j] + self.eys[i][j]
                    etotal[i][j] = sqrt(extotal**2 + eytotal**2)

        # x and y grid for plotting
        x = linspace(0, self.nx * self.dx, self.nx)
        y = linspace(0, self.ny * self.dy, self.ny)

        x_grid, y_grid = meshgrid(x, y)

        # Display the results
        im = self.axes1.pcolor(x_grid,
                               y_grid,
                               abs(etotal),
                               cmap="jet",
                               vmin=0,
                               vmax=self.gaussian_pulse_amplitude)
        self.cbar = self.fig.colorbar(im,
                                      ax=self.axes1,
                                      orientation='vertical')
        self.cbar.set_label('Electric Field (V/m)', size=10)

        # Set the tick label size
        self.axes1.tick_params(labelsize=12)

        # Update the canvas
        self.my_canvas.draw()
        self.my_canvas.flush_events()
Пример #3
0
class SingleEnergyPitchIJ(QtWidgets.QMainWindow):
    def __init__(self, argv):
        QtWidgets.QMainWindow.__init__(self)

        self.ui = SingleEnergyPitchIJ_design.Ui_SingleEnergyPitchIJ()
        self.ui.setupUi(self)

        filename = None
        if len(argv) > 1:
            raise Exception("Too many input arguments given to 's12ij'.")
        elif len(argv) == 1:
            filename = argv[0]

        self.plotWindow = PlotWindow()

        self.GF = None
        self.GFintensity = None

        self.superPlotCanvas = None
        self.superPlotLayout = None
        self.superPlotAx = None
        self.superPlotHandle = None
        self.superPlotDomHandle = None
        self.pitchAngles = None
        self.overlay = None
        self.overlayHandle = None

        self.toggleEnabled(False)

        self.bindEvents()

        self.gerimap, _ = registerGeriMap(None)

        if filename is not None and os.path.isfile(filename):
            self.loadFile(filename)

    def bindEvents(self):
        self.ui.btnBrowse.clicked.connect(self.openFile)
        self.ui.btnBrowseOverlay.clicked.connect(self.openOverlay)
        self.ui.btnSaveImage.clicked.connect(self.saveImage)
        self.ui.btnSaveSuper.clicked.connect(self.saveSuper)
        self.ui.btnSaveBoth.clicked.connect(self.saveBoth)

        self.ui.sliderEnergy.valueChanged.connect(self.energyChanged)
        self.ui.sliderPitchAngle.valueChanged.connect(
            self.pitchAngleParameterChanged)
        self.ui.sliderOverlay.valueChanged.connect(self.overlaySliderChanged)

        self.ui.cbUnderlay.toggled.connect(self.toggleOverlayType)

    def closeEvent(self, event):
        self.exit()

    def exit(self):
        self.plotWindow.close()
        self.close()

    def setupSuperPlot(self):
        ymax = 1.1
        z = np.zeros(self.pitchAngles.shape)

        self.superPlotLayout = QtWidgets.QVBoxLayout(self.ui.widgetDistPlot)

        self.superPlotCanvas = FigureCanvas(Figure())
        self.superPlotLayout.addWidget(self.superPlotCanvas)

        self.superPlotAx = self.superPlotCanvas.figure.subplots()
        self.superPlotHandle, = self.superPlotAx.plot(self.pitchAngles,
                                                      z,
                                                      linewidth=2)
        self.superPlotDomHandle, = self.superPlotAx.plot([0, 0], [0, ymax],
                                                         'k--')

        self.superPlotAx.set_xlim([self.pitchAngles[0], self.pitchAngles[-1]])
        self.superPlotAx.set_ylim([0, ymax])
        self.superPlotAx.get_yaxis().set_ticks([])

        self.superPlotAx.set_xlabel(r'$\theta_{\rm p}\ \mathrm{(rad)}$')
        self.superPlotAx.set_ylabel(r'$f(\theta_{\rm p}) / f_{\rm max}$')

        self.superPlotCanvas.figure.tight_layout(pad=2.5)

    def updateSuperPlot(self, f=None):
        ei = self.getEnergyIndex()

        if f is None:
            f = self.getDistributionFunction()

        superParticle = self.GFintensity[ei, :] * f
        superParticle = superParticle / np.amax(superParticle)
        maxpitch = self.pitchAngles[np.argmax(superParticle)]

        self.ui.lblDomPitch.setText('{0:.3f} rad'.format(maxpitch))

        self.superPlotHandle.set_ydata(superParticle)
        self.superPlotDomHandle.set_xdata([maxpitch, maxpitch])

        self.superPlotCanvas.draw()
        self.superPlotCanvas.flush_events()

    def getDistributionFunction(self):
        C = self.ui.sliderPitchAngle.value()

        f = np.exp(C * self.cosPitchAngles)
        return f

    def energyChanged(self):
        ei = self.ui.sliderEnergy.value()
        if len(self.GF._param1.shape) == 2:
            self.ui.lblEnergy.setText('{0:.2f}'.format(self.GF._param1[0][ei]))
        else:
            self.ui.lblEnergy.setText('{0:.2f}'.format(self.GF._param1[ei]))

        f = self.getDistributionFunction()
        self.updateSuperPlot(f=f)
        self.updateImage(f=f)

    def pitchAngleParameterChanged(self):
        self.ui.lblPitchAngle.setText(str(self.ui.sliderPitchAngle.value()))

        f = self.getDistributionFunction()
        self.updateSuperPlot(f=f)
        self.updateImage(f=f)

    def toggleEnabled(self, enabled=False):
        self.ui.lblREEnergy.setEnabled(enabled)
        self.ui.lblREPitchAngle.setEnabled(enabled)

        self.ui.lblEnergy.setEnabled(enabled)
        self.ui.lblPitchAngle.setEnabled(enabled)

        self.ui.sliderEnergy.setEnabled(enabled)
        self.ui.sliderPitchAngle.setEnabled(enabled)

        self.ui.lblEnergyMin.setEnabled(enabled)
        self.ui.lblEnergyMax.setEnabled(enabled)
        self.ui.lblPitchAngleMin.setEnabled(enabled)
        self.ui.lblPitchAngleMax.setEnabled(enabled)

        self.ui.lblOverlay.setEnabled(enabled)
        self.ui.lblOverlayMin.setEnabled(enabled)
        self.ui.lblOverlayMax.setEnabled(enabled)
        self.ui.lblOverlay25.setEnabled(enabled)
        self.ui.lblOverlay50.setEnabled(enabled)
        self.ui.lblOverlay75.setEnabled(enabled)
        self.ui.btnBrowseOverlay.setEnabled(enabled)
        self.ui.tbOverlay.setEnabled(enabled)
        self.ui.sliderOverlay.setEnabled(enabled)

        self.ui.widgetDistPlot.setEnabled(enabled)

    def loadFile(self, filename):
        self.ui.tbFilename.setText(filename)

        self.GF = Green(filename)

        if not self.validateGreensFunction(self.GF):
            return

        self.toggleEnabled(True)
        if len(self.GF._param2.shape) == 2:
            self.pitchAngles = np.abs(self.GF._param2[0])
        else:
            self.pitchAngles = np.abs(self.GF._param2)

        self.cosPitchAngles = np.cos(self.pitchAngles)

        # Sum all pixels of each image
        self.GFintensity = np.sum(self.GF.FUNC, axis=(2, 3)) * np.sin(
            self.pitchAngles)

        self.setupEnergySlider()
        self.setupSuperPlot()
        self.setupImage()

        f = self.getDistributionFunction()
        self.updateSuperPlot(f=f)
        self.updateImage(f=f)

    def loadOverlay(self, filename):
        self.ui.tbOverlay.setText(filename)
        self.overlay = mpimg.imread(filename)

        self.setupOverlay()

    def openFile(self):
        filename, _ = QFileDialog.getOpenFileName(
            parent=self,
            caption="Open SOFT Green's function file",
            filter="SOFT Green's function (*.mat *.h5 *.hdf5);;All files (*.*)"
        )

        if filename:
            self.loadFile(filename)

    def openOverlay(self):
        filename, _ = QFileDialog.getOpenFileName(
            parent=self,
            caption="Open image overlay",
            filter="Portable Network Graphics (*.png)")

        if filename:
            self.loadOverlay(filename)

    def validateGreensFunction(self, gf):
        if gf.getFormat() != '12ij':
            QMessageBox.critical(
                self, 'Invalid input file',
                "The specified Green's function is not of the appropriate format. Expected '12ij', got '{0}'."
                .format(gf.getFormat()))
            return False

        pn = gf.getParameterName('1')
        if pn != 'gamma' and pn != 'p':
            QMessageBox.critical(
                self, 'Invalid input file',
                "The first momentum parameter has an invalid type: '{0}'. Expected either 'gamma' or 'p'."
                .format(pn))
            return False

        return True

    def saveImage(self):
        self.doSaveImage()

    def saveSuper(self):
        self.doSaveSuper()

    def doSaveImage(self, filename=None):
        if filename is None:
            filename, _ = QFileDialog.getSaveFileName(
                self,
                caption='Save synthetic image',
                filter=
                'Portable Document Form (*.pdf);;Portable Network Graphics (*.png);;Encapsulated Post-Script (*.eps);;Scalable Vector Graphics (*.svg)'
            )

        if not filename:
            return

        self.imageAx.set_axis_off()
        self.plotWindow.figure.subplots_adjust(top=1,
                                               bottom=0,
                                               right=1,
                                               left=0,
                                               hspace=0,
                                               wspace=0)

        self.imageAx.get_xaxis().set_major_locator(
            matplotlib.ticker.NullLocator())
        self.imageAx.get_yaxis().set_major_locator(
            matplotlib.ticker.NullLocator())

        fcolor = self.plotWindow.figure.patch.get_facecolor()

        self.plotWindow.canvas.print_figure(filename,
                                            bbox_inches='tight',
                                            pad_inches=0,
                                            dpi=300)

    def doSaveSuper(self, filename=None):
        if filename is None:
            filename, _ = QFileDialog.getSaveFileName(
                self,
                caption='Save super particle',
                filter=
                'Portable Document Form (*.pdf);;Portable Network Graphics (*.png);;Encapsulated Post-Script (*.eps);;Scalable Vector Graphics (*.svg)'
            )

        if not filename:
            return

        self.superPlotCanvas.print_figure(filename, bbox_inches='tight')

    def saveBoth(self):
        filename, _ = QFileDialog.getSaveFileName(
            self,
            caption='Save both figures',
            filter=
            'Portable Document Form (*.pdf);;Portable Network Graphics (*.png);;Encapsulated Post-Script (*.eps);;Scalable Vector Graphics (*.svg)'
        )

        if not filename:
            return

        f = filename.split('.')
        filename = str.join('.', f[:-1])
        ext = f[-1]

        if filename.endswith('_image') or filename.endswith('_super'):
            filename = filename[:-6]

        imgname = filename + '_image.' + ext
        supname = filename + '_super.' + ext

        self.doSaveImage(filename=imgname)
        self.doSaveSuper(filename=supname)

    def setupEnergySlider(self):
        if len(self.GF._param1.shape) == 2:
            vmin, vmax, vn = self.GF._param1[0][0], self.GF._param1[0][
                -1], self.GF._param1[0].size
        else:
            vmin, vmax, vn = self.GF._param1[0], self.GF._param1[
                -1], self.GF._param1.size

        if self.GF.getParameterName('1') == 'gamma':
            self.ui.lblREEnergy.setText('Runaway energy (mc²)')
        else:
            self.ui.lblREEnergy.setText('Runaway momentum (mc)')

        self.ui.lblEnergyMin.setText('{0:.2f}'.format(vmin))
        self.ui.lblEnergyMax.setText('{0:.2f}'.format(vmax))
        self.ui.lblEnergy.setText('{0:.2f}'.format(vmin))

        self.ui.sliderEnergy.setMinimum(0)
        self.ui.sliderEnergy.setMaximum(vn - 1)
        self.ui.sliderEnergy.setSingleStep(1)

    def getEnergyIndex(self):
        return self.ui.sliderEnergy.value()

    def setupImage(self):
        lbl = ''.join(random.choices(string.ascii_uppercase, k=4))
        self.imageAx = self.plotWindow.figure.add_subplot(111, label=lbl)

        a = 1
        if self.overlayHandle is not None:
            self.setupOverlay()
            if not self.ui.cbUnderlay.isChecked():
                a = 1 - (self.ui.sliderOverlay.value()) / 100.0

        dummy = np.zeros(self.GF._pixels)
        self.imageHandle = self.imageAx.imshow(dummy,
                                               cmap=self.gerimap,
                                               alpha=a,
                                               interpolation=None,
                                               clim=(0, 1),
                                               extent=[-1, 1, -1, 1],
                                               zorder=1)
        self.imageAx.get_xaxis().set_visible(False)
        self.imageAx.get_yaxis().set_visible(False)

        if not self.plotWindow.isVisible():
            self.plotWindow.show()

        self.plotWindow.drawSafe()

    def setupOverlay(self):
        if self.overlayHandle is not None:
            self.overlayHandle.remove()

        self.overlayHandle = self.imageAx.imshow(self.overlay,
                                                 extent=[-1, 1, -1, 1],
                                                 zorder=0)
        self.updateImage()
        self.plotWindow.drawSafe()

    def toggleOverlayType(self):
        ic = self.ui.cbUnderlay.isChecked()
        if not ic:
            self.gerimap, _ = registerGeriMap(None)
        else:
            self.gerimap, _ = registerGeriMap(transparencyThreshold=0.25)

        self.ui.sliderOverlay.setEnabled(not ic)
        self.ui.lblOverlayMin.setEnabled(not ic)
        self.ui.lblOverlay25.setEnabled(not ic)
        self.ui.lblOverlay50.setEnabled(not ic)
        self.ui.lblOverlay75.setEnabled(not ic)
        self.ui.lblOverlayMax.setEnabled(not ic)

        self.setupImage()

        f = self.getDistributionFunction()
        self.updateSuperPlot(f=f)
        self.updateImage(f=f)

    def overlaySliderChanged(self):
        self.updateImage()

    def updateImage(self, f=None):
        ei = self.getEnergyIndex()

        if f is None:
            f = self.getDistributionFunction()

        g = self.GF[ei, :, :, :]
        I = 0
        for i in range(0, f.size):
            I += g[i, :, :] * f[i]

        I = I.T / np.amax(I)

        self.imageHandle.set_data(I)

        if self.ui.cbUnderlay.isChecked() or self.overlayHandle is None:
            self.imageHandle.set_alpha(1)
        else:
            a = float(self.ui.sliderOverlay.value()) / 100.0
            self.imageHandle.set_alpha(1 - a)

        self.plotWindow.drawSafe()