Introduction to eplusr

Hongyuan Jia

2020-02-20

eplusr provides a rich toolkit of using whole building energy simulation program EnergyPlus directly in R, which enables programmatic navigation, modification of EnergyPlus models, conducts parametric simulations and retrieves outputs. More information about EnergyPlus can be found at its website.

Installation

You can install the latest stable release of eplusr from CRAN.

install.packages("eplusr")

Alternatively, you can install the development version from GitHub.

# install.packages("remotes")
remotes::install_github("hongyuanjia/eplusr")

Since running the IDF files requires EnergyPlus (https://energyplus.net), EnergyPlus has to be installed before running any models. There are helper functions in eplusr to download and install it automatically on major operating systems (Windows, macOS and Linux):

# install the latest version (currently v9.1.0)
eplusr::install_eplus("latest")

# OR download the latest version (currently v9.1.0) and run the installer
# manually by yourself
eplusr::download_eplus("latest", dir = tempdir())

Note that the installation process in install_eplus() requires administrative privileges. If you are not in interactive mode, you have to run R with administrator (or with sudo if you are on macOS or Linux) to make it work.

Features

Class structure

Below shows the class structure in eplusr.

Basically, eplusr uses Idf class to present the whole IDF file and IdfObject class to present a single object in an IDF. Both Idf and IdfObject class contain member functions for helping modify the data in IDF so it complies with the underlying EnergyPlus IDD (Input Data Dictionary). Similarly, IDD file is wrapped into two classes, i.e. Idd and IddObject.

Besides, Epw class is used to present EnergyPlus Weather files; EplusJob to run single EnergyPlus simulation and collect outputs, ParametricJob to run parametric EnergyPlus simulations and collect all outputs.

It is highly recommended to read the documentation to get a thorough understanding on each class.

Read and parse

All IDF reading process starts with function read_idf(), which returns an Idf object. The model will be printed in a similar style you see in IDFEditor, with additional heading lines showing the Path, Version of the model. The classes of objects in the model are ordered by groups and the number of objects in classes are shown in square bracket.

Parsing an IDF requires the IDD data of that version, which serves as the schema. Usually, when you try to edit an IDF, the corresponding EnergyPlus is likely to be installed already. If EnergyPlus is installed in standard location (C:\EnergyPlusVX-Y-0 on Windows, /usr/local/EnergyPlus-X-Y-0 on Linux and /Applications/EnergyPlus-X-Y-0 on macOS), eplusr is able to find it and use the Energy+.idd file distributed with that release to parse the input IDF. The IDD file will be parsed first and an Idd object will be created and cached. That Idd object will be reused whenever parsing IDFs with that version. For more details, please see ?use_idd() and ?idd.

Sometimes you may just want to edit the model without installing the whole EnergyPlus software. You can just download the IDD file of that version using download_idd() or set download to TRUE in use_idd(). The code below will download IDD file for EnergyPlus v8.8.0, parse it and create an Idd object that will be used whenever parsing all EnergyPlus models of version v8.8.

path_idd <- download_idd(8.8, dir = tempdir())
use_idd(path_idd)

# OR
use_idd(8.8, download = TRUE)

Now let’s read an IDF file distributed with EnergyPlus 8.8.0. As we have already got the IDD, we can just ignore the idd argument.

model <- read_idf(path = "5Zone_Transformer.idf", idd = NULL)
#> IDD v8.8.0 has not been parsed before.
#> Try to locate `Energy+.idd` in EnergyPlus v8.8.0 installation folder '/usr/local/EnergyPlus-8-8-0'.
#> IDD file found: '/home/hongyuanjia/.local/EnergyPlus-8-8-0/Energy+.idd'.
#> Start parsing...
#> Parsing completed.

model
#> ── EnergPlus Input Data File ───────────────────────────────────────────────────
#>  * Path: '/tmp/RtmpInuXyg/5Zone_Transformer.idf'
#>  * Version: '8.8.0'
#> 
#> Group: <Simulation Parameters>
#> ├─ [01<O>] Class: <Version>
#> │─ [01<O>] Class: <SimulationControl>
#> │─ [01<O>] Class: <Building>
#> │─ [01<O>] Class: <SurfaceConvectionAlgorithm:Inside>
#> │─ [01<O>] Class: <SurfaceConvectionAlgorithm:Outside>
....

Idf class provides lots of methods to programmatically query and modify EnergyPlus models. See table below. This vignette will demonstrate some of them.

Methods of Idf class
Category Method Functionality
Basic Info $version() Get Idf version
$path() Get Idf file path
$group_name() Get group names
$class_name() Get class names
$is_valid_group() Check group existence
$is_valid_class() Check class existence
Definition $definition() Get corresponding IddObject
Object Info $object_id() Get object unique ID
$object_name() Get object name
$object_num() Get object number in class
$is_valid_id() Check object ID existence
$is_valid_name() Check object name existence
Object Relation $object_relation() Get object relation with others
Object Query $object() Get single object
$objects() Get multiple objects
$object_unique() Get the unique object
$objects_in_class() Get objects in class
$objects_in_group() Get objects in group
$objects_in_relation() Get objects in relation
$search_object() Get objects using regular expression
Object Modification $dup() Duplicate objects
$add() Add new objects
$set() Modify existing objects
$del() Delete existing objects
$rename() Change object names
$insert() Add new objects from other IdfObjects
$load() Add new objects from strings and data.frames
$update() Update object values from strings and data.frames
$paste() Add new objects from IDF Editor Copy obj
$search_value() Get objects whose values match regular expression
$replace_value() Modify object values using regular expression
Validation $validate() Check any errors in Idf
$is_valid() Check if no error exists in Idf
Data Extraction $to_table() Extract Idf data in data.frames
$to_string() Extract Idf data in strings
Save $is_unsaved() Check if unsaved changes exist
$save() Save Idf to an .idf file
Clone $clone() Create an copy
Run $run() Run Idf together with an Epw
Print $print() Print Idf in different details

Below will show same example usage of methods listed above.

Basic Info

If you want to see what groups and classes exist in your model, use $group_name() and $class_name() respectively.

model$group_name()
#>  [1] "Simulation Parameters"                        
#>  [2] "Location and Climate"                         
#>  [3] "Schedules"                                    
#>  [4] "Surface Construction Elements"                
#>  [5] "Thermal Zones and Surfaces"                   
....
model$class_name()
#>  [1] "Version"                                   
#>  [2] "SimulationControl"                         
#>  [3] "Building"                                  
#>  [4] "SurfaceConvectionAlgorithm:Inside"         
#>  [5] "SurfaceConvectionAlgorithm:Outside"        
....

You can use $is_valid_group() and $is_valid_class() to check if curtain groups or names exist in current model.

model$is_valid_group("Schedules")
#> [1] TRUE
model$is_valid_class("ZoneInfiltration:DesignFlowRate")
#> [1] TRUE

Class definition

You can get class definition using $definition(), which returns an IddObject. All required fields in each class are marked with *. For example, you can get the IddObject of class Material:

def_mat <- model$definition("Material")
def_mat
#> <IddObject: 'Material'>
#> ── MEMO ────────────────────────────────────────────────────────────────────────
#>   "Regular materials described with full set of thermal properties"
#> 
#> ── PROPERTIES ──────────────────────────────────────────────────────────────────
#>   * Group: 'Surface Construction Elements'
#>   * Unique: FALSE
#>   * Required: FALSE
#>   * Total fields: 9
#> 
#> ── FIELDS ──────────────────────────────────────────────────────────────────────
#>   1*: Name
#>   2*: Roughness
#>   3*: Thickness
#>   4*: Conductivity
#>   5*: Density
#>   6*: Specific Heat
#>   7 : Thermal Absorptance
#>   8 : Solar Absorptance
#>   9 : Visible Absorptance

You can also achieve this using methods in Idd class.

idd <- use_idd(8.8)
idd$Material

# OR
# idd$object("Material")

# OR
# idd_object(8.8, "Material")

With the IddObject, you can easily get class and field properties using methods it has.

For example, you can get all default field values using $field_default(). As we did not give any field index or name, a list is returned containing default values of all fields. The type of each value will be consistent with field definition.

NOTE: For numeric fields with default values being "autosize" or "autocalculate", the type of returned values will be “character”.

def_val <- def_mat$field_default()
str(def_val)
#> List of 9
#>  $ Name               : chr NA
#>  $ Roughness          : chr NA
#>  $ Thickness          : num NA
#>  $ Conductivity       : num NA
#>  $ Density            : num NA
#>  $ Specific Heat      : num NA
#>  $ Thermal Absorptance: num 0.9
#>  $ Solar Absorptance  : num 0.7
#>  $ Visible Absorptance: num 0.7

Please see ?IddObject for detailed documentation on IddObject class.

Get object

In an Idf, each object in the model is assigned with an unique ID according to its appearance sequence in the IDF file. You can find all valid IDs using $object_id().

model$object_id(class = c("Material", "Construction"), simplify = FALSE)
#> $Material
#>  [1] 43 44 45 46 47 48 49 50 51 52
#> 
#> $Construction
#> [1] 66 67 68 69 70 71 72

You can get all object names using $object_name(). If the class does not have name attribute, NA will returned.

model$object_name(class = c("Version", "Material", "Construction"), simplify = FALSE)
#> $Version
#> [1] NA
#> 
#> $Material
#>  [1] "WD10" "RG01" "BR01" "IN46" "WD01" "PW03" "IN02" "GP01" "GP02" "CC03"
#> 
#> $Construction
#> [1] "ROOF-1"               "WALL-1"               "CLNG-1"              
#> [4] "FLOOR-SLAB-1"         "INT-WALL-1"           "Dbl Clr 3mm/13mm Air"
#> [7] "Sgl Grey 3mm"

Object number in each class can be retrieved using $object_num().

model$object_num(c("BuildingSurface:Detailed", "Material", "Output:Variable"))
#> [1] 40 10 13

Having the object ID or name, you can easily get any object using $object() which returns an IdfObject or using $objects() which returns a list of IdfObjects.

NOTE: The matching of object names is case-insensitive. For instance, model$object("rOoF") is equivalent to model$object("roof").

model$objects(c("WD10", "ROOF-1"))
#> $WD10
#> <IdfObject: 'Material'> [ID:43] `WD10`
#> Class: <Material>
#> ├─ 1*: "WD10",         !- Name
#> │─ 2*: "MediumSmooth", !- Roughness
#> │─ 3*: 0.667,          !- Thickness {m}
#> │─ 4*: 0.115,          !- Conductivity {W/m-K}
#> │─ 5*: 513,            !- Density {kg/m3}
#> │─ 6*: 1381,           !- Specific Heat {J/kg-K}
#> │─ 7 : 0.9,            !- Thermal Absorptance
#> │─ 8 : 0.78,           !- Solar Absorptance
#> └─ 9 : 0.78;           !- Visible Absorptance
#> 
#> $`ROOF-1`
#> <IdfObject: 'Construction'> [ID:66] `ROOF-1`
#> Class: <Construction>
#> ├─ 1*: "ROOF-1",   !- Name
#> │─ 2*: "RG01",     !- Outside Layer
#> │─ 3 : "BR01",     !- Layer 2
#> │─ 4 : "IN46",     !- Layer 3
#> └─ 5 : "WD01";     !- Layer 4

If you want to get all objects in a single class, use $objects_in_class().

model$objects_in_class("Material")
#> $WD10
#> <IdfObject: 'Material'> [ID:43] `WD10`
#> Class: <Material>
#> ├─ 1*: "WD10",         !- Name
#> │─ 2*: "MediumSmooth", !- Roughness
#> │─ 3*: 0.667,          !- Thickness {m}
#> │─ 4*: 0.115,          !- Conductivity {W/m-K}
#> │─ 5*: 513,            !- Density {kg/m3}
#> │─ 6*: 1381,           !- Specific Heat {J/kg-K}
#> │─ 7 : 0.9,            !- Thermal Absorptance
#> │─ 8 : 0.78,           !- Solar Absorptance
#> └─ 9 : 0.78;           !- Visible Absorptance
#> 
#> $RG01
#> <IdfObject: 'Material'> [ID:44] `RG01`
#> Class: <Material>
#> ├─ 1*: "RG01",     !- Name
#> │─ 2*: "Rough",    !- Roughness
#> │─ 3*: 0.0127,     !- Thickness {m}
#> │─ 4*: 1.442,      !- Conductivity {W/m-K}
#> │─ 5*: 881,        !- Density {kg/m3}
#> │─ 6*: 1674,       !- Specific Heat {J/kg-K}
#> │─ 7 : 0.9,        !- Thermal Absorptance
#> │─ 8 : 0.65,       !- Solar Absorptance
#> └─ 9 : 0.65;       !- Visible Absorptance
#> 
#> $BR01
#> <IdfObject: 'Material'> [ID:45] `BR01`
#> Class: <Material>
#> ├─ 1*: "BR01",         !- Name
....

Also, you can get all objects in a single class using "$" or "[[". Class names can be given in underscore-style. For example, you can just use model$Material_NoMass instead of model$`Material:Nomass` to save some typing.

model$Material_NoMass
#> $CP01
#> <IdfObject: 'Material:NoMass'> [ID:53] `CP01`
#> Class: <Material:NoMass>
#> ├─ 1*: "CP01",     !- Name
#> │─ 2*: "Rough",    !- Roughness
#> │─ 3*: 0.367,      !- Thermal Resistance {m2-K/W}
#> │─ 4 : 0.9,        !- Thermal Absorptance
#> │─ 5 : 0.75,       !- Solar Absorptance
#> └─ 6 : 0.75;       !- Visible Absorptance
#> 
#> $`MAT-SB-U`
#> <IdfObject: 'Material:NoMass'> [ID:54] `MAT-SB-U`
#> Class: <Material:NoMass>
#> ├─ 1*: "MAT-SB-U",    !- Name
#> │─ 2*: "Rough",       !- Roughness
#> │─ 3*: 0.117406666,   !- Thermal Resistance {m2-K/W}
#> │─ 4 : 0.65,          !- Thermal Absorptance
#> │─ 5 : 0.65,          !- Solar Absorptance
#> └─ 6 : 0.65;          !- Visible Absorptance
#> 
#> $`MAT-CLNG-1`
#> <IdfObject: 'Material:NoMass'> [ID:55] `MAT-CLNG-1`
#> Class: <Material:NoMass>
#> ├─ 1*: "MAT-CLNG-1", !- Name
#> │─ 2*: "Rough",      !- Roughness
#> │─ 3*: 0.65225929,   !- Thermal Resistance {m2-K/W}
#> │─ 4 : 0.65,         !- Thermal Absorptance
#> │─ 5 : 0.65,         !- Solar Absorptance
#> └─ 6 : 0.65;         !- Visible Absorptance
#> 
....
# OR
# model[["Material_NoMass"]]

Based on the above, if you want to get the first object in class RunPeriod, you can simply run:

rp <- model$RunPeriod[[1]]

For unique object, such like SimulationControl and Building, you can use $object_unique()

model$object_unique("Building")
#> <IdfObject: 'Building'> [ID:3] `Building`
#> Class: <Building>
#> ├─ 1: "Building",     !- Name
#> │─ 2: 30,             !- North Axis {deg}
#> │─ 3: "City",         !- Terrain
#> │─ 4: 0.04,           !- Loads Convergence Tolerance Value
#> │─ 5: 0.4,            !- Temperature Convergence Tolerance Value {deltaC}
#> │─ 6: "FullExterior", !- Solar Distribution
#> │─ 7: 25,             !- Maximum Number of Warmup Days
#> └─ 8: 6;              !- Minimum Number of Warmup Days

# OR just
# model$Building

Many fields in a model can be referred by others. For example, the Outside Layer and other fields in Construction class refer to the Name field in Material class and other material related classes. Here it means that the Outside Layer field refers to the Name field and the Name field is referred by the Outside Layer. $object_relation() provides a simple interface to get this kind of relation. It takes a single object ID or name and also a relation direction, and returns an IdfRelation object which contains data presenting such relation above.

model$object_name("Material:NoMass")
#> $`Material:NoMass`
#> [1] "CP01"        "MAT-SB-U"    "MAT-CLNG-1"  "MAT-FLOOR-1"
model$object_relation("mat-clng-1")
#> ── Refer to Others ─────────────────────────────────────────────────────────────
#> Target(s) does not refer to any other field.
#> 
#> ── Referred by Others ──────────────────────────────────────────────────────────
#>   Class: <Material:NoMass>
#>   └─ Object [ID:55] <MAT-CLNG-1>
#>      └─ 1: "MAT-CLNG-1";  !- Name
#>         ^~~~~~~~~~~~~~~~~~~~~~~~~
#>         └─ Class: <Construction>
#>            └─ Object [ID:68] <CLNG-1>
#>               └─ 2: "MAT-CLNG-1";  !- Outside Layer
#> 
#> ── Node Relation ───────────────────────────────────────────────────────────────
#> Target(s) has no node or their nodes have no reference to other object.

Above shows that no-mass material MAT-CLNG-1 is used at the outside layer of a construction named CLNG-1. You can extract both of them using $objects_in_relation().

mat_const <- model$objects_in_relation("mat-clng-1", "ref_by")
mat_const
#> $`MAT-CLNG-1`
#> <IdfObject: 'Material:NoMass'> [ID:55] `MAT-CLNG-1`
#> Class: <Material:NoMass>
#> ├─ 1*: "MAT-CLNG-1", !- Name
#> │─ 2*: "Rough",      !- Roughness
#> │─ 3*: 0.65225929,   !- Thermal Resistance {m2-K/W}
#> │─ 4 : 0.65,         !- Thermal Absorptance
#> │─ 5 : 0.65,         !- Solar Absorptance
#> └─ 6 : 0.65;         !- Visible Absorptance
#> 
#> $`CLNG-1`
#> <IdfObject: 'Construction'> [ID:68] `CLNG-1`
#> Class: <Construction>
#> ├─ 1*: "CLNG-1",     !- Name
#> └─ 2*: "MAT-CLNG-1"; !- Outside Layer

After you get the objects, you can perform detailed modifications on them using methods $set() in both Idf and IdfObject class.

Similarly, you can use "$" and "[[" to get a single value in an IdfObject class or "[" to get multiple values just like normal lists in R.

rp$Begin_Day_of_Month
#> [1] 14

# OR
# rp[["Begin_Day_of_Month"]]
# rp[[3]]

You can also make a chain.

model$RunPeriod$WinterDay$Begin_Day_of_Month
#> [1] 14

Modify object

There are two ways to modify objects in eplusr. One is using methods in Idf which works on multiple objects, and the other way is using methods in IdfObject which only works for a single object.

NOTE: Validations are performed during object modifications under different strictness level (none, draft, final or custom yours using custom_validate()). For detailed explanations, please see ?level_checks.

Idf class provides 9 methods for modifying objects, including $dup(), $add(), $set(), $del(), $rename(), $insert(), $load() $update() and $paste().

Object IDs will be appended after new objects are added, and the most-newly added object will always have the max ID. Object IDs will never be reused, even though their binded objects have been deleted using $del().

For modifying object’s comments and values, you can also use $comment() and $set() in IdfObject class.

Duplicate objects

$dup() duplicates objects specified by object IDs or names. If the target classes have a name attribute, you can assign new names to the duplicated objects in form new_name = "old_name". If new name is not given, the newly added object will have the same name as the original object except a appended suffix of “_1”, “_2” and etc.

model$dup(c(my_roof = "ROOF-1", "ROOF-1", "WALL-1"))
#> New names of duplicated objects not given are automatically generated:
#>  #2| Object ID [325] --> New object name 'ROOF-1_1'
#>  #3| Object ID [326] --> New object name 'WALL-1_1'
#> $my_roof
#> <IdfObject: 'Construction'> [ID:324] `my_roof`
#> Class: <Construction>
#> ├─ 1*: "my_roof",  !- Name
#> │─ 2*: "RG01",     !- Outside Layer
#> │─ 3 : "BR01",     !- Layer 2
#> │─ 4 : "IN46",     !- Layer 3
#> └─ 5 : "WD01";     !- Layer 4
#> 
#> $`ROOF-1_1`
#> <IdfObject: 'Construction'> [ID:325] `ROOF-1_1`
#> Class: <Construction>
#> ├─ 1*: "ROOF-1_1", !- Name
#> │─ 2*: "RG01",     !- Outside Layer
#> │─ 3 : "BR01",     !- Layer 2
#> │─ 4 : "IN46",     !- Layer 3
#> └─ 5 : "WD01";     !- Layer 4
#> 
#> $`WALL-1_1`
#> <IdfObject: 'Construction'> [ID:326] `WALL-1_1`
#> Class: <Construction>
#> ├─ 1*: "WALL-1_1", !- Name
#> │─ 2*: "WD01",     !- Outside Layer
#> │─ 3 : "PW03",     !- Layer 2
#> │─ 4 : "IN02",     !- Layer 3
#> └─ 5 : "GP01";     !- Layer 4

Add new objects

You can add new objects using $add(). With .default being TRUE, the default behavior, all empty fields are filled with default values, if possible. Only minimum fields will be added by default. But you can change it by setting .all to TRUE.

You can also add new comments alongside with new values using the special element .comment.

For example, here we add two new objects with comments in RunPeriod class:

rp1 <- list(RunPeriod = list("rp_test_1", 1, 1, 2, 1, .comment = c("Comment for new object 1", "Another comment")))

model$add(rp1,
  RunPeriod = list(name = "rp_test_2", begin_month = 3, begin_day_of_month = 1,
    end_month = 4, end_day_of_month = 1, .comment = "Comment for new object 2"
  )
)
#> $rp_test_1
#> <IdfObject: 'RunPeriod'> [ID:327] `rp_test_1`
#> ── COMMENTS ────────────────────────────────────────────────────────────────────
#> !Comment for new object 1
#> !Another comment
#> ── VALUES ──────────────────────────────────────────────────────────────────────
#> Class: <RunPeriod>
#> ├─ 01 : "rp_test_1",      !- Name
#> │─ 02*: 1,                !- Begin Month
#> │─ 03*: 1,                !- Begin Day of Month
#> │─ 04*: 2,                !- End Month
#> │─ 05*: 1,                !- End Day of Month
#> │─ 06 : "UseWeatherFile", !- Day of Week for Start Day
#> │─ 07 : "Yes",            !- Use Weather File Holidays and Special Days
#> │─ 08 : "Yes",            !- Use Weather File Daylight Saving Period
#> │─ 09 : "No",             !- Apply Weekend Holiday Rule
#> │─ 10 : "Yes",            !- Use Weather File Rain Indicators
#> └─ 11 : "Yes";            !- Use Weather File Snow Indicators
#> 
#> $rp_test_2
#> <IdfObject: 'RunPeriod'> [ID:328] `rp_test_2`
#> ── COMMENTS ────────────────────────────────────────────────────────────────────
#> !Comment for new object 2
#> ── VALUES ──────────────────────────────────────────────────────────────────────
#> Class: <RunPeriod>
#> ├─ 01 : "rp_test_2",      !- Name
#> │─ 02*: 3,                !- Begin Month
#> │─ 03*: 1,                !- Begin Day of Month
#> │─ 04*: 4,                !- End Month
#> │─ 05*: 1,                !- End Day of Month
#> │─ 06 : "UseWeatherFile", !- Day of Week for Start Day
#> │─ 07 : "Yes",            !- Use Weather File Holidays and Special Days
#> │─ 08 : "Yes",            !- Use Weather File Daylight Saving Period
#> │─ 09 : "No",             !- Apply Weekend Holiday Rule
#> │─ 10 : "Yes",            !- Use Weather File Rain Indicators
#> └─ 11 : "Yes";            !- Use Weather File Snow Indicators

Set new values and comments

Changing values of existing objects can be conducted using $set() in Idf and IdfObject:

model$set(
  rp_test_1 = list(name = "rp_test_3", begin_day_of_month = 2,
    .comment = c(format(Sys.Date()), "begin day has been changed.")
  )
)
#> $rp_test_3
#> <IdfObject: 'RunPeriod'> [ID:327] `rp_test_3`
#> ── COMMENTS ────────────────────────────────────────────────────────────────────
#> !2020-02-20
#> !begin day has been changed.
#> ── VALUES ──────────────────────────────────────────────────────────────────────
#> Class: <RunPeriod>
#> ├─ 01 : "rp_test_3",      !- Name
#> │─ 02*: 1,                !- Begin Month
#> │─ 03*: 2,                !- Begin Day of Month
#> │─ 04*: 2,                !- End Month
#> │─ 05*: 1,                !- End Day of Month
#> │─ 06 : "UseWeatherFile", !- Day of Week for Start Day
#> │─ 07 : "Yes",            !- Use Weather File Holidays and Special Days
#> │─ 08 : "Yes",            !- Use Weather File Daylight Saving Period
#> │─ 09 : "No",             !- Apply Weekend Holiday Rule
#> │─ 10 : "Yes",            !- Use Weather File Rain Indicators
#> └─ 11 : "Yes";            !- Use Weather File Snow Indicators

For setting a single value, you can even write in a chain:

(model$RunPeriod$rp_test_2$End_Day_of_Month <- 2)
#> [1] 2

Also, if the modified fields are referenced by fields in other objects, the corresponding fields will also be updated.

mat <- model$Material$CC03

mat$value_relation("Name")
#> ── Refer to Others ─────────────────────────────────────────────────────────────
#>   └─ 1: "CC03";        !- Name
#> 
#> ── Referred by Others ──────────────────────────────────────────────────────────
#>   └─ 1: "CC03";        !- Name
#>      ^~~~~~~~~~~~~~~~~~~~~~~~~
#>      └─ Class: <Construction>
#>         └─ Object [ID:69] <FLOOR-SLAB-1>
#>            └─ 2: "CC03";        !- Outside Layer
#> 
#> ── Node Relation ───────────────────────────────────────────────────────────────
#>   └─ 1: "CC03";        !- Name

mat$set(name = "CC03_renamed")
#> <IdfObject: 'Material'> [ID:52] `CC03_renamed`
#> Class: <Material>
#> ├─ 1*: "CC03_renamed", !- Name
#> │─ 2*: "MediumRough",  !- Roughness
#> │─ 3*: 0.1016,         !- Thickness {m}
#> │─ 4*: 1.31,           !- Conductivity {W/m-K}
#> │─ 5*: 2243,           !- Density {kg/m3}
#> │─ 6*: 837,            !- Specific Heat {J/kg-K}
#> │─ 7 : 0.9,            !- Thermal Absorptance
#> │─ 8 : 0.65,           !- Solar Absorptance
#> └─ 9 : 0.65;           !- Visible Absorptance

mat$value_relation("Name")
#> ── Refer to Others ─────────────────────────────────────────────────────────────
#>   └─ 1: "CC03_renamed";!- Name
#> 
#> ── Referred by Others ──────────────────────────────────────────────────────────
#>   └─ 1: "CC03_renamed";!- Name
#>      ^~~~~~~~~~~~~~~~~~~~~~~~~
#>      └─ Class: <Construction>
#>         └─ Object [ID:69] <FLOOR-SLAB-1>
#>            └─ 2: "CC03_renamed";!- Outside Layer
#> 
#> ── Node Relation ───────────────────────────────────────────────────────────────
#>   └─ 1: "CC03_renamed";!- Name

Sometimes, you may want to get all possible values of fields before you change them. You can achieve that by using $value_possible() method in IdfObject class.

mat$value_possible(c(2, 7))

Insert objects

Sometimes it may be useful to insert objects from other IDFs. For example, you may want to import some design days and update location data from a “.ddy” file. You can achieve that using $insert().

# read ddy file as normal IDF
ddy <- read_idf("San_Francisco.ddy", idd = 8.8)

model$insert(ddy$SizingPeriod_DesignDay)
#> $`San Francisco Intl Ap Ann Htg 99.6% Condns DB`
#> <IdfObject: 'SizingPeriod:DesignDay'> [ID:329] `San Francisco Intl Ap Ann Htg 99.6% Condns DB`
#> ── COMMENTS ────────────────────────────────────────────────────────────────────
#> ! Using Design Conditions from "Climate Design Data 2009 ASHRAE Handbook"
#> ! San Francisco Intl Ap_CA_USA Extreme Annual Wind Speeds, 1%=12.8m/s, 2.5%...
#> ! San Francisco Intl Ap_CA_USA Extreme Annual Temperatures, Max Drybulb=1.8...
#> ! San Francisco Intl Ap_CA_USA Annual Heating Design Conditions Wind Speed=...
#> ! Coldest Month=JAN
#> ! San Francisco Intl Ap_CA_USA Annual Heating 99.6%, MaxDB=3.8�C
#> ── VALUES ──────────────────────────────────────────────────────────────────────
#> Class: <SizingPeriod:DesignDay>
#> ├─ 01*: "San Francisco Intl Ap Ann Htg 99.6% Condns DB",  !- Name
#> │─ 02*: 1,                  !- Month
#> │─ 03*: 21,                 !- Day of Month
#> │─ 04*: "WinterDesignDay",  !- Day Type
#> │─ 05 : 3.8,                !- Maximum Dry-Bulb Temperature {C}
#> │─ 06 : 0,                  !- Daily Dry-Bulb Temperature Range {deltaC}
#> │─ 07 : "DefaultMultipliers",  !- Dry-Bulb Temperature Range Modifier Type
#> │─ 08 : <"Blank">,          !- Dry-Bulb Temperature Range Modifier Day Sche...
#> │─ 09 : "Wetbulb",          !- Humidity Condition Type
....

# get location data
loc <- ddy$Site_Location$value()

model$Site_Location$set(loc)
#> <IdfObject: 'Site:Location'> [ID:8] `San Francisco Intl Ap_CA_USA Design_Conditions`
#> Class: <Site:Location>
#> ├─ 1*: "San Francisco Intl Ap_CA_USA Design_Conditions",  !- Name
#> │─ 2 : 37.62,              !- Latitude {deg}
#> │─ 3 : -122.4,             !- Longitude {deg}
#> │─ 4 : -8,                 !- Time Zone {hr}
#> └─ 5 : 2;                  !- Elevation {m}

Load objects

Here load means insert. You can use character vectors or data.frames to load new objects.

mat_chr <- c("Construction,", "new_const1,", paste0(model$Material[[1]]$name(), ";"))
model$load(mat_chr)
#> $new_const1
#> <IdfObject: 'Construction'> [ID:347] `new_const1`
#> Class: <Construction>
#> ├─ 1*: "new_const1", !- Name
#> └─ 2*: "WD10";       !- Outside Layer

# extract first construction data in a data.table
dt <- model$Construction[[1L]]$to_table()
# modify value
dt[1, value := "new_const2"]
model$load(dt)
#> $new_const2
#> <IdfObject: 'Construction'> [ID:348] `new_const2`
#> Class: <Construction>
#> ├─ 1*: "new_const2", !- Name
#> │─ 2*: "RG01",       !- Outside Layer
#> │─ 3 : "BR01",       !- Layer 2
#> │─ 4 : "IN46",       !- Layer 3
#> └─ 5 : "WD01";       !- Layer 4

The relation is automatically generated whenever new fields are added or modified.

model$object_relation("new_const1")
#> ── Refer to Others ─────────────────────────────────────────────────────────────
#>   Class: <Construction>
#>   └─ Object [ID:347] <new_const1>
#>      └─ 2: "WD10";        !- Outside Layer
#>         v~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#>         └─ Class: <Material>
#>            └─ Object [ID:43] <WD10>
#>               └─ 1: "WD10";        !- Name
#> 
#> ── Referred by Others ──────────────────────────────────────────────────────────
#> Target(s) is not referred by any other field.
#> 
#> ── Node Relation ───────────────────────────────────────────────────────────────
#> Target(s) has no node or their nodes have no reference to other object.
model$object_relation("new_const2")
#> ── Refer to Others ─────────────────────────────────────────────────────────────
#>   Class: <Construction>
#>   └─ Object [ID:348] <new_const2>
#>      ├─ 2: "RG01",        !- Outside Layer
#>      │  v~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#>      │  └─ Class: <Material>
#>      │     └─ Object [ID:44] <RG01>
#>      │        └─ 1: "RG01";        !- Name
#>      │─ 3: "BR01",        !- Layer 2
#>      │  v~~~~~~~~~~~~~~~~~~~~~~~~~~~
#>      │  └─ Class: <Material>
#>      │     └─ Object [ID:45] <BR01>
#>      │        └─ 1: "BR01";        !- Name
#>      │─ 4: "IN46",        !- Layer 3
#>      │  v~~~~~~~~~~~~~~~~~~~~~~~~~~~
#>      │  └─ Class: <Material>
#>      │     └─ Object [ID:46] <IN46>
#>      │        └─ 1: "IN46";        !- Name
#>      └─ 5: "WD01";        !- Layer 4
#>         v~~~~~~~~~~~~~~~~~~~~~~~~~~~
#>         └─ Class: <Material>
#>            └─ Object [ID:47] <WD01>
#>               └─ 1: "WD01";        !- Name
#> 
#> ── Referred by Others ──────────────────────────────────────────────────────────
#> Target(s) is not referred by any other field.
#> 
#> ── Node Relation ───────────────────────────────────────────────────────────────
#> Target(s) has no node or their nodes have no reference to other object.

Update objects

Here update means set. You can use character vectors or data.frames to update existing objects.

mat_chr <- model$Material$WD10$to_string()
# change material density
mat_chr[6] <- "600,"
model$update(mat_chr)
#> $WD10
#> <IdfObject: 'Material'> [ID:43] `WD10`
#> Class: <Material>
#> ├─ 1*: "WD10",         !- Name
#> │─ 2*: "MediumSmooth", !- Roughness
#> │─ 3*: 0.667,          !- Thickness {m}
#> │─ 4*: 0.115,          !- Conductivity {W/m-K}
#> │─ 5*: 600,            !- Density {kg/m3}
#> │─ 6*: 1381,           !- Specific Heat {J/kg-K}
#> │─ 7 : 0.9,            !- Thermal Absorptance
#> │─ 8 : 0.78,           !- Solar Absorptance
#> └─ 9 : 0.78;           !- Visible Absorptance

# extract roof construction data in a data.table
dt <- model$Construction$`ROOF-1`$to_table()
# modify value
dt[1, value := "ROOF"]
model$update(dt)
#> $ROOF
#> <IdfObject: 'Construction'> [ID:66] `ROOF`
#> Class: <Construction>
#> ├─ 1*: "ROOF",     !- Name
#> │─ 2*: "RG01",     !- Outside Layer
#> │─ 3 : "BR01",     !- Layer 2
#> │─ 4 : "IN46",     !- Layer 3
#> └─ 5 : "WD01";     !- Layer 4

Delete object

$del() will delete objects specified by object IDs or names. For example, in current model, there is a material named "MAT-CLNG-1" in class Material:NoMass. Let’s see if it has been referred by other objects.

model$Material_NoMass$`MAT-CLNG-1`$value_relation()
#> ── Refer to Others ─────────────────────────────────────────────────────────────
#>   ├─ 1: "MAT-CLNG-1",  !- Name
#>   │─ 2: "Rough",       !- Roughness
#>   │─ 3: 0.65225929,    !- Thermal Resistance
#>   │─ 4: 0.65,          !- Thermal Absorptance
#>   │─ 5: 0.65,          !- Solar Absorptance
#>   └─ 6: 0.65;          !- Visible Absorptance
#> 
#> ── Referred by Others ──────────────────────────────────────────────────────────
#>   ├─ 1: "MAT-CLNG-1",  !- Name
#>   │  ^~~~~~~~~~~~~~~~~~~~~~~~~
#>   │  └─ Class: <Construction>
#>   │     └─ Object [ID:68] <CLNG-1>
#>   │        └─ 2: "MAT-CLNG-1";  !- Outside Layer
#>   │─ 2: "Rough",       !- Roughness
#>   │─ 3: 0.65225929,    !- Thermal Resistance
#>   │─ 4: 0.65,          !- Thermal Absorptance
#>   │─ 5: 0.65,          !- Solar Absorptance
#>   └─ 6: 0.65;          !- Visible Absorptance
#> 
#> ── Node Relation ───────────────────────────────────────────────────────────────
#>   ├─ 1: "MAT-CLNG-1",  !- Name
#>   │─ 2: "Rough",       !- Roughness
#>   │─ 3: 0.65225929,    !- Thermal Resistance
#>   │─ 4: 0.65,          !- Thermal Absorptance
#>   │─ 5: 0.65,          !- Solar Absorptance
#>   └─ 6: 0.65;          !- Visible Absorptance

As we can see, MAT-CLNG-1 has been referred by a construction named "CLNG-1".

First, let’s try to direct delete Material MAT-CLNG-1.

model$del("mat-clng-1")
#> Error: Cannot delete object(s) that are referred by others:
#> 
#>   Class: <Material:NoMass>
#>   └─ Object [ID:55] <MAT-CLNG-1>
#>      └─ 1: "MAT-CLNG-1";  !- Name
#>         ^~~~~~~~~~~~~~~~~~~~~~~~~
#>         └─ Class: <Construction>
#>            └─ Object [ID:68] <CLNG-1>
#>               └─ 2: "MAT-CLNG-1";  !- Outside Layer

We got an error, because directly deleting MAT-CLNG-1 will introduce invalid reference in Construction CLNG-1.

In some cases, you may still want to delete that object. You can achieve this by setting .force to TRUE.

model$del("mat-clng-1", .force = TRUE)
#> Deleting object(s) [ID: 55]
#> 
#> Object relation is shown below:
#>  ── Referred by Others ──────────────────────────────────────────────────────────
#>    Class: <Material:NoMass>
#>    └─ Object [ID:55] <MAT-CLNG-1>
#>       └─ 1: "MAT-CLNG-1";  !- Name
#>          ^~~~~~~~~~~~~~~~~~~~~~~~~
#>          └─ Class: <Construction>
#>             └─ Object [ID:68] <CLNG-1>
#>                └─ 2: "MAT-CLNG-1";  !- Outside Layer
#> 

Paste from IDF Editor

Once an IDF file is opened in IDF Editor, you can copy objects by clicking the Copy Obj button in IDF Editor, and use $paste() to insert those objects into current Idf. Note that IDF Editor only exists on Windows, which means that $paste() will also work only on that platform.

Validate

$validate() checks if there are errors in current Idf under specified validation level. You can customize what kind of errors to check by changing the level argument. The default validation level is equal to eplusr_option("validate_level").

There are 10 different validation check components in total. Three predefined validation level are included, i.e. "none", "draft" and "final". To get what validation components those levels contain, use level_checks().

eplusr_option("validate_level")
#> [1] "final"
str(level_checks("final"))
#> List of 10
#>  $ required_object: logi TRUE
#>  $ unique_object  : logi TRUE
#>  $ unique_name    : logi TRUE
#>  $ extensible     : logi TRUE
#>  $ required_field : logi TRUE
#>  $ auto_field     : logi TRUE
#>  $ type           : logi TRUE
#>  $ choice         : logi TRUE
#>  $ range          : logi TRUE
#>  $ reference      : logi TRUE

In the previous section, we deleted a material named MAT-CLNG-1.

The final validation level turns all checking components on. We can just trigger invalid reference checking using custom_validate() function.

model$validate(custom_validate(reference = TRUE))
#>  ✖ [1] Errors found during validation.
#> ════════════════════════════════════════════════════════════════════════════════
#> 
#> ── [1] Invalid Reference ───────────────────────────────────────────────────────
#>    Fields below are not one of valid references:
#> 
#>     Class: <Construction>
#>     └─ Object [ID:68] <CLNG-1>
#>        └─ 2: "MAT-CLNG-1";  !- Outside Layer
#> 

As we can see, the invalid reference in construction CLNG-1 is successfully detected.

In this example, we already knows that CLNG-1 is the invalid object. In many cases, we don’t know that information in advance. As $validate() returns a list of data.tables, we can extract invalid objects for different types directly using $validate(). Below we extract all objects that have invalid reference errors.

(id <- model$validate()$invalid_reference$object_id)
#> [1] 68
model$objects(id)
#> $`CLNG-1`
#> <IdfObject: 'Construction'> [ID:68] `CLNG-1`
#> Class: <Construction>
#> ├─ 1*: "CLNG-1",     !- Name
#> └─ 2*: "MAT-CLNG-1"; !- Outside Layer

Then we can use $set() to correct them. We can get all possible values for field Outside Layer using $value_possible() method in IdfObject class.

model$object(id)$value_possible("Outside Layer")$source
#> [[1]]
#>  [1] "WD10"          "RG01"          "BR01"          "IN46"         
#>  [5] "WD01"          "PW03"          "IN02"          "GP01"         
#>  [9] "GP02"          "CC03_renamed"  "CP01"          "MAT-SB-U"     
#> [13] "MAT-FLOOR-1"   "AL21"          "AL23"          "CLEAR 3MM"    
#> [17] "GREY 3MM"      "CLEAR 6MM"     "LoE CLEAR 6MM" "AIR 6MM"      
#> [21] "AIR 13MM"      "ARGON 13MM"

Now let’s change the construction’s Outside Layer to WD10.

model$object(id)$set(Outside_Layer = "WD10")
#> <IdfObject: 'Construction'> [ID:68] `CLNG-1`
#> Class: <Construction>
#> ├─ 1*: "CLNG-1",   !- Name
#> └─ 2*: "WD10";     !- Outside Layer

Save

You can save your model using $save(). If no path is given, the path of model itself will be used. This may overwrite the current file which has a risk of losing your original file and data. You have to set overwrite to TRUE to confirm the process.

model$save(overwrite = TRUE)

model$save("test.idf")

Run and Collect Output

eplusr uses the EnergyPlus command line interface which was introduced since EnergyPlus v8.3.0, which means that $run() only supports models with version higher than v8.3.0.

eplusr will auto-detect already installed EnergyPlus in the standard installation locations. You can get all detected EnergyPlus versions using avail_eplus().

avail_eplus()
#> [1] '8.7.0' '8.8.0' '9.1.0' '9.2.0'

$run() will issue an error if corresponding version of EnergyPlus is not found. If your EnergyPlus was not installed in standard location, you can add that location using use_eplus(). After adding, all models of that version will use this path to call EnergyPlus.

use_eplus("C:/EnergyPlusV8-8-0")

If the needed version of EnergyPlus was not installed, you can use install_eplus() to install it.

install_eplus(ver = 8.8)

$run() will run the current model with specified weather using corresponding version of EnergyPlus. The model and the weather used will be copied to the output directory. An EplusJob object will be returned which provides detailed information of the simulation and methods to collect simulation output. Please see ?EplusJob for details.

# read the model again
model <- read_idf("5Zone_Transformer.idf")
path_epw <- "San_Francisco.epw"

job <- model$run(path_epw, wait = TRUE)
#> ExpandObjects Started.
#> No expanded file generated.
#> ExpandObjects Finished. Time:     0.016
#> EnergyPlus Starting
#> EnergyPlus, Version 8.8.0-7c3bbe4830, YMD=2020.02.20 15:42
#> Processing Data Dictionary
#> Processing Input File
#> Initializing Response Factors
#> Calculating CTFs for "ROOF-1", Construction # 1
#> Calculating CTFs for "WALL-1", Construction # 2
....

Retrieve simulation output

eplusr uses the EnergyPlus SQL output for extracting simulation output. In order to do so, an object in Output:SQLite class with Option Type value of SimpleAndTabular will be automatically created if it does not exists. EplusJob has provided some wrappers that do SQL queries to get report data results, i.e. results from Output:Variable and Output:Meter*. But for Output:Table results, you have to be familiar with the structure of the EnergyPlus SQL output, especially for table “TabularDataWithStrings”. For details, please see “2.20 eplusout.sql”, especially “2.20.4.4 TabularData Table” in EnergyPlus “Output Details and Examples” documentation.

$report_data_dict() returns a data.table which contains all information about report data. For details on the meaning of each columns, please see “2.20.2.1 ReportDataDictionary Table” in EnergyPlus “Output Details and Examples” documentation.

str(job$report_data_dict())
#> Classes 'data.table' and 'data.frame':   20 obs. of  10 variables:
#>  $ report_data_dictionary_index: int  6 8 18 38 238 459 460 461 462 463 ...
#>  $ is_meter                    : int  0 1 1 1 1 0 0 0 0 0 ...
#>  $ type                        : chr  "Avg" "Sum" "Sum" "Sum" ...
#>  $ index_group                 : chr  "Zone" "Facility:Electricity" "Building:Electricity" "Facility:Electricity:InteriorLights" ...
#>  $ timestep_type               : chr  "HVAC System" "HVAC System" "HVAC System" "HVAC System" ...
#>  $ key_value                   : chr  "Environment" "" "" "" ...
#>  $ name                        : chr  "Site Outdoor Air Drybulb Temperature" "Electricity:Facility" "Electricity:Building" "InteriorLights:Electricity" ...
#>  $ reporting_frequency         : chr  "Zone Timestep" "Zone Timestep" "Zone Timestep" "Zone Timestep" ...
#>  $ schedule_name               : chr  NA NA NA NA ...
#>  $ units                       : chr  "C" "J" "J" "J" ...
#>  - attr(*, ".internal.selfref")=<externalptr>

$report_data() extracts the report data using key values and variable names. Just for demonstration, let’s get the transformer input electric power at 11 a.m for the first day of RunPeriod named SUMMERDAY, tag this simulation as case example, and return all possible output columns.

power <- job$report_data("transformer 1", "transformer input electric power", case = "example",
  all = TRUE, simulation_days = 1, environment_name = "summerday", hour = 11, minute = 0)

str(power)
#> Classes 'data.table' and 'data.frame':   1 obs. of  22 variables:
#>  $ case                    : chr "example"
#>  $ datetime                : POSIXct, format: "2020-07-07 11:00:00"
#>  $ month                   : int 7
#>  $ day                     : int 7
#>  $ hour                    : int 11
#>  $ minute                  : int 0
#>  $ dst                     : int 0
#>  $ interval                : int 15
#>  $ simulation_days         : int 1
#>  $ day_type                : chr "Tuesday"
#>  $ environment_name        : chr "SUMMERDAY"
#>  $ environment_period_index: int 4
#>  $ is_meter                : int 0
#>  $ type                    : chr "Avg"
#>  $ index_group             : chr "System"
#>  $ timestep_type           : chr "Zone"
#>  $ key_value               : chr "TRANSFORMER 1"
#>  $ name                    : chr "Transformer Input Electric Power"
#>  $ reporting_frequency     : chr "Zone Timestep"
#>  $ schedule_name           : chr NA
#>  $ units                   : chr "W"
#>  $ value                   : num 12273
#>  - attr(*, ".internal.selfref")=<externalptr>

$report_data() can also directly take the whole or subset results of $report_data_dict() to extract report data. In some case this may be quite handy. Let’s get all report variable with Celsius degree unit.

str(job$report_data(job$report_data_dict()[units == "C"]))
#> Classes 'data.table' and 'data.frame':   192 obs. of  6 variables:
#>  $ case     : chr  "5Zone_Transformer" "5Zone_Transformer" "5Zone_Transformer" "5Zone_Transformer" ...
#>  $ datetime : POSIXct, format: "2020-01-14 00:15:00" "2020-01-14 00:30:00" ...
#>  $ key_value: chr  "Environment" "Environment" "Environment" "Environment" ...
#>  $ name     : chr  "Site Outdoor Air Drybulb Temperature" "Site Outdoor Air Drybulb Temperature" "Site Outdoor Air Drybulb Temperature" "Site Outdoor Air Drybulb Temperature" ...
#>  $ units    : chr  "C" "C" "C" "C" ...
#>  $ value    : num  9.9 9.2 8.5 7.8 8.07 ...
#>  - attr(*, ".internal.selfref")=<externalptr>

$tabular_data() extracts tabular data. For details on the meaning of each columns, please see “2.20.4.4 TabularData Table” in EnergyPlus “Output Details and Examples” documentation.

Now let’s get the total site energy per total building area. Note that the value column in the returned data.table is character types, as some table store not just numbers. We need to manually convert it.

site_energy <- job$tabular_data(
    column_name = "energy per total building area", row_name = "total site energy",
    wide = TRUE, string_value = FALSE
)[[1]]
str(site_energy)
#> Classes 'data.table' and 'data.frame':   1 obs. of  6 variables:
#>  $ case                                  : chr "5Zone_Transformer"
#>  $ report_name                           : chr "AnnualBuildingUtilityPerformanceSummary"
#>  $ report_for                            : chr "Entire Facility"
#>  $ table_name                            : chr "Site and Source Energy"
#>  $ row_name                              : chr "Total Site Energy"
#>  $ Energy Per Total Building Area [MJ/m2]: num 1.44
#>  - attr(*, ".internal.selfref")=<externalptr> 
#>  - attr(*, "sorted")= chr  "case" "report_name" "report_for" "table_name"

Run Parametric Analysis

eplusr provides tools to do parametric simulations which take full advantages of eplusr’s model editing and result collecting functionalities. You can create a parametric job using param_job(), which takes an IDF file or an Idf object as the seed and an EPW file or an Epw object as weather.

param <- param_job(idf = model, epw = path_epw)

param
#> ── EnergPlus Parametric Simulation Job ─────────────────────────────────────────
#> * Seed: '/tmp/RtmpInuXyg/5Zone_Transformer.idf'
#> * Weather: '/tmp/RtmpInuXyg/San_Francisco.epw'
#> * EnergyPlus Version: '8.8.0'
#> * EnergyPlus Path: '/home/hongyuanjia/.local/EnergyPlus-8-8-0'
#> << No measure has been applied >>

param_job() returns a ParametricJob object which provides a prototype of conducting parametric analysis of EnergyPlus simulations. For more details, please see ?param.

Apply measure

$apply_measure() allows to apply a measure to an Idf and create parametric models for simulations. Here, the concept of measure in eplusr is inspired by “measures” in OpenStudio. Basically, a measure is just a function that takes an Idf object and other arguments as input, and returns a modified Idf object as output. Use ... to supply different arguments to that measure.

Let’s create a function that modifies infiltration rate:

set_infil_rate <- function (idf, infil_rate) {

  # validate input value
  # this is optional, as validations will be made when setting values
  stopifnot(is.numeric(infil_rate), infil_rate >= 0)

  if (!idf$is_valid_class("ZoneInfiltration:DesignFlowRate"))
    stop("Input model does not have any object in class `ZoneInfiltration:DesignFlowRate`")

  # get all object IDS
  ids <- idf$object_id("ZoneInfiltration:DesignFlowRate", simplify = TRUE)

  # make a list of new values to set
  new_val <- list(design_flow_rate_calculation_method = "AirChanges/Hour", air_changes_per_hour = infil_rate)

  # create proper format for all objects in that class
  val <- rep(list(new_val), length(ids))
  names(val) <- paste0("..", ids)

  idf$set(val)

  idf
}

The measure set_infil_rate() is pretty simple. First, it gets all objects in class ZoneInfiltration:DesignFlowRate. Then it sets ACH in all zones to the input value.

Now, let’s apply this measure to the seed model with different infiltration rates from 0.0 to 4.0, respectively.

param$apply_measure(set_infil_rate, seq(0, 4, by = 1), .names = NULL)
#> Measure 'set_infil_rate' has been applied with 5 new models created:
#> [1]: set_infil_rate_1
#> [2]: set_infil_rate_2
#> [3]: set_infil_rate_3
#> [4]: set_infil_rate_4
#> [5]: set_infil_rate_5

As we can see, 5 models have been created. As we left .names as NULL, each newly created models will be named as a combination of measure name and model number.

Run in parallel and collect results

Now let’s run the parametric job. All simulations will be run in parallel. The number of parallel EnergyPlus processes can be specified using option num_parallel. Now let’s run all simulations and wait them to finish.

param$run(wait = TRUE)
#> 1|RUNNING    --> [IDF]'set_infil_rate_1.idf' + [EPW]'San_Francisco.epw'
#> 2|RUNNING    --> [IDF]'set_infil_rate_2.idf' + [EPW]'San_Francisco.epw'
#> 3|RUNNING    --> [IDF]'set_infil_rate_3.idf' + [EPW]'San_Francisco.epw'
#> 4|RUNNING    --> [IDF]'set_infil_rate_4.idf' + [EPW]'San_Francisco.epw'
#> 5|RUNNING    --> [IDF]'set_infil_rate_5.idf' + [EPW]'San_Francisco.epw'
#> 1|COMPLETED  --> [IDF]'set_infil_rate_1.idf' + [EPW]'San_Francisco.epw'
#> 2|COMPLETED  --> [IDF]'set_infil_rate_2.idf' + [EPW]'San_Francisco.epw'
#> 3|COMPLETED  --> [IDF]'set_infil_rate_3.idf' + [EPW]'San_Francisco.epw'
#> 4|COMPLETED  --> [IDF]'set_infil_rate_4.idf' + [EPW]'San_Francisco.epw'
#> 5|COMPLETED  --> [IDF]'set_infil_rate_5.idf' + [EPW]'San_Francisco.epw'
#> ── EnergPlus Parametric Simulation Job ─────────────────────────────────────────
#> * Seed: '/tmp/RtmpInuXyg/5Zone_Transformer.idf'
#> * Weather: '/tmp/RtmpInuXyg/San_Francisco.epw'
#> * EnergyPlus Version: '8.8.0'
#> * EnergyPlus Path: '/home/hongyuanjia/.local/EnergyPlus-8-8-0'
#> Applied Measure: 'set_infil_rate'
#> Parametric Models [5]: 
#> [1]: 'set_infil_rate_1' <-- SUCCEEDED
#> [2]: 'set_infil_rate_2' <-- SUCCEEDED
#> [3]: 'set_infil_rate_3' <-- SUCCEEDED
#> [4]: 'set_infil_rate_4' <-- SUCCEEDED
#> [5]: 'set_infil_rate_5' <-- SUCCEEDED
#>  Simulation started at '2020-02-20 15:42:38' and completed successfully after 1.66 secs.

After all simulations completed, let’s see the variations of total energy.

tab <- param$tabular_data(
    table_name = "Site and Source Energy",
    column_name = "Total Energy",
    row_name = "Total Site Energy"
)

total_eng <- tab[, list(case, `Total Energy (GJ)` = as.numeric(value))]
total_eng
case Total Energy (GJ)
set_infil_rate_1 1.33
set_infil_rate_2 1.35
set_infil_rate_3 1.36
set_infil_rate_4 1.38
set_infil_rate_5 1.39