This interface works both to call LatNet Builder as a Python module (in a Python script), and as a GUI interface. To use the GUI, please refer to the instructions in the README of the repository.
The files notebooks/Example_Python_module.py
and notebooks/Example_Python_module.ipynb
contain an example of how to use this interface in a Python script.
The GUI was built using Jupyter and ipywidgets. It allows to easily run LatNet Builder without having to manually construct the command line.
The application is based on the following stack of technologies:
- the Jupyter notebook and ipywidgets (interactive widgets for Jupyter) for user interactivity
- the Python modules subprocess and threading, which allow to handle processes and threads from Python, as well as matplotlib, a plotting library for Python
- the LatNet Builder software, called from its command line interface
- the Jupyter extension appmode for the final rendering of the user interface
The packaging as a Docker container is based on Docker and ready-to-run Docker images created by the Jupyter project.
The online interface is powered by Binder, which is essentially a bridge between:
- jupyter-repo2docker, which is a tool to build, run, and push Docker images from source code repositories. The image contains a Jupyter installation, customized according to the specifications found in the repository, in the
environment.yml
file. - JupyterHub, which hosts user instances with a Jupyter server in the cloud. See this excellent blog post for more information.
The goal of this part is to present information for developers to extend the Python Interface.
The dev install is pretty straight-forward (at least for Linux and Mac OS X users):
- Download the LatNet Builder source code
- Install all the dependencies for LatNet Builder (FFTW, GMP, NTL, Boost) listed here
- Install all the dependencies for the interface: a conda distribution, then
conda config --add channels conda-forge
conda create -n dev-latnetbuilder python ipywidgets widgetsnbextension notebook matplotlib numexpr appmode numpy
- Run from the root directory of LatNet Builder
./waf configure --prefix PATH_TO_YOUR_INSTALLATION_FOLDER
./waf build
./waf install
Once this is done, you can test the Python GUI by running from the python-wrapper folder
source activate dev-latnetbuilder
jupyter notebook notebooks/Interface-dev.ipynb
In the notebook, modify the latnetbuilder.PATH_TO_LATNETBUILDER variable to fit your PATH_TO_YOUR_INSTALLATION_FOLDER.
This method allows you to modify the Python code and test the new version simply by restarting the Jupyter notebook kernel (see the doc from Jupyter for more information).
The code is structured in the following way:
-
in folder
code_output
: .txt files in several languages (as of now Python, C++, and Matlab) that allow to construct data points from the generating vector computed by LatNetBuilder. The code is displayed as text in the GUI, for the user to copy-paste. Furthermore, the same Python code is used in the Python interface (with anexec
statement) to generate the points (see methodpoints()
of classSearch
). -
in folder
notebooks
: the notebooks used by the GUI (both in dev and production modes) -
in folder
gui
: the source code to produce the GUI (see more explanations below) -
search.py
andparse_output.py
contain classes to represent a Search instance (that is, the search parameters defined by the user) and a Result instance. They are (almost) independent from the GUI side, and can be used separately.
The GUI is based on the ipywidgets library, and follows an object-oriented design. It is an instanciation of the GUI
class (defined in gui/__init__.py
), which possesses several BaseGUIElement
as attributes. A BaseGUIElement
corresponds to a logical tile in the GUI. Each BaseGUIElement
has several attributes, which are individual ipywidgets. These widgets can be buttons, dropdowns, text areas, sliders, and also containers (VBox and Hbox) for other widgets.
Before going any further, we invite the unfamiliar developer to have a look at the great documentation of ipywidgets, up to the section about Layout and Styling of Jupyter widgets. The section about widget events is particularly important, as it explains the GUI interactivity.
To summarize the main ideas about widget events, when the user modifies the value of a widget, an event is triggered by the library Traitlets, and a callback can run to handle the event. The registered callback must have the signature handler(change)
where change
is a dictionary holding the information about the change. For us, useful keys of this dictionary are value
(which contains the name of the trait of the widget that has changed), and new
(which contains the new value of this trait).
For example, the following is a valid callback:
def handler(change):
print(change)
Because we want the callbacks to modify the gui object, we would like to pass the gui as a second argument to the callback, for instance:
def handler(change, gui):
print(change)
gui.my_element.my_widget.value = 42
For such a syntax to be valid, we need to use lambda functions, and declare the callback to be lambda change: handler(change, gui)
. The class BaseGUIElement
in gui/common.py
implements this little trick, while allowing for a rather intuitive syntax to add new widgets and callbacks.
A typical (simple) example of a file used to define an instance of BaseGUIElement
is gui/lattice_type.py
, which has been written as a tutorial file. Look at the bottom first for the function lattice_type()
. It starts by defining some widgets that will be used by that Element, then instanciates and returns a BaseGUIElement
. Above in the file, you can see a callback used by that element. All callbacks share the same signature (explained above) and the same structure: depending on the new value of the widget that fired the callback, they update the GUI accordingly.
To add a new GUI element, a good starting point would be to copy-paste this file, and work from there.
Some of the callbacks can be quite complex, reflecting the complexity of the rules that define what will be seen in the GUI, depending on the interaction choices. The complexity thus mostly arises from the rules themselves, rather than from the implementation that is mostly straight-forward.
The layout
attribute of each widget contains information for the frontend to display correctly the widget, in the form of CSS specifiers. For us, a very important feature is the layout.display
attribute. If set to 'none'
, the widget is invisible. If set to 'flex'
, it is visible. Most callbacks only change the value of this display
attribute, to hide some parts of the GUI under some conditions.
Other layout and styling attributes are used, mainly for web-design purposes.
In the folder gui
, three files do not correspond to instanciations of BaseGUIElement
that are explained above:
common.py
contains tools used by different elements of the GUI.main_tab.py
only contains the callback fired when the user changes the uppermost tab between lattices and nets.parse_input.py
constructs the Search instance by parsing the user's input in the GUI.
Several files deal with the communication with LatNetBuilder; we list them below, and invite the reader to look at them in this order. The Python side and the C++ side interact via the command line interface, and log files. When the user clicks the Search button, a subprocess instance is launched which contains a call to the LatNetBuilder executable. Moreover, a thread instance is launched as well: it encapsulates a Python thread which monitors the output and updates the GUI accordingly. In particular, when the subprocess has finished running, the thread reads the output files and updates the Output GUI element. The reason we use a Python thread is to avoid blocking the Python kernel while waiting for the C++ process to end. This allows the user to do something else in the notebook in the meantime, for example aborting the C++ process by clicking the Abort button.
Files:
-
gui/button_box.py
contains the callbacks which are fired when the user clicks the Search or the Abort button -
in
search.py
theSearch
class contains several methods related to the interaction with the C++ software, namely_launch_subprocess
,_parse_subprocess
,execute
and_monitor_subprocess
. -
parse_output.py
contains classes and functions to parse the output files written by LatNetBuilder, and construct Python objects. -
gui/output.py
contains the output Base GUI Element, which displays plots and code snippets for the user to visualize and use the result of the C++ computations.
-
Increment the version number of the conda package in python-wrapper/conda_recipe/meta.yaml
-
For Mac OS and Linux, build and install LatNetBuilder following the instructions on the main README of the repository.
-
For Windows: from the folder containing the repository, execute the following command:
conda build python-wrapper\conda_recipe
- On all platforms, upload the conda packages to Anaconda Cloud (where the PATH_TO_PACKAGE can for instance be found in the console ouptut of the
./waf install
command or theconda build
command for Windows):
anaconda login
anaconda upload PATH_TO_PACKAGE
- update the Docker images by following the instructions in the README of the repository.