CEA Plugins - Part 4: How to add your own tools to the CEA

Now that you have an overview of the CEA plugin template, let’s dive into some of the files responsible for adding a new tool to the CEA.

This is part 4 in a series of articles on CEA plugins:

Adding tools to the CEA

Remember the files in the CEA plugin template? Here’s a quick reminder:

my-awesome-plugin
│   .gitignore
│   LICENSE
│   README.md
│   setup.py
│
\---cea_plugin_template
        demand_summary.py
        plots.yml
        plugin.config
        schemas.yml
        scripts.yml
        __init__.py

We’ve already covered some of these files in Part 3: Introduction to the CEA plugin template.

In this article we’ll cover the following files:

  • demand_summary.py,
  • scripts.yml,
  • schemas.yml,
  • and plugin.config

demand_summary.py

This file contains an important definition:

class DemandSummaryPlugin(cea.plugin.CeaPlugin):
    pass

Now… this definition class DemandSummaryPlugin(cea.plugin.CeaPlugin) does some “magic”, in that by inheriting from cea.plugin.CeaPlugin, most of the work of creating a CEA plugin is already done - you only need to edit some yaml files.

Theoretically, a CEA plugin could be any class that supports the same interface as cea.plugin.CeaPlugin and it’s possible to override how certain parts work. For example, you could replace how plots are defined in your plugin and avoid (or augment) the plots.yml feature to gain more flexibility. Let’s stick to the beaten path here and just keep in mind that if you do need more flexibility, it’s there.

This file also contains the demand-summary script exposed by the plugin. It follows some conventions used in the CEA project:

The main function that accepts a single argument of type cea.config.Configuration. This function needs to be called main to be picked up by the CEA. Normally, it’s job is to read the arguments from the config object and then call a function to compute the results - in this case summarize. Asside from the main function, how you actually organize your code is up to you.

Let’s look at the main function for demand-summary quickly:

def main(config):  
    locator = cea.inputlocator.InputLocator(config.scenario, config.plugins)
    summary_df = summarize(locator.get_total_demand.read(), config.demand_summary.fudge_factor)
    locator.demand_summary.write(summary_df)

The locator variable is created using the scenario and list of plugins installed. Refer to the section on schemas.yml for more information on InputLocator objects. Here we use it to read in the data from Total_deman.csv file generated by the demand script, create a summary and write it out to the demand-summary.csv file. How to find, read and write these data files is handled by the InputLocator class (with some help from cea.schemas.SchemaIo).

The config file contains some important information for the script:

  • config.scenario is used in most scripts to create an InputLocator

  • config.plugins is also used for this purpose - it contains a list of all plugin classes registered with the CEA - it should also include cea_plugin_template.DemandSummaryPlugin.

  • config.demand_summary.fudge_factor is a plugin-specific parameter added in plugin.config - see below.

Note that having the following code at the bottom of your file will help you debug it more easily from the code editor - you just need to run the current file to run your script with the current configuration:

# (it's not necessary to have this in your script - 
# it has just proven practical)
if __name__ == "__main__":
   main(cea.config.Configuration())

plugin.config

The plugin.config file describes sections and parameters within those sections for use in your plugin. See the Configuration File Details section in the CEA Documentation for more information on how Configuration files work. The plugin.config file uses the exact same format as the default.config file in the CEA.

Here’s an example from the CEA plugin template:

[demand-summary]
fudge-factor = 1.0
fudge-factor.type = RealParameter
fudge-factor.help = A factor to fudge (multiply) the results by

This defines a section (demand-summary) as well as a parameter (fudge-factor) to be used in scripts and plots. The fudge-factor.type defines the type of the parameter - see the subclasses of cea.config.Parameter for the list of valid parameter types. Note that plugins can’t define their own parameter types.

fudge-factor.help describes the parameter - this is shown in the command line interface (CLI) when you use the --help parameter as well as in the GUI for running tools using this parameter.

The plugin.config file is optional. If you don’t specify one, then the scripts in your plugin can only use parameters defined elsewhere - e.g. the CEA or other plugins.

scripts.yml

The scripts.yml file declares the list of tools defined in your plugin. This is how the CEA knows where to find the demand-summary script. In fact, the CEA core uses the exact same mechanism to define it’s own list of scripts - be sure to check out the original in the CEA repository.

Let’s look at the contents of this file:

Demand Summary:

  - name: demand-summary
    label: Demand Summary
    description: Creates a simple summary of the demand totals from the cea demand script.
    interfaces: [cli, dashboard]
    module: cea_plugin_template.demand_summary
    parameters: ["general:scenario", "demand-summary:fudge-factor"]
    input-files:
      - [get_total_demand]

The scripts.yml file is a mapping of category names to a sequence of tool dictionaries. You might want to look up a tutorial of YAML if the syntax seems confusing.

