Tutorial: Adding new functionality to RepTate

Goals

In this section, we will present how to create a new RepTate application. We will show the important steps needed to build a new RepTate application. To do so, we will need to modify some already existing RepTate code, as well as writing new custom code.

As an example, let us say that we want to create a new RepTate application that:

  1. accepts text files (.xy) which are supposed to be composed of:

    • a first line (header) containing the date and temperature T

    • two columns containing data \(x\) and \(y\)

  2. allows to view the data:

    • without transformation, \(x\) vs \(y\)

    • \(x\) vs \(\sqrt{y}\)

  3. has a theory: a simple line fitting with two parameters \(a\) and \(b\) such that \(y = ax+b\).

Tip

Each time we need to modify or add some line of Python code, the line number is indicated on the left hand side for information. This should correspond to the same line numbering for the “template” files, but might be different for the files in the core/ or gui/ folders.

New application

Create a new application file

To create a new RepTate application, we will use the template application file ApplicationTemplate.py that can be found in the applications/ folder.

  1. Make a copy of this file and rename it with a sensible name that relates to the application purpose. According to the Goals section, we name it ApplicationXY.py.

  2. Open ApplicationXY.py with your favourite text editor and replace all the occurrences of “Template” by “XY”. For example,

    44 class ApplicationTemplate(QApplicationWindow):
    

    becomes

    44 class ApplicationXY(QApplicationWindow):
    
  3. Give a brief description of the purpose of the application, e.g. “Application reading XY files”. The first lines of ApplicationXY.py should now look like

    33"""Module ApplicationXY
    34
    35Application reading XY files
    36
    37"""
    

The file ApplicationXY.py is ready for the next round of modifications that are (i) file types accepted by the application, (ii) the views, and (iii) the theories, as defined in the Goals section. But first, we have to let RepTate know about our new application.

Edit RepTate’s QApplicationManager

We need to add a reference to this new application into RepTate’s QApplicationManager, so it knows it exists. To do so:

  1. Insert this line in the top part of the file gui/QsApplicationManager.py, e.g.

    77from RepTate.applications.ApplicationXY import ApplicationXY
    
  2. Insert the following line to add an entry to the QApplicationManager dictionary

    155self.available_applications[ApplicationXY.appname] = ApplicationXY
    

In order to have our new application available in the Graphical User Interface (GUI), we need to create a new “button” that will launch our new application when clicked.

  1. Add a button in the main RepTate tool-bar by inserting the following lines in the __init__ method of gui/QApplicationManager.py. The icon name (filename) should correspond to the appname, here XY.png. See the section New Icons to create and use your onwn icon in RepTate.

    258 # ApplicationXY button
    259 #choose the button icon
    260 icon = QIcon(':/Icon8/Images/new_icons/XY.png')
    261 tool_tip = 'XY'  # text that appear on hover
    262 self.actionXY = QAction(icon, tool_tip, self)
    263 #insert the new button before the "MWD" button
    264 self.toolBarApps.insertAction(self.actionMWD, self.actionXY)
    
  2. The new button has been successfully inserted into the application tool bar. However, if we click on it, nothing happens because it is not linked to any action. We need to “wire” (connect) this new button to a “function”. In the same file gui/QApplicationManager.py, below the previous lines, add

    266 #connect button
    267 self.actionXY.triggered.connect(lambda: self.handle_new_app('XY'))
    

    Warning

    The application name (appname), defined at line 46 of ApplicationXY.py, should then be “XY”. Additionally, the icon name defining the logo of the new application should be named “XY.png”, see the definition of the handle_new_app method.

Note

Our new application is ready to be used in RepTate!

Note on default theories

By default, some “basic theories” are included with the application (e.g. polynomial, power-law, exponential). To remove all these “basic theories” from your new application, comment the following line in the __init__ method of class ApplicationXY

135self.add_common_theories()  # Add basic theories to the application

New file type

RepTate applications are designed to accept a only a predefined file extension. As defined in the Goals section, we want our new application ApplicationXY.py to accept .xy files. To do so, we modify ApplicationXY.py as follows.

In class ApplicationXY, before def __new__, add

48extension = "xy"  # drag and drop this extension automatically opens this application

In the __init__ method of class ApplicationXY add

