The plugin script
In this tutorial we will make a plugin to create a statistical chart called "violin plot" from a set of values extracted using an expression from the results of a simulation. This allows to understand how:
-
to develop a Python program able to interact with SAInt;
-
to create a wizard form (i.e., a graphical user interface) to let the user interact with SAInt and your new program;
-
to transfer data from a SAInt session to an external program;
-
to showcase the processing of the dataset;
-
to create a specialized chart to summarize the statistical properties of the dataset.
-
package everything for distribution or exchange with other SAInt users.
1. Install required packages
We need to install in our virtual environment a set of packages that we will use in the plugin. For the sake of the tutorial, we will specify the version of the package to be installed. So, we prepare a "requirements.txt" file for pip and we save it in the project folder. The content of the "requirements.txt" is:
pandas==2.3.3
plotly==6.3.1
We will also require other packages, but they are part of the standard Python libraries, so we do not need to install them or use pip. The remaining packages are:
-
ctypes: The module provides C compatible data types, and allows calling functions in DLLs or shared libraries. It can be used to wrap these libraries in pure Python. -
pathlib: The module offers classes representing filesystem paths with semantics appropriate for different operating systems. -
argparse: The module implements command-line interfaces.
To install the required packages in the virtual environment, we need the following command:
python -m pip install -r requirements.txt
2. Create the "main.py" script
In the project folder, create an empty text file named "main.py". This file will hold our Python code.
With your preferred Python IDE, open the file "main.py" and start writing your script.
We start by declaring which packages to import with:
# Required packages for the plugin
from ctypes import *
from pathlib import Path
import argparse
import pandas as pd
import plotly.express as px
Then, we follow with:
# Define path to SAInt ddl
SAINT_DLL_PATH = "C:\\Program Files\\encoord\\SAInt-v3\\SAInt-API.dll"
Here we define the position of the SAInt-API DLL. please, note that the path could be different on your machine, and the one used in the script matches the default location of a standard installation.
|
When dealing with Windows path and string literals in Python there are multiple options. We use here the option with double |
And we close the basic structure of the script with:
# Body of the script, the "main" function
def main():
# Call to the "main" function
if __name__ == "__main__":
main()
We can now work on the main body of the script where we will perform the following tasks:
- Task 1
-
We need to parse the arguments passed to the plugin executable into valid parameters for teh script.
- Task 2
-
We need to load the SAInt-API dll and define some basic ctypes for the correct handling of teh conversion between numerical representations of the types.
- Task 3
-
We need to load the network and the scenario selected by the user by paying attention to the type of system. The SAInt-API has different methods for different types of networks and the associated scenarios.
- Task 4
-
Once the scenario is loaded, we can evaluate the expression specified by the user to select the objects and properties to plot in our chart. The data needs to be correctly formatted to be used by the function
violinplotof the package "Plotly". - Task 5
-
We can prepare the violinplot chart with the extracted data and with the user-specified title and string for the y-axis.
- Task 6
-
Finally, we need to define where to save the chart. And we want to have small output files, so we will specify to use the only version of the plotting JavaScript library (cfr. the "cdn" option from the the page "to_html" of Plotly). An Internet connection is needed to properly visualize the results.
The tasks are referred in the following code snippet:
# Body of the script, the "main" function
def main():
# Task 1: Parse CLI arguments
parser = argparse.ArgumentParser()
parser.add_argument('--network', required=True)
parser.add_argument('--scenario', required=True)
parser.add_argument('--expression', required=True)
parser.add_argument('--title', required=True)
parser.add_argument('--axis', required=True)
parser.add_argument('--output-folder', required=True)
args = parser.parse_args()
# Task 2: Connect to SAInt API
saint_dll = cdll.LoadLibrary(SAINT_DLL_PATH)
saint_dll.evalStr.restype = c_wchar_p
saint_dll.evalCmdStr.restype = c_wchar_p
# Task 3: Extract the type of network
Type_Network = args.network[-4:]
if Type_Network == 'enet':
saint_dll.openENET(args.network)
saint_dll.openESCE(args.scenario)
elif Type_Network == 'gnet':
saint_dll.openGNET(args.network)
saint_dll.openGSCE(args.scenario)
elif Type_Network == 'tnet':
saint_dll.openTNET(args.network)
saint_dll.openTSCE(args.scenario)
else:
print("The network specified is not recognized!")
# Task 4: Convert from string to one-column dataframe of floats
DATA = saint_dll.evalStr(args.expression)
DATA = DATA.strip("[]")
DATA = DATA.split(", ")
df = pd.DataFrame(DATA, columns=['Values'])
df = df.astype(float)
# Task 5: Define the violinplot chart
Title="<b>"+ args.title +"</b>"
AxisLabel = args.axis
ChartTemplate = "ggplot2"
fig = px.violin(df, y="Values", box=True, points='all',
labels = {
"Values": AxisLabel,
},
width=1000, height=750)
# Task 6: Define the output location for the chart
file_path = Path(args.output_folder) / "ViolinPlot.html"
fig.write_html(file_path, include_plotlyjs='cdn')
The complete version of the plugin script is available here (Click to open).
# Define path to SAInt ddl
from ctypes import *
from pathlib import Path
import argparse
import pandas as pd
import plotly.express as px
# Define path to SAInt ddl
SAINT_DLL_PATH = "C:\\Program Files\\encoord\\SAInt-v3\\SAInt-API.dll"
# Body of the script, the "main" function
def main():
# Task 1: Parse CLI arguments
parser = argparse.ArgumentParser()
parser.add_argument('--network', required=True)
parser.add_argument('--scenario', required=True)
parser.add_argument('--expression', required=True)
parser.add_argument('--title', required=True)
parser.add_argument('--axis', required=True)
parser.add_argument('--output-folder', required=True)
args = parser.parse_args()
# Task 2: Connect to SAInt API
saint_dll = cdll.LoadLibrary(SAINT_DLL_PATH)
saint_dll.evalStr.restype = c_wchar_p
saint_dll.evalCmdStr.restype = c_wchar_p
# Task 3: Extract the type of network
Type_Network = args.network[-4:]
if Type_Network == 'enet':
saint_dll.openENET(args.network)
saint_dll.openESCE(args.scenario)
elif Type_Network == 'gnet':
saint_dll.openGNET(args.network)
saint_dll.openGSCE(args.scenario)
elif Type_Network == 'tnet':
saint_dll.openTNET(args.network)
saint_dll.openTSCE(args.scenario)
else:
print("The network specified is not recognized!")
# Task 4: Convert from string to one-column dataframe of floats
DATA = saint_dll.evalStr(args.expression)
DATA = DATA.strip("[]")
DATA = DATA.split(", ")
df = pd.DataFrame(DATA, columns=['Values'])
df = df.astype(float)
# Task 5: Define the violinplot chart
Title="<b>"+ args.title +"</b>"
AxisLabel = args.axis
ChartTemplate = "ggplot2"
fig = px.violin(df, y="Values", box=True, points='all',
labels = {
"Values": AxisLabel,
},
width=1000, height=750)
# Task 6: Define the output location for the chart
file_path = Path(args.output_folder) / "ViolinPlot.html"
fig.write_html(file_path, include_plotlyjs='cdn')
# Call to the "main" function
if __name__ == "__main__":
main()
Let’s test if our code works as expected. First, we need to make sure to have a valid solution for the scenario we will take data from. Please, launch SAInt, load the network ENET39 from the "data" folder, and load and execute the scenario "ACPF_PEAK_DEMAND_WEEK.esce". Once the scenario is solved, let’s run our script in the virtual environment with the command:
python main.py --network ".\\data\\ENET39.enet" --scenario ".\\data\\ACPF_PEAK_DEMAND_WEEK.esce" --expression "ENO.%.VPU.(5)" --title "Test Title" --axis "VPU (p.u.)" --output-folder ".\\results\\"
We can check the output in the "results" folder, where we have the file "ViolinPlot.html".
3. Plugin packaging
Once we are happy with the functionalities and output of our script, we can use "PyInstaller" to create an executable to be distributed as a plugin.
The following command packages our script and all dependencies in one executable:
pyinstaller --name=main --onefile main.py
We should now be able to find the program "main.exe" in the "dist" folder. And we should be able to run the "main.exe" executable with:
./dist/main.exe --network ".\\data\\ENET39.enet" --scenario ".\\data\\ACPF_PEAK_DEMAND_WEEK.esce" --expression "ENO.%.VPU.(5)" --title "Test Title" --axis "VPU (p.u.)" --output-folder ".\\results\\"
As before, we can see the output file in the "results" folder.
|
On computers with an antivirus software checking for executables, it may be necessary to wait for the security software to validate the executable of your plugin before using it. |