In this example, the category name “Demand Summary” contains a single tool. Each tool contains the following fields:

  • name: the tool’s name - as used on the command line interface (CLI). The convention is to use hyphens (-) to connect words. The name should only use lowercase letters and hyphens.

  • label: This is the name to display the tool as in the GUI in the Tools menu.

  • description: Use this field to give a short description of what your tool does and how to use it.

  • interfaces: A list of interfaces to show this tool in. For the purposes of CEA plugins, [cli, dashboard] should be used - you can omit dashboard to hide your script from the GUI.

  • module: this is the fully qualified name of your script - the module containing the main function to call. This module should be importable by the python provided by the CEA Console. Open up the CEA Console and type python -m cea_plugin_template.demand_summary. If you get an Error message like No module named cea_plugin_template.demand_summary then you haven’t installed the plugin yet.

  • parameters: This is a list of the parameters to be made available to your plugin - each parameter is written in the form section:parameter as it appears in the config file. Note you can add your own parameters - see plugin.config above.

  • input-files: This is an (optional) list of files that need to be present before the script can run. Each entry in the list is itself a list of the form [locator_method, arg1, arg2, ..., argn]. The arguments to the locator method are optional. For more information on locator methods, see the description of schemas.yml.

The scripts.yml file is optional. If you don’t provide one, then your plugin can only work on files described elsewhere - e.g. in the CEA or other plugins.

schemas.yml

The file schemas.yml defines the shape of input files and output files used by scripts (and plots) in the CEA as well as their location inside a scenario. The CEA core uses exact same mechanism for definining it’s own data files - each entry in the schemas.yml defines a locator method, a method of the cea.inputlocator.InputLocator class that is used throughout the CEA to locate data files.

These locator methods are used in places like the input-files key in scripts.yml as well as the location part of plots.yml. They’re also used for reading Dataframes from (and writing them to) disk.

When the CEA creates an InputLocator object, the information from your plugin is appended to the list of known locator methods.

Each entry in the schemas.yml file is the name of a locator method. Here’s the example from the CEA plugin template:

demand_summary:
  created_by:
  - demand-summary
  file_path: outputs/data/demand-summary/demand-summary.csv
  file_type: csv
  schema:
    columns:
      Name:
        description: Unique building ID. It must start with a letter.
        type: string
        unit: NA
        values: alphanumeric
      GFA_m2:
        description: Gross floor area
        type: float
        unit: '[m2]'
        min: 0.0
        values: '{0.0...n}'
      QC_sys_MWhyr:
        description: Total system cooling demand
        type: float
        min: 0.0
        unit: '[MWh/yr]'
        values: '{0.0...n}'
        plot-color: blue
      QH_sys_MWhyr:
        description: Total system heating demand
        type: float
        min: 0.0
        unit: '[MWh/yr]'
        values: '{0.0...n}'
        plot-color: red
  used_by: []

There’s quite a lot going on here, so let’s take it apart piece by piece:

  • demand_summary the first line defines the name of a locator method - the CEA adds this to the cea.inputlocator.InputLocator instances at runtime. This means you can do things like locator.demand_sumary() to get the path to the file outputs/data/demand-summary/demand-summary.csv located in the current scenario.

  • created_by: A list of script names (entries in scripts.yml - either the one defined in your plugin or the one defined by CEA) that produce this file.

  • file_path: The path to the file, relative to the scenario path. This path may include variable references in the form {variable_name}. When calling a locator method, you can pass in additional keywords, like this: locator.demand_summary(variable_name="abc") and the result will have the replacement. Note that with read() and write() you’ll also need to pass in these variables.

  • file_type: The type of file this locator method points to. Currently, for plugins, we suggest using csv files. The class cea.schemas.CsvSchemaIo has some nice features - like reading and writing DataFrames as well as validation of the schema on reading / writing. This functionality will be extended for the other file types known to the CEA at a later stage.

  • schema: For “flat” file types like csv, dbf and shp files, the contents of the schema dictionary is just columns, which itself is a dictionary of columns contained inside the data file.

A column definition contains the following keys:

  • description: A description of the contents of the column. This is also used for the legend in columns that are plotted.

  • type: The type of the data in the column. The CEA knows about string, int , float, date and boolean column types. Files with file_type: shp (shapefiles) also can have Point, Polygon and LineString.

  • unit: Describes the unit of a column - especially for columns of type float, this is usually the physical unit of measurment. By convention, we place the unit inside square brackets - see the example for QH_sys_MWhyr: unit: "[[MWh/yr]]". If a unit is not available / does not apply, use NA instead.

  • values: This is a short description for the user of the type of values to be found in here and ignored by the CEA.

  • min / max: Columns with types int or float can optionally specify minimum and maximum values for the range of data in the column. E.g. set min to 0.0 to specify that only positive values are to be found in the column.

  • plot-color : Optional. If a column is used in a plot, this color will be used to plot that series. This is either a color in the format "rgb(255, 255, 255)" or a color taken from the list in cea/plots/colors.py like “red”, “blue” etc.

Summary

We’ve covered all the files necessary to add a new tool to the CEA using the CEA plugin template - in Part 5: How to add your own plots to the CEA we’ll add the final touch.

Written on May 25, 2020