86# set the type of files that ApplicationTemplate can open
87ftype = TXTColumnFile(
88    name='XY data',  # name the type of data
89    extension='xy',  # file extension
90    description='XY data from XY-experiment',
91    col_names=['X', 'Y'],  # name the variables for legend
92    basic_file_parameters=['date', 'T'],  # parameter in file header
93    col_units=['-', '-'])  # units of X and Y (here none)

New view

About the “old” view

At the moment, only one view is allowed in our new ApplicationXY. That view is located in the __init__ method of class ApplicationXY:

62# VIEWS
63# set the views that can be selected in the view combobox
64self.views['y(x)'] = View(
65    name='y(x)',
66    description='y as a function of x',
67    x_label='x',
68    y_label='y(x)',
69    x_units='-',
70    y_units='-',
71    log_x=False,
72    log_y=False,
73    view_proc=self.viewyx,
74    n=1,
75    snames=['y(x)'])

The important attributes of the view called “y(x)” are:

  • the x- and y-label to be used in the plot,

  • the units that are appended to the x- and y-labels,

  • the log_x and log_y define whether the axes should be in in log-scale (base 10)

  • self.viewyx is the method that defines what operations are done on the data before plotting them (see below),

  • n defines the number of series the view is plotting.

In the line below, you can define the default number of view, i.e., the number of views that appear when you open the appliction. In case the new application would benefit from having multiple views shown at the same time (similar to the React or Stress Relaxation applications), this number can be increased (up to 4)

75# set multiviews
76# default view order in multiplot views, set nplots=1 for single view
77self.nplots = 1

The definition of the method viewyx is given by

108def viewyx(self, dt, file_parameters):
109    """Documentation"""
110    x = np.zeros((dt.num_rows, 1))
111    y = np.zeros((dt.num_rows, 1))
112    x[:, 0] = dt.data[:, 0]
113    y[:, 0] = dt.data[:, 1]
114    return x, y, True

The two lines x[:, 0] = dt.data[:, 0] and y[:, 0] = dt.data[:, 1] tell us that viewyx does not perform any operations on the data. It simply copies the input data into x and y arrays. It means that we already have one of the views required from the Goals section.

Definition of a new view

To define a new view that shows \(x\) vs \(\sqrt{y}\), as requested in the Goals section, we add a view to self.views dictionary. The new view is called “sqrt(y)”. In the __init__ method of class ApplicationXY, add

74self.views['sqrt(y)'] = View(
75    name='sqrt(y)',
76    description='sqrt(y) as a function of x',
77    x_label='x',
78    y_label='$y^{1/2}$',
79    x_units='-',
80    y_units='-',
81    log_x=False,
82    log_y=False,
83    view_proc=self.view_sqrt_y,
84    n=1,
85    snames=['sqrt(y)'])

Tip

The x_label and y_label support LaTeX-like syntax.

We also need to define the new method view_sqrt_y. In class ApplicationXY, add the definition

118def view_sqrt_y(self, dt, file_parameters):
119    """Documentation"""
120    x = np.zeros((dt.num_rows, 1))
121    y = np.zeros((dt.num_rows, 1))
122    x[:, 0] = dt.data[:, 0]
123    y[:, 0] = (dt.data[:, 1])**0.5
124    return x, y, True

Note

The new view is ready!

New theory

Create a new theory file

To create a new RepTate application, we will use the template theory file TheoryTemplate.py that can be found in RepTate theories/ folder.

  1. Make a copy of this file and rename it with a sensible name that relates to the theory purpose. According to the Goals section, we name it TheoryLine.py.

  2. Open TheoryLine.py with your favourite text editor and replace all the occurrences of “Template” by “Line”. For example,

    42 class TheoryTemplate(QTheory):
    

    becomes

    42 class TheoryLine(QTheory):
    
  3. Give a brief description of the purpose of the application, e.g. “ Theory fitting a line to the data”. The first lines of TheoryLine.py should now look like

    33"""Module TheoryLine
    34
    35Theory fitting a line to the data
    36
    37"""
    38import numpy as np
    

The file TheoryLine.py is ready for the next round of modifications that are (i) define the parameters, (ii) define the theory “function”. But first, we have to let ApplicationXY (developed just above) know about our new theory.

Edit ApplicationXY.py

We need to add a reference to this new theory into ApplicationXY.py, so it knows it exists. To do so:

  1. Insert the following line in the __init__ method of class ApplicationXY, after the “# IMPORT THEORIES” comment

    54 # IMPORT THEORIES
    55 # Import theories specific to the Application e.g.:
    56 from RepTate.theories.TheoryLine import TheoryLine
    

    Hint

    We choose to place the theories import inside the __init__ method of class ApplicationXY rather than in the very top of the file ApplicationXY.py as this prevents RepTate from loading all theories at start. Instead, theories are loaded only when an application using them is opened.

  2. Insert the following line, also in the __init__ method of class ApplicationXY, after the # THEORIES, and before self.add_common_theories(), the line

    102 self.theories[TheoryLine.thname] = TheoryLine
    

Edit the theory

According to the Goals section, the theory should define a straight line \(y=ax+b\), hence there are two parameters. We will (i) write a short documentation of our new theory, (ii) define the parameters, and (iii) write the main function that calculates the theory values.

  1. Add a Python docstring to (auto)-document the theory. Place some description of the goal of the theory as well as a description of the parameters. This will help future reader of the file understand the purpose of the theory and it will be automatically integrated to the online RepTate documentation (reptate.readthedocs).

    42 class TheoryLine(QTheory):
    43     """Fit a straight line.
    44
    45     * **Function**
    46         .. math::
    47             y = a x + b
    48
    49     * **Parameters**
    50     - :math:`a`: slope of the line
    51     - :math:`b`: the :math:`y`:-intercept
    52
    53     """
    
  2. To define the theory parameters, \(a\) and \(b\), we modify the __init__ method of class TheoryLine to have only these two parameter definitions

    60 self.parameters['a'] = Parameter(
    61     name='a',
    62     value=1,
    63     description='parameter a',
    64     type=ParameterType.real,
    65     opt_type=OptType.opt)
    66 self.parameters['b'] = Parameter(
    67     name='b',
    68     value=0,
    69     description='parameter b',
    70     type=ParameterType.real,
    71     opt_type=OptType.opt)
    

    The important attributes of the parameters are:

    • value: the initial value of the parameter

    • type: defines if he parameter is real, integer or discrete

    • opt_type: optimisation type is either const for constant parameter (cannot be optimised), opt if the parameter is optimised by default, nopt if the parameter can be optimised but is not by default.

  3. Modify the method calculate of class TheoryLine

    89 ft = f.data_table
    90 tt = self.tables[f.file_name_short]
    91 tt.num_columns = ft.num_columns
    92 tt.num_rows = ft.num_rows
    93 tt.data = np.zeros((tt.num_rows, tt.num_columns))
    94 a = self.parameters['a'].value
    95 b = self.parameters['b'].value
    96 tt.data[:, 0] = ft.data[:, 0]  # x values
    97 tt.data[:, 1] = a * ft.data[:, 0] + b  # y values
    

    Hint

    • The file type of ApplicationXY defined in section New file type tells us that there are two columns in the data files. Hence, the theory data also have two columns to populate. For example of application/theory using more than two data columns, see class ApplicationLVE of ApplicationLVE.py and class TheoryMaxwellModesFrequency of TheoryMaxwellModes.py.

    • The information from the data file header, in our example date and T, can be called via, e.g. T = float(f.file_parameters["T"]). Parameters are stored as strings, hence the float conversion.

Note

The new “Line” theory is ready to be used in our new ApplicationXY!

New Icons

Application icons are stored in a compiled resource file gui/MainWindow_rc.py. In order to add a new icon to this resource file, that can later be used as a button icon for instance, we need to

  1. Modify the file gui/MainWindow.qrc by opening it in a text editor and add the relative path of the new image or icon we want to have in the resource file. For instance:

    • copy and paste you favourite icon my_favourite_icon.png in the gui/Images/new_icons/ folder.

    • add the line <file>Images/new_icons/my_favourite_icon.png</file> to the file gui/MainWindow.qrc

  2. Re-compile the file MainWindow_rc.py into a resource file MainWindow_rc.py by running the following command in a terminal (assuming the current working directory is gui/)

    $ rcc MainWindow.qrc -o MainWindow_rc.py
    

Note

Your new icon my_favourite_icon.png is now ready to be used by Qt:

icon = QIcon(':/Icons/Images/new_icons/my_favourite_icon.png')