Nothing
#' @importFrom R6 R6Class
#' @importFrom cli cat_line cat_rule
#' @importFrom utils .DollarNames
#' @include impl-idf.R
NULL
#' Read, Modify, and Run an EnergyPlus Model
#'
#' eplusr provides parsing EnergyPlus Input Data File (IDF) files and strings
#' in a hierarchical structure, which was extremely inspired by
#' [OpenStudio utilities library](https://openstudio-sdk-documentation.s3.amazonaws.com/cpp/OpenStudio-2.5.1-doc/utilities/html/idf_page.html),
#' but with total different data structure under the hook.
#'
#' eplusr uses `Idf` class to present the whole IDF file and use [IdfObject]
#' to present a single object in IDF. Both `Idf` and [IdfObject] contain member
#' functions for helping modify the data in IDF so it complies with the
#' underlying IDD (EnergyPlus Input Data Dictionary).
#'
#' Under the hook, eplusr uses a SQL-like structure to store both IDF and IDD
#' data in different [data.table::data.table]s. So to modify an EnergyPlus model
#' in eplusr is equal to change the data in those IDF tables accordingly, in the
#' context of specific IDD data. This means that a corresponding [Idd] object is
#' needed whenever creating an `Idf` object. eplusr provides several
#' [helpers][use_idd()] to easily download IDD files and create [Idd] objects.
#'
#' All IDF reading process starts with function [read_idf()] which returns an
#' `Idf` object. `Idf` class provides lots of methods to programmatically query
#' and modify EnergyPlus models.
#'
#' Internally, the powerful [data.table](https://cran.r-project.org/package=data.table)
#' package is used to speed up the whole IDF parsing process and store the
#' results. Under the hook, eplusr uses a SQL-like structure to store both IDF
#' and IDD data in [data.table::data.table] format. Every IDF will be parsed and
#' stored in three tables:
#'
#' * `object`: contains object IDs, names and comments.
#' * `value`: contains field values
#' * `reference`: contains cross-reference data of field values.
#'
#' @seealso [IdfObject] class for a single object in an IDF.
#' @author Hongyuan Jia
#' @name Idf
#'
NULL
#' @export
# Idf {{{
Idf <- R6::R6Class(classname = "Idf",
public = list(
# INITIALIZE {{{
#' @description
#' Create an `Idf` object
#'
#' @details
#' It takes an EnergyPlus Input Data File (IDF) as input and returns an
#' `Idf` object.
#'
#' Currently, Imf file is not fully supported. All EpMacro lines will be treated
#' as normal comments of the nearest downwards object. If input is an Imf file,
#' a warning will be given during parsing. It is recommended to convert the Imf
#' file to an Idf file and use [ParametricJob] class to conduct
#' parametric analysis.
#'
#' @param path Either a path, a connection, or literal data (either a single
#' string or a raw vector) to an EnergyPlus Input Data File
#' (IDF). If a file path, that file usually has a extension
#' `.idf`.
#' @param idd Any acceptable input of [use_idd()]. If `NULL`, which is the
#' default, the version of IDF will be passed to [use_idd()]. If
#' the input is an `.ddy` file which does not have a version
#' field, the latest version of [Idf] cached will be used.
#' @param encoding The file encoding of input IDF. Should be one of
#' `"unknown"`, `"Latin-1" and `"UTF-8"`. The default is
#' `"unknown"` which means that the file is encoded in the native
#' encoding.
#'
#' @return An `Idf` object.
#'
#' @examples
#' \dontrun{
#' # example model shipped with eplusr from EnergyPlus v8.8
#' path_idf <- system.file("extdata/1ZoneUncontrolled.idf", package = "eplusr") # v8.8
#'
#' # If neither EnergyPlus v8.8 nor Idd v8.8 was found, error will
#' # occur. If Idd v8.8 is found, it will be used automatically.
#' idf <- Idf$new(path_idf)
#'
#' # argument `idd` can be specified explicitly using `use_idd()`
#' idf <- Idf$new(path_idf, idd = use_idd(8.8))
#'
#' # you can set `download` arugment to "auto" in `use_idd()` if you
#' # want to automatically download corresponding IDD file when
#' # necessary
#' idf <- Idf$new(path_idf, use_idd(8.8, download = "auto"))
#'
#' # Besides use a path to an IDF file, you can also provide IDF in literal
#' # string format
#' string_idf <-
#' "
#' Version, 8.8;
#' Building,
#' Building; !- Name
#' "
#'
#' Idf$new(string_idf, use_idd(8.8, download = "auto"))
#' }
#'
initialize = function(path, idd = NULL, encoding = "unknown") {
# only store if input is a path
if (is.character(path) && length(path) == 1L) {
if (file.exists(path)) private$m_path <- normalizePath(path)
}
idf_file <- parse_idf_file(path, idd, encoding = encoding)
idd <- use_idd(idf_file$version)
# in case there is no version field in input IDF
private$m_version <- idf_file$version
# init idd tbl
private$m_idd <- idd
# init idf tbl
private$m_idf_env <- list2env(idf_file[c("object", "value", "reference")], parent = emptyenv())
# init log data
private$m_log <- new.env(hash = FALSE, parent = emptyenv())
# add a uuid
private$m_log$uuid <- unique_id()
private$m_log$unsaved <- FALSE
private$m_log$order <- private$m_idf_env$object[, list(object_id)][
, object_order := 0L]
private$m_log$view_in_ip <- eplusr_option("view_in_ip")
private$m_log$save_format <- idf_file$options$save_format
},
# }}}
# PUBLIC FUNCTIONS {{{
# version {{{
#' @description
#' Get the version of current `Idf`
#'
#' @details
#' `$version()` returns the version of current `Idf` in a
#' [base::numeric_version()] format. This makes it easy to direction
#' compare versions of different `Idf`s, e.g. `idf$version() > 8.6` or
#' `idf1$version() > idf2$version()`.
#'
#' @return A [base::numeric_version()] object.
#' @examples
#' \dontrun{
#' # get version
#' idf$version()
#' }
#'
version = function()
idf_version(self, private),
# }}}
# path {{{
#' @description
#' Get the file path of current `Idf`
#'
#' @details
#' `$path()` returns the full path of current `Idf` or `NULL` if the
#' `Idf` object is created using a character vector and not saved
#' locally.
#'
#' @return `NULL` or a single string.
#'
#' @examples
#' \dontrun{
#' # get path
#' idf$path()
#'
#' # return `NULL` if Idf is not created from a file
#' Idf$new("Version, 8.8;\n")$path()
#' }
#'
path = function()
idf_path(self, private),
# }}}
# group_name {{{
#' @description
#' Get names of groups
#'
#' @details
#' `$group_name()` returns names of groups current `Idf` contains or
#' the underlying [Idd] object contains.
#'
#' @param all If `FALSE`, only names of groups in current `Idf` object
#' will be returned. If `TRUE`, all group names in the underlying
#' [Idd] will be returned. Default: `FALSE`.
#' @param sorted Only applicable when `all` is `FALSE`. If `TRUE`,
#' duplications in returned group or class names are removed, and
#' unique names are further sorted according to their occurrences
#' in the underlying [Idd]. Default: `TRUE`.
#'
#' @return A character vector.
#'
#' @examples
#' \dontrun{
#' # get names of all groups Idf contains
#' idf$group_name()
#'
#' # get group name of each object in Idf
#' idf$group_name(sorted = FALSE)
#'
#' # get names of all available groups in underlying Idd
#' idf$group_name(all = TRUE)
#' }
#'
group_name = function(all = FALSE, sorted = TRUE)
idf_group_name(self, private, all, sorted),
# }}}
# class_name {{{
#' @description
#' Get names of classes
#'
#' @details
#' `$class_name()` returns names of classes current `Idf` contains or
#' the underlying [Idd] object contains.
#'
#' @param all If `FALSE`, only names of classes in current `Idf` object
#' will be returned. If `TRUE`, all class names in the underlying
#' [Idd] will be returned. Default: `FALSE`.
#' @param sorted Only applicable when `all` is `FALSE`. If `TRUE`,
#' duplications in returned group or class names are removed, and
#' unique names are further sorted according to their occurrences
#' in the underlying [Idd]. Default: `TRUE`.
#' @param by_group Only applicable when `all` or `sorted` is `TRUE`. If
#' `TRUE`, a list is returned which separates class names by the
#' group they belong to.
#'
#' @return A character vector if `by_group` is `FALSE` and a list of
#' character vectors when `by_group` is `TRUE`.
#'
#' @examples
#' \dontrun{
#' # get names of all classes in Idf
#' idf$class_name()
#'
#' # get names of all classes grouped by group names in Idf
#' idf$class_name(by_group = TRUE)
#'
#' # get class name of each object in Idf
#' idf$class_name(sorted = FALSE)
#'
#' # get names of all available classes in underlying Idd
#' idf$class_name(all = TRUE)
#'
#' # get names of all available classes grouped by group names in
#' # underlying Idd
#' idf$class_name(all = TRUE, by_group = TRUE)
#' }
#'
class_name = function(all = FALSE, sorted = TRUE, by_group = FALSE)
idf_class_name(self, private, all, sorted, by_group),
# }}}
# is_valid_group {{{
#' @description
#' Check if elements in input character vector are valid group names.
#'
#' @details
#' `$is_valid_group()` returns `TRUE`s if given character vector
#' contains valid group names in the context of current `Idf` (when
#' `all` is `FALSE`) or current underlying [Idd] (when `all` is `TRUE`).
#'
#' Note that case-sensitive matching is performed, which means that
#' `"Location and Climate"` is a valid group name but `"location and
#' climate"` is not.
#'
#' @param group A character vector to check.
#' @param all If `FALSE`, check if input characters are valid group names
#' for current `Idf`. If `TRUE`, check if input characters are
#' valid group names for underlying [Idd]. Default: FALSE
#'
#' @return A logical vector with the same length as input character
#' vector.
#'
#' @examples
#' \dontrun{
#' # check if input is a valid group name in current Idf
#' idf$is_valid_group(c("Schedules", "Compliance Objects"))
#'
#' # check if input is a valid group name in underlying Idd
#' idf$is_valid_group(c("Schedules", "Compliance Objects"), all = TRUE)
#' }
#'
is_valid_group = function(group, all = FALSE)
idf_is_valid_group_name(self, private, group, all),
# }}}
# is_valid_class {{{
#' @description
#' Check if elements in input character vector are valid class names.
#'
#' @details
#' `$is_valid_class()` returns `TRUE`s if given character vector
#' contains valid class names in the context of current `Idf` (when
#' `all` is `FALSE`) or current underlying [Idd] (when `all` is `TRUE`),
#' and `FALSE`s otherwise.
#'
#' Note that case-sensitive matching is performed, which means that
#' `"Version"` is a valid class name but `"version"` is not.
#'
#' @param class A character vector to check.
#' @param all If `FALSE`, check if input characters are valid class names
#' for current `Idf`. If `TRUE`, check if input characters are
#' valid class names for underlying [Idd]. Default: FALSE
#'
#' @return A logical vector with the same length as input character
#' vector.
#'
#' @examples
#' \dontrun{
#' # check if input is a valid class name in current Idf
#' idf$is_valid_class(c("Building", "ShadowCalculation"))
#'
#' # check if input is a valid class name in underlying Idd
#' idf$is_valid_class(c("Building", "ShadowCalculation"), all = TRUE)
#' }
#'
is_valid_class = function(class, all = FALSE)
idf_is_valid_class_name(self, private, class, all),
# }}}
# definition {{{
#' @description
#' Get the [IddObject] object for specified class.
#'
#' @details
#' `$definition()` returns an [IddObject] of given class. [IddObject]
#' contains all data used for parsing and creating an [IdfObject]. For
#' details, please see [IddObject] class.
#'
#' @param class A **single** string of valid class name in current
#' [Idd]. If `NULL`, the underlying [Idd] object is returned.
#' Default: `NULL`.
#'
#' @return An [IddObject] object if class is not `NULL` or an [Idd]
#' object if class is `NULL`.
#'
#' @examples
#' \dontrun{
#' # get the IddObject object for specified class
#' idf$definition("Version")
#' }
#'
definition = function(class = NULL)
idf_definition(self, private, class),
# }}}
# object_id {{{
#' @description
#' Get the unique ID for each object in specified classes in the `Idf`.
#'
#' @details
#' In `Idf`, each object is assigned with an integer as an universally
#' unique identifier (UUID) in the context of current `Idf`. UUID is
#' not reused even if the object associated is deleted.
#'
#' `$object_id()` returns an integer vector (when `simplify` is `TRUE`)
#' or a named list (when `simplify` is `FALSE`) of integer vectors that
#' contain object IDs in each specified class. The returned list is
#' named using specified class names.
#'
#' @param class A character vector that contains valid class names for
#' current `Idf` object. If `NULL`, all classes in current `Idf`
#' object are used. Default: `NULL`.
#' @param simplify If `TRUE`, an integer vector contains object IDs of
#' all specified classes is returned. If `FALSE`, a named list
#' that contains object IDs for each specified class is returned.
#' Default: `FALSE`.
#'
#' @return An integer vector (when `simplify` is `TRUE`) or a named list
#' of integer vectors (when `simplify` is `FALSE`).
#'
#' @examples
#' \dontrun{
#' # get IDs of all objects in current Idf object
#' idf$object_id()
#'
#' # get IDs of all objects in current Idf object, and merge them into a
#' # single integer vector
#' idf$object_id(simplify = TRUE)
#'
#' # get IDs of objects in class Version and Zone
#' idf$object_id(c("Version", "Zone"))
#'
#' # get IDs of objects in class Version and Zone, and merge them into a
#' # single integer vector
#' idf$object_id(c("Version", "Zone"), simplify = TRUE)
#' }
#'
object_id = function(class = NULL, simplify = FALSE)
idf_object_id(self, private, class, simplify),
# }}}
# object_name {{{
#' @description
#' Get names for objects in specified classes in the `Idf`.
#'
#' @details
#' In `Idf`, each object is assigned with a single string as the name
#' for it, if the class it belongs to has name attribute, e.g. class
#' `RunPeriod`, `Material` and etc. That name should be unique among all
#' objects in that class. EnergyPlus will fail with an error if
#' duplications are found among object names in a class.
#'
#' `$object_name()` returns a character vector (when `simplify` is
#' `TRUE`) or a named list (when `simplify` is `FALSE`) of character
#' vectors that contain object IDs in each specified class. The returned
#' list is named using specified class names. If specified class does
#' not have name attribute, `NA`s are returned.
#'
#' @param class A character vector that contains valid class names for
#' current `Idf`. If `NULL`, all classes in current `Idf` are
#' used. Default: `NULL`.
#' @param simplify If `TRUE`, a character vector contains object names
#' of all specified classes is returned. If `FALSE`, a named list
#' that contains a character vector for each specified class is
#' returned. Default: `FALSE`.
#'
#' @return A character vector (when `simplify` is `TRUE`) or a named
#' list of character vectors (when `simplify` is `FALSE`).
#'
#' @examples
#' \dontrun{
#' # get names of all objects in current Idf object
#' idf$object_name()
#'
#' # get names of all objects in current Idf object, and merge them into
#' # a single character vector
#' idf$object_name(simplify = TRUE)
#'
#' # get names of objects in class Version and Zone
#' idf$object_name(c("Version", "Zone"))
#'
#' # get names of objects in class Version and Zone, and merge them into
#' # a single character vector
#' idf$object_name(c("Version", "Zone"), simplify = TRUE)
#' }
#'
object_name = function(class = NULL, simplify = FALSE)
idf_object_name(self, private, class, simplify),
# }}}
# object_num {{{
#' @description
#' Get number of objects in specified classes in the [Idf] object.
#'
#' @details
#' `$object_num()` returns an integer vector of object number in
#' specified classes. `0` is returned if there is no object in that
#' class.
#'
#' @param class A character vector that contains valid class names for
#' underlying [Idd]. If `NULL`, all classes in current `Idf` are
#' used, and the total object number is returned. Default: `NULL`.
#'
#' @return An integer vector.
#'
#' @examples
#' \dontrun{
#' # get total number of objects
#' idf$object_num()
#'
#' # get number of objects in class Zone and Schedule:Compact
#' idf$object_num(c("Zone", "Schedule:Compact"))
#' }
#'
object_num = function(class = NULL)
idf_object_num(self, private, class),
# }}}
# is_valid_id {{{
#' @description
#' Check if elements in input integer vector are valid object IDs.
#'
#' @details
#' `$is_valid_id()` returns `TRUE`s if given integer vector
#' contains valid object IDs in current `Idf` object.
#'
#' @param id An integer vector to check.
#' @param class A single string indicates the class where the objects to
#' check against. If `NULL`, all classes in current `Idf` are
#' used. Default: `NULL`.
#'
#' @return A logical vector with the same length as input integer
#' vector.
#'
#' @examples
#' \dontrun{
#' idf$is_valid_id(c(51, 1000))
#' }
#'
is_valid_id = function(id, class = NULL)
idf_is_valid_object_id(self, private, id, class),
# }}}
# is_valid_name {{{
#' @description
#' Check if elements in input character vector are valid object names.
#'
#' @details
#' `$is_valid_name()` returns `TRUE`s if given character vector
#' contains valid object names in current `Idf` object.
#'
#' Note that **case-insensitive** matching is performed, which means
#' that `"rOoF"` is equivalent to `"roof"`. This behavior is consistent
#' in all methods that take object name(s) as input.
#'
#' @param name A character vector to check.
#' @param class A single string indicates the class where the objects to
#' check against. If `NULL`, all classes in current `Idf` are
#' used. Default: `NULL`.
#'
#' @return A logical vector with the same length as input character
#' vector.
#'
#' @examples
#' \dontrun{
#' idf$is_valid_name(c("Simple One Zone (Wireframe DXF)", "ZONE ONE", "a"))
#'
#' # name matching is case-insensitive
#' idf$is_valid_name(c("simple one zone (wireframe dxf)", "zone one", "a"))
#' }
#'
is_valid_name = function(name, class = NULL)
idf_is_valid_object_name(self, private, name, class),
# }}}
# object {{{
#' @description
#' Extract an [IdfObject] object using object ID or name.
#'
#' @details
#' `$object()` returns an [IdfObject] object specified by an object ID
#' or name.
#'
#' Note that unlike object ID, which is always unique across the whole
#' `Idf` object, different objects can have the same name. If the name
#' given matches multiple objects, an error is issued showing what
#' objects are matched by the same name. This behavior is consistent in
#' all methods that take object name(s) as input. In this case, it is
#' suggested to directly use object ID instead of name.
#'
#' Note that **case-insensitive** matching is performed for object
#' names, which means that `"rOoF"` is equivalent to `"roof"`. This
#' behavior is consistent in all methods that take object name(s) as
#' input.
#'
#' @param which A single integer specifying the object ID or a single
#' string specifying the object name.
#' @param class A character vector that contains valid class names for
#' current `Idf` object used to locate objects. If `NULL`, all
#' classes in current `Idf` object are used. Default: `NULL`.
#'
#' @return An [IdfObject] object.
#'
#' @examples
#' \dontrun{
#' # get an object whose ID is 3
#' idf$object(3)
#'
#' # get an object whose name is "simple one zone (wireframe dxf)"
#' # NOTE: object name matching is case-insensitive
#' idf$object("simple one zone (wireframe dxf)")
#' }
#'
object = function(which, class = NULL)
idf_obj(self, private, which, class),
# }}}
# objects {{{
#' @description
#' Extract multiple [IdfObject] objects using object IDs or names.
#'
#' @details
#' `$objects()` returns a named list of [IdfObject] objects using object
#' IDS or names. The returned list is named using object names.
#'
#' Note that unlike object ID, which is always unique across the whole
#' `Idf` object, different objects can have the same name. If the name
#' given matches multiple objects, an error is issued showing what
#' objects are matched by the same name. This behavior is consistent in
#' all methods that take object name(s) as input. In this case, it is
#' suggested to directly use object ID instead of name.
#'
#' Note that **case-insensitive** matching is performed for object
#' names, which means that `"rOoF"` is equivalent to `"roof"`. This
#' behavior is consistent in all methods that take object name(s) as
#' input.
#'
#' @param which An integer vector specifying object IDs or a character
#' vector specifying object names.
#'
#' @return A named list of [IdfObject] objects.
#'
#' @examples
#' \dontrun{
#' # get objects whose IDs are 3 and 10
#' idf$objects(c(3,10))
#'
#' # get objects whose names are "Simple One Zone (Wireframe DXF)" and "ZONE ONE"
#' # NOTE: object name matching is case-insensitive
#' idf$objects(c("Simple One Zone (Wireframe DXF)", "zone one"))
#' }
#'
objects = function(which)
idf_objects(self, private, which),
# }}}
# object_unique {{{
#' @description
#' Extract the [IdfObject] in class with `unique-object` attribute.
#'
#' @details
#' For each version of an `Idf` object, the corresponding underlying
#' [Idd] describe how many objects can be defined in each class. Classes
#' that have `unique-object` attribute can only hold a single object,
#' e.g. `Version`, `SimulationControl` and etc. `$object_unique()` can
#' be used to directly return the [IdfObject] in one `unique-object`
#' class. An error will be issued if there are multiple objects in that
#' class or input class is not an `unique-object` class. This makes sure
#' that `$object_unique()` always returns a single [IdfObject].
#'
#' `Idf` class also provides custom S3 method of `$` and \code{[[} to
#' make it more convenient to get the [IdfObject] in `unique-object`
#' class. Basically, `idf$ClassName` and \code{idf[["ClassName"]]},
#' where `ClassName` is a single valid class name, is equivalent to
#' `idf$object_unique(ClassName)` if `ClassName` is an `unique-object`
#' class. For convenience, underscore-style names are allowed when using
#' `$`, e.g. `Site_Location` is equivalent to `Site:Location`. For
#' instance, `idf$Site_Location` and also `idf[["Site_Location"]]` will
#' both return the [IdfObject]s in `Site:Location` class. Note that
#' unlike `$object_unique()`, `idf$ClassName` and `idf[["ClassName"]]`
#' will directly return `NULL` instead of giving an error when
#' `ClassName` is not a valid class name in current `Idf` object. This
#' makes it possible to use `is.null(idf$ClassName)` to check if
#' `ClassName` is a valid class or not.
#'
#' @param class A single string of valid class name for current `Idf`
#' object.
#'
#' @return An [IdfObject] object.
#'
#' @examples
#' \dontrun{
#' # get the SimulationColtrol object
#' idf$object_unique("SimulationControl")
#'
#' # S3 "[[" and "$" can also be used
#' idf$SimulationControl
#' idf[["SimulationControl"]]
#' }
#'
object_unique = function(class)
idf_object_unique(self, private, class),
# }}}
# objects_in_class {{{
#' @description
#' Extract all [IdfObject] objects in one class.
#'
#' @details
#' `$objects_in_class()` returns a named list of all [IdfObject] objects
#' in specified class. The returned list is named using object names.
#'
#' `Idf` class also provides custom S3 method of `$` and \code{[[} to
#' make it more convenient to get all [IdfObject] objects in one class.
#' Basically, `idf$ClassName` and \code{idf[["ClassName"]]}, where
#' `ClassName` is a single valid class name, is equivalent to
#' `idf$objects_in_class(ClassName)` if `ClassName` is not an
#' `unique-object` class. For convenience, *underscore-style* names are
#' allowed, e.g. `BuildingSurface_Detailed` is equivalent to
#' `BuildingSurface:Detailed` when using `$`. For instance,
#' `idf$BuildingSurface_Detailed` and also
#' `idf[["BuildingSurface:Detailed"]]` will both return all [IdfObject]
#' objects in `BuildingSurface:Detailed` class. Note that
#' unlike `$objects_in_class()`, `idf$ClassName` and
#' `idf[["ClassName"]]` will directly return `NULL` instead of giving
#' an error when `ClassName` is not a valid class name in current `Idf`
#' object. This makes it possible to use `is.null(idf$ClassName)` to
#' check if `ClassName` is a valid class or not.
#'
#' @param class A single string of valid class name for current `Idf`
#' object.
#'
#' @return A named list of [IdfObject] objects.
#'
#' @examples
#' \dontrun{
#' # get all objects in Zone class
#' idf$objects_in_class("Zone")
#'
#' # S3 "[[" and "$" can also be used
#' idf$Zone
#' idf[["Zone"]]
#' }
#'
objects_in_class = function(class)
idf_objects_in_class(self, private, class),
# }}}
# objects_in_group {{{
#' @description
#' Extract all [IdfObject] objects in one group.
#'
#' @details
#' `$objects_in_group()` returns a named list of all [IdfObject] objects
#' in specified group. The returned list is named using object names.
#'
#' @param group A single string of valid group name for current `Idf`
#' object.
#'
#' @return A named list of [IdfObject] objects.
#'
#' @examples
#' \dontrun{
#' # get all objects in Schedules group
#' idf$objects_in_group("Schedules")
#' }
#'
objects_in_group = function(group)
idf_objects_in_group(self, private, group),
# }}}
# object_relation {{{
#' @description
#' Extract the relationship between object field values.
#'
#' @details
#' Many fields in [Idd] 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`. In
#' EnergyPlus, there is also a special type of field called `Node`,
#' which together with `Branch`, `BranchList` and other classes define
#' the topography of the HVAC connections. A outlet node of a component
#' can be referred by another component as its inlet node, but can also
#' exists independently, such as zone air node.
#'
#' `$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. For instance, if
#' `model$object_relation("WALL-1", "ref_to")` gives results below:
#'
#' ```
#' -- Refer to Others ------------------------
#' Class: <Construction>
#' \- Object [ID:2] <WALL-1>
#' \- 2: "WD01"; !- Outside Layer
#' v~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#' \- Class: <Material>
#' \- Object [ID:1] <WD01>
#' \- 1: "WD01"; !- Name
#' ```
#'
#' This means that the value `"WD01"` of `Outside Layer` in a
#' construction named `WALL-1` refers to a material named `WD01`. All
#' those objects can be further easily extracted using
#' `$objects_in_relation()` method described below.
#'
#' @param which A single integer specifying object ID or a single string
#' specifying object name.
#' @param direction The relation direction to extract. Should be either
#' `"all"`, `"ref_to"`, `"ref_by"` and `"node"`.
#' @param object A character vector of object names or an integer vector
#' of object IDs used for searching relations. Default: `NULL`.
#' @param class A character vector of class names used for searching
#' relations. Default: `NULL`.
#' @param group A character vector of group names used for searching
#' relations. Default: `NULL`.
#' @param depth If > 0, the relation is searched recursively. A
#' simple example of recursive reference: one material named
#' `mat` is referred by a construction named `const`, and `const`
#' is also referred by a surface named `surf`. If `NULL`,
#' all possible recursive relations are returned. Default: `0`.
#' @param keep If `TRUE`, all fields of specified object are returned
#' regardless they have any relations with other objects or not.
#' If `FALSE`, only fields in specified object that have
#' relations with other objects are returned. Default: `FALSE`.
#' @param class_ref Specify how to handle class-name-references. Class
#' name references refer to references in like field `Component 1
#' Object Type` in `Branch` objects. Their value refers to other
#' many class names of objects, instaed of referring to specific
#' field values. There are 3 options in total, i.e. `"none"`,
#' `"both"` and `"all"`, with `"both"` being the default.
#' * `"none"`: just ignore class-name-references. It is a reasonable
#' option, as for most cases, class-name-references always come
#' along with field value references. Ignoring
#' class-name-references will not impact the most part of the
#' relation structure.
#' * `"both"`: only include class-name-references if this object
#' also reference field values of the same one. For example, if the
#' value of field `Component 1 Object Type` is
#' `Coil:Heating:Water`, only the object that is referenced in the
#' next field `Component 1 Name` is treated as referenced by
#' `Component 1 Object Type`. This is the default option.
#' * `"all"`: include all class-name-references. For example, if the
#' value of field `Component 1 Object Type` is
#' `Coil:Heating:Water`, all objects in `Coil:Heating:Water` will
#' be treated as referenced by that field. This is the most
#' aggressive option.
#'
#' @return An `IdfRelation` object, which is a list of 3
#' [data.table::data.table()]s named `ref_to`, `ref_by` and `node`.
#' Each [data.table::data.table()] contains 24 columns.
#'
#' @examples
#' \dontrun{
#' # check each layer's reference of a construction named FLOOR
#' idf$object_relation("floor", "ref_to")
#'
#' # check where is this construction being used
#' idf$object_relation("floor", "ref_by")
#' }
#'
object_relation = function(which, direction = c("all", "ref_to", "ref_by", "node"),
object = NULL, class = NULL, group = NULL, depth = 0L,
keep = FALSE, class_ref = c("both", "none", "all"))
idf_object_relation(self, private, which, match.arg(direction),
object = object, class = class, group = group, depth = depth, keep = keep, class_ref = match.arg(class_ref)),
# }}}
# objects_in_relation {{{
#' @description
#' Extract multiple [IdfObject] objects referencing each others.
#'
#' @details
#' `$objects_in_relation()` returns a named list of [IdfObject] objects
#' that have specified relationship with given object. The first element of
#' returned list is always the specified object itself. If that
#' object does not have specified relationship with other objects in
#' specified `class`, a list that only contains specified object itself
#' is returned.
#'
#' For instance, assuming that `const` is a valid object name in
#' `Construction` class, `idf$objects_in_relation("const", "ref_by",
#' "BuildingSurface:Detailed")`
#' will return a named list of an [IdfObject] object named `const` and
#' also all other [IdfObject] objects in `BuildingSurface:Detailed`
#' class that refer to field values in `const`. Similarly,
#' `idf$objects_in_relation("const", "ref_to", "Material")`
#' will return a named list of an [IdfObject] object named `const` and
#' also all other [IdfObject] objects in `Material` class that `const`
#' refers to. This makes it easy to directly extract groups of related
#' objects and then use `$insert()` method or other methods
#' described below to insert them or extract data.
#'
#' There are lots of recursive references in a model. For instance, a
#' material can be referred by a construction, that construction can be
#' referred by a building surface, and that building surface can be
#' referred by a window on that surface. These objects related
#' recursively can be extracted by setting `recursive` to `TRUE`.
#'
#' @param which A single integer specifying object ID or a single string
#' specifying object name.
#' @param direction The relation direction to extract. Should be one of
#' `"ref_to"`, `"ref_by"` or `"node"`.
#' @param object A character vector of object names or an integer vector
#' of object IDs used for searching relations. Default: `NULL`.
#' @param class A character vector of valid class names in the
#' underlying [Idd]. It is used to restrict the classes to be
#' returned. If `NULL`, all possible classes are considered and
#' corresponding [IdfObject] objects are returned if
#' relationships are found. Default: `NULL`.
#' @param group A character vector of valid group names in the
#' underlying [Idd]. It is used to restrict the groups to be
#' returned. If `NULL`, all possible groups are considered and
#' corresponding [IdfObject] objects are returned if
#' relationships are found. Default: `NULL`.
#' @param depth If > 0, the relation is searched recursively. A
#' simple example of recursive reference: one material named
#' `mat` is referred by a construction named `const`, and `const`
#' is also referred by a surface named `surf`. If `NULL`,
#' all possible recursive relations are returned. Default: `0`.
#' @param class_ref Specify how to handle class-name-references. Class
#' name references refer to references in like field `Component 1
#' Object Type` in `Branch` objects. Their value refers to other
#' many class names of objects, instaed of refering to specific
#' field values. There are 3 options in total, i.e. `"none"`,
#' `"both"` and `"all"`, with `"both"` being the default.
#' * `"none"`: just ignore class-name-references. It is a reasonable
#' option, as for most cases, class-name-references always come
#' along with field value references. Ignoring
#' class-name-references will not impact the most part of the
#' relation structure.
#' * `"both"`: only include class-name-references if this object
#' also reference field values of the same one. For example, if the
#' value of field `Component 1 Object Type` is
#' `Coil:Heating:Water`, only the object that is referenced in the
#' next field `Component 1 Name` is treated as referenced by
#' `Component 1 Object Type`. This is the default option.
#' * `"all"`: include all class-name-references. For example, if the
#' value of field `Component 1 Object Type` is
#' `Coil:Heating:Water`, all objects in `Coil:Heating:Water` will
#' be treated as referenced by that field. This is the most
#' aggressive option.
#'
#' @return An named list of [IdfObject] objects.
#'
#' @examples
#' \dontrun{
#' # get a construction named FLOOR and all materials it uses
#' idf$objects_in_relation("floor", "ref_to")
#'
#' # get a construction named FLOOR and all surfaces that uses it
#' idf$objects_in_relation("floor", "ref_by", class = "BuildingSurface:Detailed")
#' }
#'
objects_in_relation = function(which, direction = c("ref_to", "ref_by", "node"),
object = NULL, class = NULL, group = NULL, depth = 0L,
class_ref = c("both", "none", "all"))
idf_objects_in_relation(self, private, which, match.arg(direction),
object = object, class = class, group = group, depth = depth, class_ref = match.arg(class_ref)),
# }}}
# search_object {{{
#' @description
#' Extract multiple [IdfObject] objects using regular expression on
#' names.
#'
#' @details
#' `$search_object()` returns a named list of [IdfObject] objects whose
#' names meet the given regular expression in specified classes.
#'
#' @param pattern,ignore.case,perl,fixed,useBytes All are directly
#' passed to [base::grepl][base::grep()].
#' @param class A character vector of valid class names in the
#' underlying [Idd]. It is used to restrict the classes to be
#' returned. If `NULL`, all possible classes are considered and
#' corresponding [IdfObject] objects are returned if
#' `pattern` is met Default: `NULL`.
#'
#' @return A named list of [IdfObject] objects.
#'
#' @examples
#' \dontrun{
#' # get all objects whose names contains "floor"
#' idf$search_object("floor", ignore.case = TRUE)
#' }
#'
search_object = function(pattern, class = NULL, ignore.case = FALSE,
perl = FALSE, fixed = FALSE, useBytes = FALSE)
idf_search_object(self, private, pattern, class, ignore.case = ignore.case,
perl = perl, fixed = fixed, useBytes = useBytes
),
# }}}
# dup {{{
#' @description
#' Duplicate existing objects.
#'
#' @details
#' `$dup()` takes integer vectors of object IDs and character vectors of
#' object names, duplicates objects specified, and returns a list of
#' newly created [IdfObject] objects. The names of input are used as new
#' names for created [IdfObject]s. If input is not named, new names are
#' the names of duplicated objects with a suffix `"_1"`, `"_2"` and etc,
#' depending on how many times that object has been duplicated. Note an
#' error will be issued if trying to assign a new name to an object
#' which belongs to a class that does not have name attribute.
#'
#' Assigning newly added objects with an existing name in current `Idf`
#' object is prohibited if current validation level includes object name
#' conflicting checking. For details, please see `level_checks()`.
#'
#' @param ... Integer vectors of object IDs and character vectors of
#' object names. If input is named, its name will be used as the
#' name of newly created objects.
#'
#' @return A named list of [IdfObject] objects.
#'
#' @examples
#' \dontrun{
#' # duplicate an object named "FLOOR"
#' idf$dup("floor") # New object name 'FLOOR_1' is auto-generated
#'
#' # duplicate that object again by specifing object ID
#' idf$dup(16) # New object name 'FLOOR_2' is auto-generated
#'
#' # duplicate that object two times and giving new names
#' idf$dup(new_floor = "floor", new_floor2 = 16)
#'
#' # duplicate that object multiple times using variable inputs
#' floors_1 <- c(new_floor3 = "floor", new_floor4 = "floor")
#' floors_2 <- setNames(rep(16, 5), paste0("flr", 1:5))
#' idf$dup(floors_1, floors_2)
#' }
#'
dup = function(...)
idf_dup(self, private, ...),
# }}}
# add {{{
#' @description
#' Add new objects.
#'
#' @details
#' `$add()` takes new object definitions in list format, adds
#' corresponding objects in specified classes, returns a list of newly
#' added [IdfObject] objects. The returned list will be named using
#' newly added object names. Every list should be named using a valid
#' class name. Underscore-style class name is allowed for class name.
#' Names in each list element are treated as field names. Values without
#' names will be inserted according to their position. There is a
#' special element named `.comment` in each list, which will be used as
#' the comments of newly added object.
#'
#' Empty objects can be added using an empty list, e.g.
#' `idf$add(Building = list())`. All empty fields will be filled with
#' corresponding default value if `.default` is `TRUE`, leaving other
#' fields as blanks. However, adding blank objects may not be allowed if
#' there are required fields in that class and current validate level
#' includes missing-required-field checking. For what kind of validation
#' components will be performed during adding new objects, please see
#' [level_checks()].
#'
#' Note that `.()` can be used as an alias as `list()`, e.g.
#' `idf$add(Building = .())` is equivalent to
#' `idf$add(Building = list())`.
#'
#' Field name matching is **case-insensitive**. For convenience,
#' underscore-style field names are also allowed, e.g. `eNd_MoNtH` is
#' equivalent to `End Month`. This behavior is consistent among all
#' methods that take field names as input.
#'
#' There is no need to give all field values if only specific fields are
#' interested, as long as other fields are not required. For example, to
#' define a new object in `RunPeriod` class, the following is enough (at
#' least for EnergyPlus v8.8):
#'
#' ```
#' idf$add(
#' RunPeriod = list(
#' "my run period",
#' begin_month = 1, begin_day_of_month = 1,
#' end_month = 1, end_day_of_month = 31
#' ),
#' .default = TRUE
#' )
#' ```
#'
#' If not all field names are given, positions of those values without
#' field names are determined after those values with names. E.g. in
#' `idf$add(Construction = list("out_layer", name = "name"))`,
#' `"out_layer"` will be treated as the value for field `Outside Layer`
#' in `Construction` class, since the value for field `Name` has been
#' specified using explicit field name.
#'
#' @param ... Lists of object definitions. Each list should be named
#' with a valid class name. There is a special element `.comment`
#' in each list, which will be used as the comments of newly
#' added object.
#' @param .default If `TRUE`, default values are used for those blank
#' fields if possible. If `FALSE`, empty fields are kept blank.
#' Default: `TRUE`.
#' @param .all If `TRUE`, all fields are added. If `FALSE`, only minimum
#' required fields are added. Default: `FALSE`.
#'
#' @return A named list of [IdfObject] objects.
#'
#' @examples
#' \dontrun{
#' # add a new Building object with all default values
#' empty <- empty_idf(8.8) # create an empty Idf
#' empty$add(Building = .())
#'
#' # add a new Building object with all default values and comments
#' empty <- empty_idf(8.8) # create an empty Idf
#' empty$add(Building = .(.comment = c("this is", "a new building")))
#'
#' # add a new RunPeriod object with all possible fields
#' empty <- empty_idf(8.8) # create an empty Idf
#' empty$add(Building = list(), RunPeriod = list("rp", 1, 1, 1, 31), .all = TRUE)
#'
#' # add objects using variable inputs
#' empty <- empty_idf(8.8) # create an empty Idf
#' objs1 <- list(Schedule_Constant = list("const"), Building = list())
#' rp <- list(RunPeriod = list("rp", 2, 1, 2, 28))
#' empty$add(objs1, rp)
#' }
#'
add = function(..., .default = TRUE, .all = FALSE)
idf_add(self, private, ..., .default = .default, .all = .all, .env = parent.frame()),
# }}}
# set {{{
#' @description
#' Set values of existing objects.
#'
#' @details
#' `$set()` takes new field value definitions in list format, sets new
#' values for fields in objects specified, and returns a list of
#' modified [IdfObject]s. The returned list will be named using names of
#' modified objects. Every list in `$set()` should be named with a
#' valid object name. Object ID can also be used but have to be combined
#' with prevailing two periods `..`, e.g. `..10` indicates the object
#' with ID `10`. Similar to
#' \href{../../eplusr/html/Idf.html#method-add}{\code{$add()}}, a
#' special element `.comment` in each list will be used as the **new**
#' comments for modified object, overwriting the old ones. Names in list
#' element are treated as field names.
#'
#' Note that `.()` can be used as an alias as `list()`, e.g.
#' `idf$set(Building = .(...))` is equivalent to
#' `idf$set(Building = list(...))`.
#'
#' There is two special syntax in `$set()`, which is inspired by the
#' [data.table](https://cran.r-project.org/package=eplusr) package:
#'
#' * `class := list(field = value)`: Note the use of `:=` instead of
#' `=`. The main difference is that, unlike `=`, the left hand side of
#' `:=` should be a valid class name in current `Idf` object. It will
#' set the field of all objects in specified class to specified value.
#' * `.(object, object) := list(field = value)`: Similar like above, but
#' note the use of `.()` in the left hand side. You can put multiple
#' object ID or names in `.()`. It will set the field of all specified
#' objects to specified value.
#'
#' You can delete a field by assigning `NULL` to it, e.g. `list(fld =
#' NULL)` means to delete the value of field `fld`, in the condition
#' that `.default` is `FALSE`, `fld` is not a required field and the
#' index of `fld` is larger than the number minimum fields required for
#' that class. If those conditions are not required, `fld` will be left
#' as blank if `.default` is `FALSE` or filled with default value if
#' `.default` is `TRUE`.
#'
#' By default, trailing empty fields that are not required will be
#' removed and only minimum required fields are kept. For example, if
#' `rp` is an object in `RunPeriod` class in an `Idf` of version 8.8,
#' by default empty field with index larger than 11 will be removed
#' since they are all non-required fields. You can keep the trailing
#' empty fields by setting `.empty` to `TRUE`.
#'
#' New fields that currently do not exist in that object can also be
#' set. They will be automatically added on the fly.
#'
#' Field name matching is **case-insensitive**. For convenience,
#' underscore-style field names are also allowed, e.g. `eNd_MoNtH` is
#' equivalent to `End Month`.
#'
#' If not all field names are given, positions of those values without
#' field names are determined after those values with names. E.g. in
#' `idf$set(floor = list("out_layer", name = "name"))`, `"out_layer"`
#' will be treated as the value for field `Outside Layer` in an object
#' named `floor`, since the value for field `Name` has been specified
#' using explicit field name.
#'
#' @param ... Lists of object definitions. Each list should be named
#' with a valid object name or ID denoted in style `..ID`. There
#' is a special element `.comment` in each list, which will be
#' used as new comments of modified object, overwriting existing
#' comments if any.
#' @param .default If `TRUE`, default values are used for those blank
#' fields if possible. If `FALSE`, empty fields are kept blank.
#' Default: `TRUE`.
#' @param .empty If `TRUE`, trailing empty fields are kept. Default:
#' `FALSE`.
#'
#' @return A named list of [IdfObject] objects.
#'
#' @examples
#' \dontrun{
#' # modify an object by name (case-insensitive)
#' idf$set(r13layer = list(roughness = "smooth"))
#'
#' # modify an object by ID
#' idf$set(..12 = list(roughness = "rough"))
#'
#' # overwrite existing object comments
#' idf$set(r13layer = list(.comment = c("New comment")))
#'
#' # assign default values to fields
#' idf$set(r13layer = list(solar_absorptance = NULL), .default = TRUE)
#'
#' # set field values to blanks
#' idf$set(r13layer = list(solar_absorptance = NULL), .default = FALSE)
#'
#' # set field values to blank and delete trailing fields
#' idf$set(r13layer = list(visible_absorptance = NULL), .default = FALSE)
#'
#' # set field values to blank and keep blank fields
#' idf$set(r13layer = list(visible_absorptance = NULL), .default = FALSE, .empty = TRUE)
#'
#' # set all fields in one class
#' idf$set(Material_NoMass := list(visible_absorptance = 0.9))
#'
#' # set multiple objects in one class
#' idf$set(.("r13layer", "r31layer") := list(solar_absorptance = 0.8))
#' # above is equivalent to
#' idf$set(r13layer = list(solar_absorptance = 0.8),
#' r31layer = list(solar_absorptance = 0.8)
#' )
#'
#' # use variable input
#' sets <- list(r13layer = list(roughness = "smooth"))
#' idf$set(sets)
#' }
#'
set = function(..., .default = TRUE, .empty = FALSE)
idf_set(self, private, ..., .default = .default, .empty = .empty, .env = parent.frame()),
# }}}
# del {{{
#' @description
#' Delete existing objects
#'
#' @details
#' `$del()` takes integer vectors of object IDs and character vectors of
#' object names, and deletes objects specified.
#'
#' If current [validate level][level_checks()] includes reference
#' checking, objects will not be allowed to be deleted if they are
#' referred by other objects. For example, an error will be issued if
#' you want to delete one material that is referred by other
#' constructions, because doing so will result in invalid field value
#' references. You may bypass this if you really want to by setting
#' `.force` to `TRUE`.
#'
#' When `.ref_by` or `.ref_to` is `TRUE`, objects will be deleted
#' only when they have and only have relation with input objects but not
#' any other objects. For example, a construction `const` consist of 4
#' different materials. If `.ref_to` is `TRUE`, that 4 materials will
#' only be deleted when they are only used in `const`, but not used in
#' any other objects.
#'
#' There are recursively reference relations in `Idf` object. For
#' example, one material's name is referenced by one construction, and
#' that construction's name can be referred by another surface. You can
#' delete all of them by setting `.recursive` to `TRUE`.
#'
#' If `.ref_by` is `TRUE`, objects whose fields refer to input objects
#' will also be deleted.
#'
#' IF `.ref_to` is `TRUE`, objects whose fields
#' are referred by input objects will also be deleted.
#'
#'
#' @param ... integer vectors of object IDs and character vectors of
#' object names in current `Idf` object.
#' @param .ref_by If `TRUE`, objects whose fields refer to input objects
#' will also be deleted. Default: `FALSE`.
#' @param .ref_to If `TRUE`, objects whose fields are referred by input
#' objects will also be deleted. Default: `FALSE`.
#' @param .recursive If `TRUE`, relation searching is performed
#' recursively, in case that objects whose fields refer to target
#' object are also referred by another object, and also objects
#' whose fields are referred by target object are also referred
#' by another object. Default: `FALSE`.
#' @param .force If `TRUE`, objects are deleted even if they are
#' referred by other objects.
#'
#' @return The modified `Idf` object itself, invisibly.
#'
#' @examples
#' \dontrun{
#' # delete objects using names
#' idf$object("Fraction") # ScheduleTypeLimits
#' idf$del("Fraction")
#'
#' # delete objects using IDs
#' idf$objects(c(39, 40)) # Output:Variable
#' idf$del(39, 40)
#'
#' # cannot delete objects that are referred by others
#' level_checks()$reference # reference-checking is enable by default
#' idf$del("r13layer") # error
#'
#' # force to delete objects even thay are referred by others
#' idf$del("r13layer", .force = TRUE)
#'
#' # delete objects and also objects that refer to them
#' idf$del("r31layer", .ref_by = TRUE) # Construction 'ROOF31' will be kept
#'
#' # delete objects and also objects that they refer to
#' idf$del("extlights", .ref_to = TRUE) # Schedule 'AlwaysOn' will be kept
#'
#' # delete objects and also other objects that refer to them recursively
#' idf$del("roof31", .ref_by = TRUE, .recursive = TRUE)
#'
#' # delete objects using variable inputs
#' ids <- idf$object_id("Output:Variable", simplify = TRUE)
#' idf$del(ids)
#' }
#'
del = function(..., .ref_by = FALSE, .ref_to = FALSE, .recursive = FALSE, .force = FALSE)
idf_del(self, private, ..., .ref_by = .ref_by, .ref_to = .ref_to, .recursive = .recursive, .force = .force),
# }}}
# purge {{{
#' @description
#' Purge resource objects that are not used
#'
#' @details
#' `$purge()` takes an integer vector of object IDs or a character
#' vectors of object names, and deletes resource objects specified that
#' are not used by any objects.
#'
#' Here resource objects indicate all objects that can be referenced by
#' other objects, e.g. all schedules. `$purge()` will ignore any inputs
#' that are not resources. If inputs contain objects from multiple
#' classes, references among them are also taken into account, which
#' means purging is performed hierarchically. If both materials and
#' constructions are specified, the latter will be purged first, because
#' it is possible that input constructions reference input materials.
#'
#' @param object an integer vector of object IDs or a character vector
#' of object names in current `Idf` object. Default: `NULL`.
#' @param class A character vector of valid class names in current `Idf`
#' object. Default: `NULL`.
#' @param group A character vector of valid group names in current `Idf`
#' object. Default: `NULL`.
#'
#' @return The modified `Idf` object itself, invisibly.
#'
#' @examples
#' \dontrun{
#' # purge unused "Fraction" schedule type
#' idf$purge("on/off") # ScheduleTypeLimits
#'
#' # purge all unused schedule types
#' idf$purge(class = "ScheduleTypeLimits")
#'
#' # purge all unused schedule related objects
#' idf$purge(group = "Schedules")
#' }
#'
purge = function(object = NULL, class = NULL, group = NULL)
idf_purge(self, private, object = object, class = class, group = group),
# }}}
# duplicated {{{
#' @description
#' Determine duplicated objects
#'
#' @details
#' `$duplicated()` takes an integer vector of object IDs or a character
#' vectors of object names, and returns a [data.table::data.table()]
#' to show whether input objects contain duplications or not.
#'
#' Here duplicated objects refer to objects whose field values are the
#' same except the names. Object comments are just ignored during
#' comparison.
#'
#' @param object an integer vector of object IDs or a character vector
#' of object names in current `Idf` object. Default: `NULL`.
#' @param class A character vector of valid class names in current `Idf`
#' object. Default: `NULL`.
#' @param group A character vector of valid group names in current `Idf`
#' object. Default: `NULL`.
#'
#' If all `object`, `class` and `group` are `NULL`, duplication checking
#' is performed on the whole `Idf`.
#'
#' @return A [data.table::data.table()] of 4 columns:
#'
#' * `class`: Character. Names of classes that input objects belong to
#' * `id`: Integer. Input object IDs
#' * `name`: Character. Input object names
#' * `duplicate`: Integer. The IDs of objects that input objects
#' duplicate. If input object is not a duplication, `NA` is returned
#'
#' @examples
#' \dontrun{
#' # check if there are any duplications in the Idf
#' idf$duplicated(class = "ScheduleTypeLimits")
#'
#' # check if there are any duplications in the schedule types
#' idf$duplicated(class = "ScheduleTypeLimits")
#'
#' # check if there are any duplications in the schedule groups and
#' # material class
#' idf$duplicated(class = "Material", group = "Schedules")
#' }
#'
duplicated = function(object = NULL, class = NULL, group = NULL)
idf_duplicated(self, private, object = object, class = class, group = group),
# }}}
# unique {{{
#' @description
#' Remove duplicated objects
#'
#' @details
#' `$unique()` takes an integer vector of object IDs or a character
#' vectors of object names, and remove duplicated objects.
#'
#' Here duplicated objects refer to objects whose field values are the
#' same except the names. Object comments are just ignored during
#' comparison.
#'
#' `$unique()` will only keep the first unique object and remove all
#' redundant objects. Value referencing the redundant objects will be
#' redirected into the unique object.
#'
#' @param object an integer vector of object IDs or a character vector
#' of object names in current `Idf` object. Default: `NULL`.
#' @param class A character vector of valid class names in current `Idf`
#' object. Default: `NULL`.
#' @param group A character vector of valid group names in current `Idf`
#' object. Default: `NULL`.
#'
#' If all `object`, `class` and `group` are `NULL`, duplication checking
#' is performed on the whole `Idf`.
#'
#' @return The modified `Idf` object itself, invisibly.
#'
#' @examples
#' \dontrun{
#' # remove duplications in the Idf
#' idf$unique(class = "ScheduleTypeLimits")
#'
#' # remove duplications in the schedule types
#' idf$unique(class = "ScheduleTypeLimits")
#'
#' # remove duplications in the schedule groups and material class
#' idf$unique(class = "Material", group = "Schedules")
#' }
#'
unique = function(object = NULL, class = NULL, group = NULL)
idf_unique(self, private, object = object, class = class, group = group),
# }}}
# rename {{{
#' @description
#' Rename existing objects
#'
#' @details
#' `$rename()` takes named character vectors of object names and named
#' integer vectors of object IDs, renames specified objects to names of
#' input vectors and returns a list of renamed [IdfObject]s. The
#' returned list will be named using names of modified objects. An error
#' will be issued if trying to "rename" an object which does not have
#' name attribute. When renaming an object that is referred by other
#' objects, corresponding fields that refer to that object's name will
#' also be changed accordingly.
#'
#' @param ... Integer vectors of valid object IDs and character vectors
#' of valid object names in current `Idf` object. Each element
#' should be named. Names of input vectors are used as the new
#' object names
#'
#' @return A named list of renamed [IdfObject] objects.
#'
#' @examples
#' \dontrun{
#' idf$objects(c("on/off", "test 352a"))
#' idf$rename(on_off = "on/off", test_352a = 51)
#' }
#'
rename = function(...)
idf_rename(self, private, ...),
# }}}
# insert {{{
#' @description
#' Insert new objects from [IdfObject]s
#'
#' @details
#' `$insert()` takes [IdfObject]s or lists of [IdfObject]s as input,
#' inserts them into current `Idf` objects, and returns a list of
#' inserted [IdfObject]s. The returned list will be named using names of
#' inserted objects.
#'
#' `$insert()` is quite useful to insert objects from other `Idf`
#' objects. However, you cannot insert an [IdfObject] which comes from a
#' different version than current `Idf` object.
#'
#' `$insert()` will skip [IdfObject]s that have exactly same fields in
#' current `Idf` object. If input [IdfObject] has the same name as one
#' [IdfObject] in current `Idf` object but field values are not equal,
#' an error will be issued if current [validate level][level_checks()]
#' includes conflicted-name checking.
#'
#' By default, trailing empty fields that are not required will be
#' removed and only minimum required fields are kept. You can keep the
#' trailing empty fields by setting `.empty` to `TRUE`.
#'
#' @param ... [IdfObject]s or lists of [IdfObject]s from same version as
#' current `Idf` object.
#' @param .unique If there are duplications in input [IdfObject]s or
#' there is same object in current `Idf` object, duplications in
#' input are removed. Default: `TRUE`.
#' @param .empty If `TRUE`, trailing empty fields are kept. Default:
#' `FALSE`.
#'
#' @return A named list of inserted [IdfObject] objects.
#'
#' @examples
#' \dontrun{
#' # insert all material from another IDF
#' path_idf2 <- file.path(eplus_config(8.8)$dir, "ExampleFiles/5ZoneTDV.idf")
#' idf2 <- Idf$new(path_idf2)
#' idf$insert(idf2$Material)
#'
#' # insert objects from same Idf is equivalent to using Idf$dup()
#' idf$insert(idf$SizingPeriod_DesignDay)
#' }
#'
insert = function(..., .unique = TRUE, .empty = FALSE)
idf_insert(self, private, ..., .unique = .unique, .empty = .empty),
# }}}
# load {{{
#' @description
#' Load new objects from characters or data.frames
#'
#' @details
#' `$load()` is similar to
#' \href{../../eplusr/html/Idf.html#method-insert}{\code{$insert()}},
#' except it takes directly character vectors or data.frames as
#' [IdfObject] definitions, insert corresponding objects into current
#' `Idf` object and returns a named list of newly added [IdfObject]s.
#' The returned list will be named using names of added objects. This
#' makes it easy to create objects using the output from`$to_string()`
#' and `$to_table()` method from
#' \href{../../eplusr/html/Idd.html#method-to_string}{\code{Idd}},
#' \href{../../eplusr/html/IddObject.html#method-to_string}{\code{IddObject}},
#' also from
#' \href{../../eplusr/html/Idf.html#method-to_string}{\code{Idf}},
#' and
#' \href{../../eplusr/html/IdfObject.html#method-to_string}{\code{IdfObject}},
#' class.
#'
#' For object definitions in character vector format, they follow the
#' same rules as a normal IDF file:
#'
#' * Each object starts with a class name and a comma (`,`);
#' * Separates each values with a comma (`,`);
#' * Ends an object with a semicolon (`;`) for the last value.
#'
#' Each character vector can contain:
#'
#' * One single object, e.g. `c("Building,", "MyBuilding;")`, or
#' "Building, MyBuilding;".
#' * Multiple objects, e.g. `c("Building, MyBuilding;",
#' "SimulationControl, Yes")`.
#'
#' You can also provide an option header to indicate if input objects
#' are presented in IP units, using `!-Option ViewInIPunits`. If this
#' header does not exist, then all values are treated as in SI units.
#'
#' For object definitions in data.frame format, it is highly recommended
#' to use `$to_table()` method in
#' \href{../../eplusr/html/Idd.html#method-to_table}{\code{Idd}},
#' [Idd],
#' \href{../../eplusr/html/IddObject.html#method-to_table}{\code{IddObject}},
#' [IddObject],
#' \href{../../eplusr/html/Idf.html#method-to_table}{\code{Idf}},
#' and
#' \href{../../eplusr/html/IdfObject.html#method-to_table}{\code{IdfObject}},
#' class to create an acceptable data.frame template. A
#' valid definition requires at least three columns described below.
#' Note that column order does not matter.
#'
#' * `class`:Character type. Valid class names in the underlying
#' [Idd] object.
#' * `index`:Integer type. Valid field indices for each class.
#' * `value`:Character type or list type. Value for each field
#' to be added.
#' - If character type, usually when `string_value` is `TRUE`
#' in method `$to_table()` in
#' \href{../../eplusr/html/Idf.html#method-to_table}{\code{Idf}}
#' and
#' \href{../../eplusr/html/IdfObject.html#method-to_table}{\code{IdfObject}}
#' class. Note that
#' each value should be given as a string even if the corresponding
#' field is a numeric type.
#' - If list type, usually when `string_value` is set to
#' `FALSE` in method`$to_table()` in
#' \href{../../eplusr/html/Idf.html#method-to_table}{\code{Idf}}
#' and
#' \href{../../eplusr/html/IdfObject.html#method-to_table}{\code{IdfObject}}
#' class.
#' Each value should have the right type as the corresponding field
#' definition. Otherwise, errors will be issued if current
#' [validation level][level_checks()] includes invalid-type checking.
#' * `id`: **Optional**. Integer type. If input data.frame includes
#' multiple object definitions in a same class, values in `id` column
#' will be used to distinguish each definition. If `id` column does
#' not exists, it assumes that each definition is separated by `class`
#' column and will issue an error if there is any duplication in the
#' `index` column.
#'
#' Note that `$load()` assumes all definitions are from the same version
#' as current `Idf` object. If input definition is from different
#' version, parsing error may occur.
#'
#' By default, trailing empty fields that are not required will be
#' removed and only minimum required fields are kept. You can keep the
#' trailing empty fields by setting `.empty` to `TRUE`.
#'
#' @param ... Character vectors or data.frames of object definitions.
#' @param .unique If `TRUE`, and there are duplications in input
#' [IdfObject]s or there is same object in current `Idf` object,
#' duplications in input are removed. Default: `TRUE`.
#' @param .default If `TRUE`, default values are filled for those blank
#' fields if possible. Default: `TRUE`.
#' @param .empty If `TRUE`, trailing empty fields are kept. Default:
#' `FALSE`.
#'
#' @return A named list of loaded [IdfObject] objects.
#'
#' @examples
#' \dontrun{
#' # load objects from character vectors
#' idf$load(
#' c("Material,",
#' " mat, !- Name",
#' " MediumSmooth, !- Roughness",
#' " 0.667, !- Thickness {m}",
#' " 0.115, !- Conductivity {W/m-K}",
#' " 513, !- Density {kg/m3}",
#' " 1381; !- Specific Heat {J/kg-K}"),
#'
#' "Construction, const, mat;"
#' )
#'
#' # load objects from data.frame definitions
#' dt <- idf$to_table(class = "Material")
#' dt[field == "Name", value := paste(value, 1)]
#' dt[field == "Thickness", value := "0.5"]
#' idf$load(dt)
#'
#' # by default, duplications are removed
#' idf$load(idf$to_table(class = "Material"))
#'
#' # keep empty fields as they are
#' idf$load("Material, mat1, smooth, 0.5, 0.2, 500, 1000,,, 0.5;", .default = FALSE)
#'
#' # keep trailing empty fields
#' idf$load("Material, mat2, smooth, 0.5, 0.2, 500, 1000,,,;",
#' .default = FALSE, .empty = TRUE
#' )
#' }
#'
load = function(..., .unique = TRUE, .default = TRUE, .empty = FALSE)
idf_load(self, private, ..., .unique = .unique, .default = .default, .empty = .empty),
# }}}
# update {{{
#' @description
#' Update existing object values from characters or data.frames
#'
#' @details
#' `$update()` is similar to
#' \href{../../eplusr/html/Idf.html#method-set}{\code{$set()}}, except
#' it takes directly character vectors or data.frames as [IdfObject]
#' definitions, updates new values for fields in objects specified, and
#' returns a named list of modified [IdfObject]s. The returned list will
#' be named using names of modified objects. This makes it easy to
#' update object values using the output from `$to_string()` and
#' `$to_table` method from
#' \href{../../eplusr/html/Idf.html#method-to_string}{\code{Idf}},
#' and
#' \href{../../eplusr/html/IdfObject.html#method-to_string}{\code{IdfObject}},
#' class.
#'
#' The format of object definitions is similar to `$load()`.
#'
#' For object definitions in character vector format, object names are
#' used to locate which objects to update. Objects that have name
#' attribute should have valid names. This means that there is no way to
#' update object names using character vector format, but this can be
#' achieved using data.frame format as it uses object IDs instead of
#' object names to locate objects. The format of acceptable characters
#' follows the same rules as a normal IDF file:
#'
#' * Each object starts with a class name and a comma (`,`);
#' * Separates each values with a comma (`,`);
#' * Ends an object with a semicolon (`;`) for the last value.
#'
#' Each character vector can contain:
#'
#' * One single object, e.g. `c("Building,", "MyBuilding;")`, or
#' "Building, MyBuilding;".
#' * Multiple objects, e.g. `c("Building, MyBuilding;",
#' "SimulationControl, Yes")`.
#'
#' You can also provide an option header to indicate if input objects
#' are presented in IP units, using `!-Option ViewInIPunits`. If this
#' header does not exist, then all values are treated as in SI units.
#'
#' For object definitions in data.frame format, it is highly recommended
#' to use `$to_table()` method in
#' \href{../../eplusr/html/Idf.html#method-to_table}{\code{Idf}},
#' and
#' \href{../../eplusr/html/IdfObject.html#method-to_table}{\code{IdfObject}},
#' class to create an acceptable data.frame template. A valid definition
#' requires three columns described below. Note that column order does
#' not matter.
#'
#' * `id`: Integer type. Valid IDs of objects to update.
#' * `index`:Integer type. Valid field indices for each object.
#' * `value`:Character type or list type. Value for each field
#' to be added.
#' - If character type, usually when `string_value` is `TRUE`
#' in method `$to_table()` in
#' \href{../../eplusr/html/Idf.html#method-to_table}{\code{Idf}}
#' and
#' \href{../../eplusr/html/IdfObject.html#method-to_table}{\code{IdfObject}}
#' class. Note that
#' each value should be given as a string even if the corresponding
#' field is a numeric type.
#' - If list type, usually when `string_value` is set to
#' `FALSE` in method `$to_table()` in
#' \href{../../eplusr/html/Idf.html#method-to_table}{\code{Idf}}
#' and
#' \href{../../eplusr/html/IdfObject.html#method-to_table}{\code{IdfObject}}
#' class.
#' Each value should have the right type as the corresponding field
#' definition. Otherwise, errors will be issued if current
#' [validation level][level_checks()] includes invalid-type checking.
#'
#' Note that `$update()` assumes all definitions are from the same version
#' as current `Idf` object. If input definition is from different
#' version, parsing error may occur.
#'
#' By default, trailing empty fields that are not required will be
#' removed and only minimum required fields are kept. You can keep the
#' trailing empty fields by setting `.empty` to `TRUE`.
#'
#' @param ... Character vectors or data.frames of object definitions.
#' @param .default If `TRUE`, default values are filled for those blank
#' fields if possible. Default: `TRUE`.
#' @param .empty If `TRUE`, trailing empty fields are kept. Default:
#' `FALSE`.
#'
#' @return A named list of updated [IdfObject] objects.
#'
#' @examples
#' \dontrun{
#' # update objects from string definitions:
#' str <- idf$to_string("zone one", header = FALSE, format = "new_top")
#' str[8] <- "2," # Multiplier
#' idf$update(str)
#'
#' # update objects from data.frame definitions:
#' dt <- idf$to_table("zone one")
#' dt[field == "Multiplier", value := "1"]
#' idf$update(dt)
#' }
#'
update = function(..., .default = TRUE, .empty = FALSE)
idf_update(self, private, ..., .default = .default, .empty = .empty),
# }}}
# paste {{{
#' @description
#' Paste new objects from IDF Editor
#'
#' @details
#' `$paste()` reads the contents (from clipboard) of copied objects from IDF
#' Editor (after hitting `Copy Obj` button), inserts corresponding
#' objects into current `Idf` object and returns a named list of newly
#' added [IdfObject]s. The returned list will be named using names of
#' added objects. As IDF Editor is only available on Windows platform,
#' `$paste()` only works on Windows too.
#'
#' There is no version data copied to the clipboard when copying objects in
#' IDF Editor. `$paste()` assumes the file open in IDF Editor has the
#' same version as current `Idf` object. This may not be always true.
#' Please check the version before running `$paste()`, or explicitly
#' specify the version of file opened by IDF Editor using `ver`
#' parameter. Parsing error may occur if there is a version mismatch.
#'
#' By default, trailing empty fields that are not required will be
#' removed and only minimum required fields are kept. You can keep the
#' trailing empty fields by setting `.empty` to `TRUE`.
#'
#' @param in_ip Set to `TRUE` if the IDF file is open with `Inch-Pound`
#' view option toggled. Numeric values will automatically
#' converted to SI units if necessary. Default: `FALSE`.
#' @param ver The version of IDF file open by IDF Editor, e.g. `8.6`,
#' `"8.8.0"`. If `NULL`, assume that the file has the same
#' version as current Idf object. Default: `NULL`.
#' @param unique If `TRUE`, and there are duplications in copied objects
#' from IDF Editor or there is same object in current Idf,
#' duplications in input are removed. Default: `TRUE`.
#' @param empty If `TRUE`, trailing empty fields are kept. Default:
#' `FALSE`.
#'
#' @return A named list of loaded [IdfObject] objects.
#'
paste = function(in_ip = FALSE, ver = NULL, unique = TRUE, empty = FALSE)
idf_paste(self, private, in_ip = in_ip, ver = ver, unique = unique, empty = empty),
# }}}
# search_value {{{
#' @description
#' Search objects by field values using regular expression
#'
#' @details
#' `$search_value()` returns a list of [IdfObject]s that contain values
#' which match the given pattern. If no matched found, `NULL` is
#' returned invisibly. The returned list will be named using names of
#' matched objects.
#'
#' Note that during matching, all values are treated as characters,
#' including numeric values.
#'
#' @param pattern,ignore.case,perl,fixed,useBytes All of them are
#' directly passed to [base::grepl][base::grep()] and
#' [base::gsub][base::grep()].
#' @param class A character vector of invalid class names in current
#' `Idf` object to search for values. If `NULL`, all classes are
#' used. Default: `NULL`.
#'
#' @return A named list of [IdfObject] objects.
#'
#' @examples
#' \dontrun{
#' # search values that contains "floor"
#' idf$search_value("floor", ignore.case = TRUE)
#'
#' # search values that contains "floor" in class Construction
#' idf$search_value("floor", "Construction", ignore.case = TRUE)
#' }
#'
search_value = function(pattern, class = NULL, ignore.case = FALSE,
perl = FALSE, fixed = FALSE, useBytes = FALSE)
idf_search_value(self, private, pattern, class, ignore.case = ignore.case,
perl = perl, fixed = fixed, useBytes = useBytes
),
# }}}
# replace_value {{{
#' @description
#' Replace object field values using regular expression
#'
#' @details
#' `$replace_value()` returns a list of [IdfObject]s whose values have
#' been replaced using given pattern. If no matched found, `NULL` is
#' returned invisibly. The returned list will be named using names of
#' matched objects.
#'
#' Note that during matching, all values are treated as characters,
#' including numeric values.
#'
#' Modifying object values using regular expression is not recommended.
#' Consider to use
#' \href{../../eplusr/html/Idf.html#method-set}{\code{$set()}}
#' and
#' \href{../../eplusr/html/Idf.html#method-update}{\code{$update()}}
#' if possible.
#' [Validation][level_checks()] rules also apply during replacing.
#'
#' @param pattern,replacement,ignore.case,perl,fixed,useBytes All of
#' them are directly passed to [base::grepl][base::grep()] and
#' [base::gsub][base::grep()].
#' @param class A character vector of invalid class names in current
#' `Idf` object to search for values. If `NULL`, all classes are
#' used. Default: `NULL`.
#'
#' @return A named list of [IdfObject] objects.
#'
#' @examples
#' \dontrun{
#' # search values that contains "win" and replace them with "windows"
#' idf$replace_value("win", "windows")
#' }
#'
replace_value = function(pattern, replacement, class = NULL, ignore.case = FALSE,
perl = FALSE, fixed = FALSE, useBytes = FALSE)
idf_replace_value(self, private, pattern, replacement, class = class,
ignore.case = ignore.case, perl = perl, fixed = fixed, useBytes = useBytes
),
# }}}
# validate {{{
#' @description
#' Check possible object field value errors
#'
#' @details
#' `$validate()` checks if there are errors in current `Idf` object
#' under specified validation level and returns an `IdfValidity` object.
#' `$validate()` is useful to help avoid some common errors before
#' running the model. By default, validation is performed when calling
#' all methods that modify objects, e.g.
#' \href{../../eplusr/html/Idf.html#method-dup}{\code{$dup()}}
#' \href{../../eplusr/html/Idf.html#method-add}{\code{$add()}},
#' \href{../../eplusr/html/Idf.html#method-set}{\code{$set()}},
#' \href{../../eplusr/html/Idf.html#method-del}{\code{$del()}},
#' and etc.
#'
#' In total, there are 10 different validate checking components:
#'
#' * `required_object`: Check if required objects are missing in current
#' `Idf`.
#' * `unique_object`: Check if there are multiple objects in one
#' unique-object class. An unique-object class means that there should
#' be at most only one object existing in that class.
#' * `unique_name`: Check if all objects in each class have unique names.
#' * `extensible`: Check if all fields in an extensible group have
#' values. An extensible group is a set of fields that should be
#' treated as a whole, such like the X, Y and Z vertices of a building
#' surfaces. An extensible group should be added or deleted together.
#' `extensible` component checks if there are some, but not all,
#' fields in an extensible group are empty.
#' * `required_field`: Check if all required fields have values.
#' * `auto_field`: Check if all fields filled with value `"Autosize"` and
#' `"Autocalculate"` are actual autosizable and autocalculatable
#' fields or not.
#' * `type`: Check if all fields have value types complied with their
#' definitions, i.e. character, numeric and integer fields should be
#' filled with corresponding type of values.
#' * `choice`: Check if all choice fields are filled with valid choice
#' values.
#' * `range`: Check if all numeric fields have values within prescibed
#' ranges.
#' * `reference`: Check if all fields whose values refer to other fields
#' are valid.
#'
#' The `level` argument controls what checkings should be performed.
#' `level` here is just a list of 10 element which specify the toggle
#' status of each component. You can use helper [custom_validate()] to
#' get that list and pass it directly to `level`.
#'
#' There are 3 predefined validate level that indicates different
#' combinations of checking components, i.e. `none`, `draft` and
#' `final`. Basically, `none` level just does not perform any
#' checkings; `draft` includes 5 components, i.e. `auto_field`, `type`,
#' `unique_name`, `choice` and `range`; and `final` level includes all
#' 10 components. You can always get what components each level contains
#' using [level_checks()]. By default, the result from
#' `eplusr_option("validate_level")` is passed to `level`. If not set,
#' `final` level is used.
#'
#' Underneath, an `IdfValidity` object which `$validate()` returns is a
#' list of 13 element as shown below. Each element or several elements
#' represents the results from a single validation checking component.
#'
#' * `missing_object`: Result of `required_object` checking.
#' * `duplicate_object`: Result of `unique_object` checking.
#' * `conflict_name`: Result of `unique_name` checking.
#' * `incomplete_extensible`: Result of `extensible` checking.
#' * `missing_value`: Result of `required_field` checking.
#' * `invalid_autosize`: Result of `auto_field` checking for invalid
#' `Autosize` field values.
#' * `invalid_autocalculate`: Result of `auto_field` checking for
#' invalid `Autocalculate` field values.
#' * `invalid_character`: Result of `type` checking for invalid
#' character field values.
#' * `invalid_numeric`: Result of `type` checking for invalid
#' numeric field values.
#' * `invalid_integer`: Result of `type` checking for invalid
#' integer field values.
#' * `invalid_choice`: Result of `choice` checking.
#' * `invalid_range`: Result of `range` checking.
#' * `invalid_reference`: Result of `reference` checking.
#'
#' Except `missing_object`, which is a character vector of class names
#' that are missing, all other elements are
#' [data.table][data.table::data.table()] with 9 columns containing data
#' of invalid field values:
#'
#' * `object_id`: IDs of objects that contain invalid values
#' * `object_name`: names of objects that contain invalid values
#' * `class_id`: indexes of classes that invalid objects belong to
#' * `class_name`: names of classes that invalid objects belong to
#' * `field_id`: indexes (at Idd level) of object fields that are invalid
#' * `field_index`: indexes of object fields in corresponding that are invalid
#' * `field_name`: names (without units) of object fields that are invalid
#' * `units`: SI units of object fields that are invalid
#' * `ip_units`: IP units of object fields that are invalid
#' * `type_enum`: An integer vector indicates types of invalid fields
#' * `value_id`: indexes (at Idf level) of object field values that are invalid
#' * `value_chr`: values (converted to characters) of object fields that are
#' invalid
#' * `value_num`: values (converted to numbers in SI units) of object fields
#' that are invalid
#'
#' Knowing the internal structure of `IdfValidity`, it is easy to extract
#' invalid [IdfObject]s you interested in. For example, you can get all IDs of
#' objects that contain invalid value references using
#' `model$validate()$invalid_reference$object_id`. Then using
#' \href{../../eplusr/html/Idf.html#method-set}{\code{$set()}}
#' method to correct them.
#'
#' Different validate result examples are shown below:
#'
#' * No error is found:
#'
#' ```
#' v No error found.
#' ```
#'
#' Above result shows that there is no error found after conducting all
#' validate checks in specified validate level.
#'
#' * Errors are found:
#'
#' ```
#' x [2] Errors found during validation.
#' =========================================================================
#'
#' -- [2] Invalid Autocalculate Field --------------------------------------
#' Fields below cannot be `autocalculate`:
#'
#' Class: <AirTerminal:SingleDuct:VAV:Reheat>
#' \- Object [ID:176] <SPACE5-1 VAV Reheat>
#' +- 17: AUTOCALCULATE, !- Maximum Flow per Zone Floor Area During Reheat {m3/s-m2}
#' \- 18: AUTOCALCULATE; !- Maximum Flow Fraction During Reheat
#' ```
#'
#' Above result shows that after all validate components
#' performed under current validate level, 2 invalid field values
#' are found. All of them are in a object named `SPACE5-1 VAV Reheat`
#' with ID `176`. They are invalid because those two fields do not
#' have an autocalculatable attribute but are given `AUTOCALCULATE`
#' value. Knowing this info, one simple way to fix the
#' error is to correct those two fields by doing:
#'
#' ```
#' idf$set(..176 =
#' list(`Maximum Flow per Zone Floor Area During Reheat` = "autosize",
#' `Maximum Flow Fraction During Reheat` = "autosize"
#' )
#' )
#' ```
#'
#' @param level One of `"none"`, `"draft"`, `"final"` or a list of 10
#' elements with same format as [custom_validate()] output.
#'
#' @return An `IdfValidity` object.
#'
#' @examples
#' \dontrun{
#' idf$validate()
#'
#' # check at predefined validate level
#' idf$validate("none")
#' idf$validate("draft")
#' idf$validate("final")
#'
#' # custom validate checking components
#' idf$validate(custom_validate(auto_field = TRUE, choice = TRUE))
#' }
#'
validate = function(level = eplusr_option("validate_level"))
idf_validate(self, private, level),
# }}}
# is_valid {{{
#' @description
#' Check if there is any error in current `Idf`
#'
#' @details
#' `$is_valid()` checks if there are errors in current `Idf` object
#' under specified validation level and returns `TRUE` or `FALSE`
#' accordingly. For detailed description on validate checking, see
#' \href{../../eplusr/html/Idf.html#method-validate}{\code{$validate()}}
#' documentation above.
#'
#' @param level One of `"none"`, `"draft"`, `"final"` or a list of 10
#' elements with same format as [custom_validate()] output.
#'
#' @return A single logical value of `TRUE` or `FALSE`.
#'
#' @examples
#' \dontrun{
#' idf$is_valid()
#'
#' # check at predefined validate level
#' idf$is_valid("none")
#' idf$is_valid("draft")
#' idf$is_valid("final")
#'
#' # custom validate checking components
#' idf$is_valid(custom_validate(auto_field = TRUE, choice = TRUE))
#' }
#'
is_valid = function(level = eplusr_option("validate_level"))
idf_is_valid(self, private, level),
# }}}
# to_string {{{
#' @description
#' Format `Idf` as a character vector
#'
#' @details
#' `$to_string()` returns the text format of parts or whole `Idf`
#' object.
#'
#' @param which Either an integer vector of valid object IDs or a
#' character vector of valid object names. If `NULL`, the whole
#' `Idf` object is converted. Default: `NULL`.
#' @param class A character vector of class names. If `NULL`, all
#' classed in current `Idf` object is converted. Default: `NULL`.
#' @param comment If `FALSE`, all comments will not be included.
#' Default: `TRUE`.
#' @param header If `FALSE`, the header will not be included. Default:
#' `TRUE`.
#' @param format Specific format used when formatting. Should be one of
#' `"asis"`, `"sorted"`, `"new_top"`, and `"new_bot"`.
#' * If `"asis"`, `Idf` object will be formatted in the same way as it
#' was when first read. If `Idf` object does not contain any format
#' saving option, which is typically the case when the model was not
#' saved using eplusr or IDFEditor, `"sorted"` will be used.
#' * `"sorted"`, `"new_top"` and `"new_bot"` are the same as the save
#' options `"Sorted"`, `"Original with New at Top"`, and `"Original
#' with New at Bottom"` in IDFEditor. Default:
#' `eplusr_option("save_format")`.
#' @param leading Leading spaces added to each field. Default: `4L`.
#' @param sep_at The character width to separate value string and field
#' string. Default: `29L` which is the same as IDF Editor.
#'
#' @return A character vector.
#'
#' @examples
#' \dontrun{
#' # get text format of the whole Idf
#' head(idf$to_string())
#'
#' # get text format of the whole Idf, excluding the header and all comments
#' head(idf$to_string(comment = FALSE, header = FALSE))
#'
#' # get text format of all objects in class Material
#' head(idf$to_string(class = "Material", comment = FALSE, header = FALSE))
#'
#' # get text format of some objects
#' head(idf$to_string(c("floor", "zone one")))
#'
#' # tweak output formatting
#' head(idf$to_string("floor", leading = 0, sep_at = 0))
#' }
#'
to_string = function(which = NULL, class = NULL, comment = TRUE,
header = TRUE, format = eplusr_option("save_format"),
leading = 4L, sep_at = 29L)
idf_to_string(self, private, which, class, comment = comment,
header = header, format = format,
leading = leading, sep_at = sep_at),
# }}}
# to_table {{{
#' @description
#' Format `Idf` as a data.frame
#'
#' @details
#' `$to_table()` returns a [data.table][data.table::data.table()] that
#' contains core data of specified objects.
#' The returned [data.table][data.table::data.table()] has 5 columns:
#'
#' * `id`: Integer type. Object IDs.
#' * `name`: Character type. Object names.
#' * `class`: Character type. Current class name.
#' * `index`: Integer type. Field indexes.
#' * `field`: Character type. Field names.
#' * `value`: Character type if `string_value` is `TRUE` or list type if
#' `string_value` is `FALSE` or `group_ext` is not `"none"`. Field values.
#'
#' Note that when `group_ext` is not `"none"`, `index` and `field`
#' values will not match the original field indices and names. In this
#' case, `index` will only indicate the indices of sequences. For
#' `field` column, specifically:
#'
#' * When `group_ext` is `"group"`, each field name in a extensible group
#' will be abbreviated using [abbreviate()] with `minlength` being
#' `10L` and all abbreviated names will be separated by `|` and
#' combined together. For example, field names in the extensible group
#' (`Vertex 1 X-coordinate`, `Vertex 1 Y-coordinate`, `Vertex 1
#' Z-coordinate`) in class `BuildiBuildingSurface:Detailed` will be
#' merged into one name `Vrtx1X-crd|Vrtx1Y-crd|Vrtx1Z-crd`.
#' * When `group_ext` is `"index"`, the extensible group indicator in field
#' names will be removed. Take the same example as above, the
#' resulting field names will be `Vertex X-coordinate`, `Vertex
#' Y-coordinate`, and `Vertex Z-coordinate`.
#'
#' @param which Either an integer vector of valid object IDs or a
#' character vector of valid object names. If `NULL`, the whole
#' `Idf` object is converted. Default: `NULL`.
#' @param class A character vector of class names. If `NULL`, all
#' classed in current `Idf` object is converted. Default: `NULL`.
#' @param string_value If `TRUE`, all field values are returned as
#' character. If `FALSE`, `value` column in returned
#' [data.table][data.table::data.table()] is a list column with
#' each value stored as corresponding type. Note that if the
#' value of numeric field is set to `"Autosize"` or
#' `"Autocalculate"`, it is left as it is, leaving the returned
#' type being a string instead of a number. Default: `TRUE`.
#' @param unit Only applicable when `string_value` is `FALSE`. If
#' `TRUE`, values of numeric fields are assigned with units using
#' [units::set_units()] if applicable. Default: `FALSE`.
#' @param wide Only applicable if target objects belong to a same class.
#' If `TRUE`, a wide table will be returned, i.e. first three
#' columns are always `id`, `name` and `class`, and then every
#' field in a separate column. Note that this requires all
#' objects specified must from the same class.
#' Default: `FALSE`.
#' @param align If `TRUE`, all objects in the same class will have the
#' same field number. The number of fields is the same as the
#' object that have the most fields among objects specified.
#' Default: `FALSE`.
#' @param all If `TRUE`, all available fields defined in IDD for the
#' class that objects belong to will be returned. Default:
#' `FALSE`.
#' @param group_ext Should be one of `"none"`, `"group"` or `"index"`.
#' If not `"none"`, `value` column in returned
#' [data.table::data.table()] will be converted into a list.
#' If `"group"`, values from extensible fields will be grouped by the
#' extensible group they belong to. For example, coordinate
#' values of each vertex in class `BuildingSurface:Detailed` will
#' be put into a list. If `"index"`, values from extensible fields
#' will be grouped by the extensible field indice they belong to.
#' For example, coordinate values of all x coordinates will be
#' put into a list. If `"none"`, nothing special will be done.
#' Default: `"none"`.
#' @param force If `TRUE`, `wide` can be `TRUE` even though there are
#' multiple classes in input. This can result in a data.table
#' with lots of columns. But may be useful when you know that
#' target classes have the exact same fields, e.g.
#' `Ceiling:Adiabatic` and `Floor:Adiabatic`. Default: `FALSE`.
#' @param init If `TRUE`, a table for new object input will be returned
#' with all values filled with defaults. In this case, `object`
#' input will be ignored. The `id` column will be filled with
#' possible new object IDs. Default: `FALSE`.
#'
#' @return A [data.table][data.table::data.table()] with 6 columns (if
#' `wide` is `FALSE`) or at least 6 columns (if `wide` is `TRUE`).
#'
#' @examples
#' \dontrun{
#' # extract whole Idf data
#' idf$to_table()
#'
#' # extract all data from class Material
#' idf$to_table(class = "Material")
#'
#' # extract multiple object data
#' idf$to_table(c("FLOOR", "ZONE ONE"))
#'
#' # keep value types and put actual values into a list column
#' idf$to_table(c("FLOOR", "ZONE ONE"), string_value = FALSE)$value
#'
#' # add the unit to each value
#' idf$to_table(c("FLOOR", "ZONE ONE"), string_value = FALSE, unit = TRUE)
#'
#' # get all possible fields
#' idf$to_table("ZONE ONE", all = TRUE)
#'
#' # make sure all objects in same class have the same number of fields
#' idf$to_table(class = "Construction", align = TRUE)
#'
#' # get a wide table with string values
#' idf$to_table(class = "Construction", wide = TRUE)
#'
#' # get a wide table with actual values
#' idf$to_table(class = "OtherEquipment", wide = TRUE, string_value = FALSE)
#'
#' # group extensible by extensible group number
#' idf$to_table(class = "BuildingSurface:Detailed", group_ext = "group")
#'
#' # group extensible by extensible group number and convert into a wide table
#' idf$to_table(class = "BuildingSurface:Detailed", group_ext = "group", wide = TRUE)
#'
#' # group extensible by extensible field index
#' idf$to_table(class = "BuildingSurface:Detailed", group_ext = "index")
#'
#' # group extensible by extensible field index and convert into a wide table
#' idf$to_table(class = "BuildingSurface:Detailed", group_ext = "index", wide = TRUE)
#'
#' # when grouping extensible, 'string_value' and 'unit' still take effect
#' idf$to_table(class = "BuildingSurface:Detailed", group_ext = "index",
#' wide = TRUE, string_value = FALSE, unit = TRUE
#' )
#'
#' # create table for new object input
#' idf$to_table(class = "BuildingSurface:Detailed", init = TRUE)
#' }
#'
to_table = function(which = NULL, class = NULL, string_value = TRUE,
unit = FALSE, wide = FALSE, align = FALSE, all = FALSE,
group_ext = c("none", "group", "index"), force = FALSE,
init = FALSE)
idf_to_table(self, private, which = which, class = class,
string_value = string_value, unit = unit, wide = wide, align = align,
all = all, group_ext = match.arg(group_ext), force = force, init = init),
# }}}
# external_deps {{{
#' @description
#' Get external file dependencies that the Idf needs for simulation.
#'
#' @details
#' `$external_deps()` returns information of files that are used as
#' external resources for the simulation.
#'
#' Currently, classes below are checked:
#'
#' * `Schedule:File:Shading`
#' * `Schedule:File`
#' * `Construction:WindowDataFile`
#' * `ExternalInterface:FunctionalMockupUnitImport`
#' * `ExternalInterface:FunctionalMockupUnitImport:From:Variable`
#' * `ExternalInterface:FunctionalMockupUnitImport:To:Schedule`
#' * `ExternalInterface:FunctionalMockupUnitImport:To:Actuator`
#' * `ExternalInterface:FunctionalMockupUnitImport:To:Variable`
#' * `Table:IndependentVariable`
#' * `Table:Lookup`
#'
#' Note that, for `ExternalInterface:FunctionalMockupUnitImport` and
#' `ExternalInterface:FunctionalMockupUnitImport:*`, resources of FMU
#' will also be extracted.
#'
#' @param full If `TRUE`, a [data.table][data.table::data.table()] is
#' returned giving details about the objects and fields that use those
#' external file dependencies. Default: `FALSE`.
#'
#' @return
#' When `full` is `FALSE`, which is the default, a character vector.
#'
#' When `full` is `TRUE`, a [data.table][data.table::data.table()] of 8
#' columns:
#'
#' * `id`: Integer type. Object IDs.
#' * `name`: Character type. Object names.
#' * `class`: Character type. Current class name.
#' * `index`: Integer type. Field indexes.
#' * `field`: Character type. Field names.
#' * `value`: Character type. Field values.
#' * `path`: Character type. Full file paths.
#' * `exist`: Logical type. `TRUE` if file exists, `FALSE` otherwise.
#'
#' If there are any FMUs using external file resources, the returned
#' data.table will have an attribute named `extra` which is a list
#' giving the FMU name and external file resources it use.
#'
#' @examples
#' \dontrun{
#' idf$external_deps()
#' }
#'
external_deps = function(full = FALSE)
idf_external_deps(self, private, full),
# }}}
# is_unsaved {{{
#' @description
#' Check if there are unsaved changes in current `Idf`
#'
#' @details
#' `$is_unsaved()` returns `TRUE` if there are modifications on the
#' model since it was read or since last time it was saved, and returns
#' `FALSE` otherwise.
#'
#' @return A single logical value of `TRUE` or `FALSE`.
#'
#' @examples
#' \dontrun{
#' idf$is_unsaved()
#' }
#'
is_unsaved = function()
idf_is_unsaved(self, private),
# }}}
# save {{{
#' @description
#' Save `Idf` object as an IDF file
#'
#' @details
#' `$save()` formats current `Idf` object, saves it as an IDF file and
#' returns the path of saved file invisibly. After saving,
#' \href{../../eplusr/html/Idf.html#method-path}{\code{$path()}}
#' will also be updated to return the path of saved file.
#'
#' @param path A path where to save the IDF file. If `NULL`, the path of
#' the `Idf` itself, i.e.
#' \href{../../eplusr/html/Idf.html#method-path}{\code{$path()}},
#' will be used.
#' @param format Specific format used when formatting. Should be one of
#' `"asis"`, `"sorted"`, `"new_top"`, and `"new_bot"`.
#' * If `"asis"`, `Idf` object will be formatted in the same way as it
#' was when first read. If `Idf` object does not contain any format
#' saving option, which is typically the case when the model was not
#' saved using eplusr or IDFEditor, `"sorted"` will be used.
#' * `"sorted"`, `"new_top"` and `"new_bot"` are the same as the save
#' options `"Sorted"`, `"Original with New at Top"`, and `"Original
#' with New at Bottom"` in IDFEditor. Default:
#' `eplusr_option("save_format")`.
#' @param overwrite Whether to overwrite the file if it already exists.
#' Default: `FALSE`.
#' @param copy_external If `TRUE`, the external files extracted from
#' `$external_deps()` will also be copied into the same directory.
#' The values of file paths in the `Idf` will be changed into
#' relative path automatically. This makes it possible to create
#' fully reproducible simulation conditions. If `FALSE`, the
#' values of those fields that reference external file paths will
#' be updated to absolute paths. Default: `FALSE`.
#'
#' @return A length-one character vector, invisibly.
#'
#' @examples
#' \dontrun{
#' # save Idf as a new file
#' idf$save(tempfile(fileext = ".idf"))
#'
#' # save and overwrite current file
#' idf$save(overwrite = TRUE)
#'
#' # save the model with newly created and modified objects at the top
#' idf$save(overwrite = TRUE, format = "new_top")
#'
#' # save the model to a new file and copy all external csv files used in
#' # "Schedule:File" class into the same folder
#' idf$save(path = file.path(tempdir(), "test1.idf"), copy_external = TRUE)
#' }
#'
save = function(path = NULL, format = eplusr_option("save_format"), overwrite = FALSE, copy_external = TRUE)
idf_save(self, private, path, format = format, overwrite = overwrite, copy_external = copy_external),
# }}}
# run {{{
#' @description
#' Run simulation using EnergyPlus
#'
#' @details
#' `$run()` calls corresponding version of EnergyPlus to run the current
#' `Idf` object together with specified weather. The model and the
#' weather used will be copied into the output directory. An [EplusJob]
#' object is returned which provides detailed info of the simulation and
#' methods to collect simulation results. Please see [EplusJob] for
#' details.
#'
#' eplusr uses the EnergyPlus command line interface which was
#' introduced since EnergyPlus 8.3.0. So `$run()` only supports models
#' with version no lower than 8.3.0.
#'
#' When calling `$run()`, eplusr will do steps below to make sure the
#' output collecting methods work as expected. Please note that this may
#' result in an IDF file that may not be exactly same as your current
#' `Idf` object.
#'
#' * eplusr uses EnergyPlus SQL output for extracting simulation
#' results. In order to do so, an object in `Output:SQLite` class with
#' `Option Type` value being `SimpleAndTabular` will be automatically
#' created if it does not exists.
#' * In order to make sure `.rdd` (Report Data Dictionary) and `.mdd`
#' (Meter Data Dictionary) files are created during simulation, an
#' object in `Output:VariableDictionary` class with `Key Field` value
#' being `IDF` will be automatically created if it does not exists.
#'
#' @param weather A path to an `.epw` file or an [Epw] object. `weather`
#' can also be `NULL` which will force design-day-only
#' simulation. Note that this needs at least one
#' `Sizing:DesignDay` object exists in the `Idf`.
#' @param dir The directory to save the simulation results. If `NULL`,
#' the folder of `Idf` path will be used. Default: `NULL`.
#' @param wait Whether to wait until the simulation completes and print
#' the standard output and error of EnergyPlus. If `FALSE`, the
#' simulation will run in the background. Default is `TRUE`.
#' @param force Only applicable when the last simulation runs with
#' `wait` equals to `FALSE` and is still running. If `TRUE`,
#' current running job is forced to stop and a new one will
#' start. Default: `FALSE`.
#' @param copy_external If `TRUE`, the external files that current `Idf`
#' object depends on will also be copied into the simulation
#' output directory. The values of file paths in the Idf will be
#' changed automatically. This ensures that the output directory
#' will have all files needed for the model to run. Default is
#' `FALSE`.
#' @param echo Only applicable when `wait` is `TRUE`. Whether to show
#' standard output and error from EnergyPlus. Default: same as
#' `wait`.
#'
#' @return An [EplusJob] object of current simulation.
#'
#' @examples
#' \dontrun{
#' idf <- Idf$new(path_idf)
#' # save the model to tempdir()
#' idf$save(file.path(tempdir(), "test_run.idf"))
#'
#' # use the first epw file in "WeatherData" folder in EnergyPlus v8.8
#' # installation path
#' epw <- list.files(file.path(eplus_config(8.8)$dir, "WeatherData"),
#' pattern = "\\.epw$", full.names = TRUE)[1]
#'
#' # if `dir` is NULL, the directory of IDF file will be used as simulation
#' # output directory
#' job <- idf$run(epw, dir = NULL)
#'
#' # run simulation in the background
#' idf$run(epw, dir = tempdir(), wait = FALSE)
#'
#' # copy all external files into the directory run simulation
#' idf$run(epw, dir = tempdir(), copy_external = TRUE)
#'
#' # check for simulation errors
#' job$errors()
#'
#' # get simulation status
#' job$status()
#'
#' # get output directory
#' job$output_dir()
#'
#' # re-run the simulation
#' job$run()
#'
#' # get simulation results
#' job$report_data()
#' }
#'
run = function(weather, dir = NULL, wait = TRUE, force = FALSE, copy_external = FALSE, echo = wait)
idf_run(self, private, weather, dir, wait, force, copy_external = copy_external, echo),
# }}}
# last_job {{{
#' @description
#' Get the last simulation job
#'
#' @details
#' `$last_job()` returns the last [EplusJob] object that was created
#' using
#' \href{../../eplusr/html/Idf.html#method-run}{\code{$run()}}. If the
#' `Idf` hasn't been run yet, `NULL` is returned.
#'
#' @return `NULL` or an [EplusJob] object.
#'
#' @examples
#' \dontrun{
#' idf$last_job()
#' }
#'
last_job = function()
idf_last_job(self, private),
# }}}
# geometry {{{
#' @description
#' Extract `Idf` geometries
#'
#' @details
#' `$geometry()` extracts all geometry objects into an [IdfGeometry]
#' object. `IdfGeometry` is an abstraction of a collection of geometry
#' in an [Idf]. It provides more detail methods to query geometry
#' properties, update geometry vertices and visualize geometry in 3D
#' using the [rgl](https://cran.r-project.org/package=rgl) package.
#'
#' @return An [IdfGeometry] object.
#'
#' @examples
#' \dontrun{
#' idf$geometry()
#' }
#'
geometry = function()
idf_geom(self, private),
# }}}
# view {{{
#' @description
#' View 3D `Idf` geometry
#'
#' @details
#' `$view()` uses the [rgl](https://cran.r-project.org/package=rgl)
#' package to visualize the IDF geometry in 3D in a similar way as
#' [OpenStudio](https://openstudio.net/).
#'
#' `$view()` returns an [IdfViewer] object which can be used to further
#' tweak the viewer scene.
#'
#' In the rgl window, you can control the view using your mouse:
#'
#' * Left button: Trackball
#' * Right button: Pan
#' * Middle button: Field-of-view (FOV). '0' means orthographic
#' projection.
#' * Wheel: Zoom
#'
#' @param new If `TRUE`, a new rgl window will be open using
#' [rgl::open3d()]. If `FALSE`, existing rgl window will be
#' reused if possible. Default: `FALSE`.
#'
#' @param render_by A single string specifying the way of rendering the
#' geometry. Possible values are:
#'
#' * `"surface_type"`: Default. Render the model by surface type
#' model. Walls, roofs, windows, doors, floors, and shading
#' surfaces will have unique colors.
#' * `"boundary"`: Render the model by outside boundary condition.
#' Only surfaces that have boundary conditions will be rendered
#' with a color. All other surfaces will be white.
#' * `"construction"`: Render the model by surface constructions.
#' * `"zone"`: Render the model by zones assigned.
#' * `"normal"`: Render the model by surface normal. The outside
#' face of a heat transfer face will be rendered as white and the
#' inside face will be rendered as red.
#'
#' @param axis If `TRUE`, the X, Y and Z axes will be drawn at the
#' global origin. Default: `TRUE`.
#'
#' @param wireframe If `TRUE`, the wireframe of each surface will be
#' shown. Default: `TRUE`.
#'
#' @param x_ray If `TRUE`, all surfaces will be rendered translucently.
#' Default: `FALSE`.
#'
#' @return An [IdfViewer] object
#'
#' @examples
#' \dontrun{
#' idf$view()
#' idf$view(render_by = "zone")
#' idf$view(render_by = "construction")
#' }
#'
view = function(new = FALSE, render_by = "surface_type",
wireframe = TRUE, x_ray = FALSE, axis = TRUE)
idf_view(self, private, new = new, render_by = render_by,
axis = axis, wireframe = wireframe, x_ray = x_ray),
# }}}
# print {{{
#' @description
#' Print `Idf` object
#'
#' @details
#' `$print()` prints the `Idf` object according to different detail
#' level specified using the `zoom` argument.
#'
#' With the default `zoom` level `object`, contents of the `Idf` object
#' is printed in a similar style as you see in IDF Editor, with
#' additional heading lines showing `Path`, `Version` of the `Idf`
#' object. Class names of objects are ordered by group and the number of
#' objects in classes are shown in square bracket.
#'
#' @param zoom Control how detailed of the Idf object should be printed.
#' Should be one of `"group"`, `"class"`, `"object"` and
#' `"field"`. Default: `"group"`.
#' * `"group"`: all group names current existing are shown with prevailing
#' square bracket showing how many \strong{C}lasses existing in that group.
#' * `"class"`: all class names are shown with prevailing square bracket
#' showing how many \strong{O}bjects existing in that class, together with
#' parent group name of each class.
#' * `"object"`: all object IDs and names are shown, together with parent
#' class name of each object.
#' * `"field"`: all object IDs and names, field names and values are shown,
#' together with parent class name of each object.
#'
#' @param order Only applicable when `zoom` is `"object"` or `"field"`.
#' If `TRUE`, objects are shown as the same order in the IDF. If
#' `FALSE`, objects are grouped and ordered by classes. Default:
#' `TRUE`.
#'
#' @return The `Idf` object itself, invisibly.
#'
#' @examples
#' \dontrun{
#' idf$print("group")
#' idf$print("class")
#' idf$print("object")
#' idf$print("field")
#'
#' # order objects by there classes
#' idf$print("object", order = FALSE)
#' idf$print("field", order = FALSE)
#' }
#'
print = function(zoom = "class", order = TRUE)
idf_print(self, private, zoom, order)
# }}}
# }}}
),
private = list(
# PRIVATE FIELDS {{{
m_path = NULL,
m_version = NULL,
m_idd = NULL,
m_idf_env = NULL,
m_log = NULL,
# }}}
# PRIVATE FUNCTIONS {{{
uuid = function() private$m_log$uuid,
log_new_uuid = function() log_new_uuid(private$m_log),
log_saved = function() log_saved(private$m_log),
log_unsaved = function() log_unsaved(private$m_log),
log_new_order = function(id) log_new_order(private$m_log, id),
log_add_order = function(id) log_add_order(private$m_log, id),
log_del_order = function(id) log_del_order(private$m_log, id),
idd_env = function() get_priv_env(private$m_idd)$m_idd_env,
idf_env = function() private$m_idf_env,
update_idf_env = function(lst) {
private$m_idf_env$object <- lst$object
private$m_idf_env$value <- lst$value
private$m_idf_env$reference <- lst$reference
},
deep_clone = function(name, value) idf_deep_clone(self, private, name, value)
# }}}
)
)
# set deep default value to `TRUE`
formals(Idf$clone_method)$deep <- TRUE
formals(Idf$public_methods$clone)$deep <- TRUE
# }}}
# idf_version {{{
idf_version <- function(self, private) {
private$m_version
}
# }}}
# idf_path {{{
idf_path <- function(self, private) {
private$m_path
}
# }}}
# idf_group_name {{{
idf_group_name <- function(self, private, all = FALSE, sorted = TRUE) {
if (all) {
get_idd_group_name(private$idd_env())
} else {
grp <- private$idd_env()$class[private$idf_env()$object, on = "class_id", group_id]
if (sorted) {
get_idd_group_name(private$idd_env(), sort(unique(grp)))
} else {
get_idd_group_name(private$idd_env(), grp)
}
}
}
# }}}
# idf_class_name {{{
idf_class_name <- function(self, private, all = FALSE, sorted = TRUE, by_group = FALSE) {
if (all) {
if (!by_group) return(private$idd_env()$class$class_name)
cls <- get_idd_class(private$idd_env(), property = "group_name")
res <- cls[, list(class_name = list(class_name)), by = "group_name"]
setattr(res$class_name, "names", res$group_name)[]
} else {
add_class_name(private$idd_env(), private$idf_env()$object)
on.exit(set(private$idf_env()$object, NULL, "class_name", NULL), add = TRUE)
if (sorted) {
if (!by_group) {
private$idf_env()$object[order(class_id), unique(class_name)]
} else {
cls <- get_idd_class(private$idd_env(), sort(unique(private$idf_env()$object$class_id)),
property = "group_name")
res <- cls[, list(class_name = list(class_name)), by = "group_name"]
setattr(res$class_name, "names", res$group_name)[]
}
} else {
private$idf_env()$object$class_name
}
}
}
# }}}
# idf_object_id {{{
idf_object_id <- function(self, private, class = NULL, simplify = TRUE) {
get_idf_object_id(private$idd_env(), private$idf_env(), class, simplify)
}
# }}}
# idf_object_name {{{
idf_object_name <- function(self, private, class = NULL, simplify = FALSE) {
get_idf_object_name(private$idd_env(), private$idf_env(), class, simplify)
}
# }}}
# idf_object_num {{{
idf_object_num <- function(self, private, class = NULL) {
get_idf_object_num(private$idd_env(), private$idf_env(), class)
}
# }}}
# idf_is_valid_group_name {{{
#' @importFrom checkmate assert_character
idf_is_valid_group_name <- function(self, private, group, all = FALSE) {
assert_valid_type(group, "Group Name", type = "name")
group %chin% idf_group_name(self, private, all, FALSE)
}
# }}}
# idf_is_valid_class_name {{{
#' @importFrom checkmate assert_character
idf_is_valid_class_name <- function(self, private, class, all = FALSE) {
assert_valid_type(class, "Class Name", type = "name")
class %in% idf_class_name(self, private, all, FALSE)
}
# }}}
# idf_is_valid_object_id {{{
#' @importFrom checkmate assert_integerish
idf_is_valid_object_id <- function(self, private, id, class = NULL) {
assert_valid_type(id, "Object ID", type = "id")
assert_string(class, null.ok = TRUE)
id <- assert_integerish(id, any.missing = FALSE, coerce = TRUE)
id %in% idf_object_id(self, private, class, simplify = TRUE)
}
# }}}
# idf_is_valid_object_name {{{
idf_is_valid_object_name <- function(self, private, name, class = NULL) {
assert_valid_type(name, "Object Name", type = "name")
assert_string(class, null.ok = TRUE)
if (is.null(class)) {
stri_trans_tolower(name) %chin% private$idf_env()$object[!is.na(object_name), object_name_lower]
} else {
stri_trans_tolower(name) %chin% stri_trans_tolower(get_idf_object_name(private$idd_env(), private$idf_env(), class, simplify = TRUE))
}
}
# }}}
# idf_external_deps {{{
idf_external_deps <- function(self, private, full = FALSE) {
assert_flag(full)
base_dir <- if (!is.null(private$m_path)) dirname(private$m_path) else NULL
deps <- get_idf_external_deps(private$idd_env(), private$idf_env(), base_dir)
if (!full) return(c(unique(deps$path), unlist(attr(deps, "extra"), FALSE, FALSE)))
setnames(deps,
c("object_id", "object_name", "class_name", "field_index", "field_name", "value_chr"),
c("id", "name", "class", "index", "field", "value"))
cols <- c("id", "name", "class", "index", "field", "value", "path", "exist")
if (length(to_del <- setdiff(names(deps), cols))) set(deps, NULL, to_del, NULL)
setcolorder(deps, cols)[]
}
# }}}
# idf_is_unsaved {{{
idf_is_unsaved <- function(self, private) {
private$m_log$unsaved
}
# }}}
# idf_definition {{{
idf_definition <- function(self, private, class = NULL) {
if (is.null(class)) {
private$m_idd
} else {
IddObject$new(class, private$m_idd)
}
}
# }}}
# idf_obj {{{
#' @importFrom checkmate assert
idf_obj <- function(self, private, which, class = NULL) {
assert_valid_type(which, "Object ID|Name", len = 1L)
obj <- get_idf_object(private$idd_env(), private$idf_env(),
class = class, object = which, ignore_case = TRUE
)
idf_return_matched(self, private, obj)[[1L]]
}
# }}}
# idf_object_unique {{{
idf_object_unique <- function(self, private, class) {
assert_valid_type(class, "Class Name", len = 1L, type = "name")
obj <- get_idf_object(private$idd_env(), private$idf_env(), class)
if (!unique(obj$class_id) %in% private$idd_env()$class[J(TRUE), on = "unique_object", class_id, nomatch = NULL]) {
abort(paste0(surround(unique(obj$class_name)), " is not a valid unique-object class index or name."))
}
if (nrow(obj) > 1L) {
abort(paste0("Unique-object class ", surround(unique(obj$class_name)), " have more than one objects:\n",
get_object_info(obj[, rleid := .I], c("id", "name"), collapse = "\n"),
"\nPlease see '$validate()' for more details."
))
}
idf_return_matched(self, private, obj)[[1L]]
}
# }}}
# idf_objects {{{
#' @importFrom checkmate assert check_character check_integerish
idf_objects <- function(self, private, which, class = NULL) {
assert_valid_type(which, "Object ID|Name")
obj <- get_idf_object(private$idd_env(), private$idf_env(),
class = class, object = which, ignore_case = TRUE
)
idf_return_matched(self, private, obj)
}
# }}}
# idf_objects_in_class {{{
#' @importFrom checkmate assert_string
idf_objects_in_class <- function(self, private, class) {
assert_valid_type(class, "Class Name", type = "name")
obj <- get_idf_object(private$idd_env(), private$idf_env(), class)
idf_return_matched(self, private, obj)
}
# }}}
# idf_objects_in_group {{{
#' @importFrom checkmate assert_string
idf_objects_in_group <- function(self, private, group) {
assert_string(group)
add_joined_cols(private$idd_env()$class, private$idf_env()$object, "class_id", "group_id")
add_joined_cols(private$idd_env()$group, private$idf_env()$object, "group_id", "group_name")
on.exit(set(private$idf_env()$object, NULL, c("group_id", "group_name"), NULL), add = TRUE)
grp_in <- recognize_input(group, "group")
obj <- join_from_input(private$idf_env()$object, grp_in, "group_id")
idf_return_matched(self, private, obj)
}
# }}}
# idf_object_relation {{{
#' @importFrom checkmate assert check_string
idf_object_relation <- function(self, private, which,
direction = c("all", "ref_to", "ref_by", "node"),
object = NULL, class = NULL, group = NULL,
depth = 0L, keep = FALSE, class_ref = c("both", "none", "all")) {
assert_valid_type(which, "Object ID|Name")
obj <- get_idf_object(private$idd_env(), private$idf_env(),
object = which, ignore_case = TRUE
)
get_idfobj_relation(private$idd_env(), private$idf_env(),
object_id = obj$object_id, name = TRUE, direction = direction,
keep_all = keep, depth = depth, class_ref = class_ref,
object = object, class = class, group = group
)
}
# }}}
# idf_objects_in_relation {{{
idf_objects_in_relation <- function(self, private, which, direction = c("ref_to", "ref_by", "node"),
object = NULL, class = NULL, group = NULL, depth = 0L,
class_ref = c("both", "none", "all")) {
assert_valid_type(which, "Object ID|Name")
direction <- match.arg(direction)
obj <- get_idf_object(private$idd_env(), private$idf_env(), object = which, ignore_case = TRUE)
rel <- get_idfobj_relation(private$idd_env(), private$idf_env(), obj$object_id,
name = FALSE, depth = depth, direction = direction,
object = object, class = class, group = group, class_ref = class_ref
)
id_self <- obj$object_id
if (direction == "ref_to") {
id_ref <- unique(rel$ref_to$src_object_id[!is.na(rel$ref_to$src_object_id)])
} else {
id_ref <- unique(rel[[direction]]$object_id[!is.na(rel[[direction]]$object_id)])
}
obj_self <- list(IdfObject$new(id_self, obj$class_id, self))
setattr(obj_self, "names", obj$object_name)
if (!length(id_ref)) {
if (in_verbose()) {
dir <- switch(direction, ref_to = "does not refer to", ref_by = "is not referred by",
node = "has no node or their nodes have no reference to"
)
msg <- paste(get_object_info(obj, numbered = FALSE), dir, "any objects")
if (is.null(object) && is.null(class) && is.null(group)) {
verbose_info(msg, ".")
} else {
verbose_info(msg, " specified.")
}
}
return(obj_self)
}
res <- c(obj_self, lapply(id_ref, IdfObject$new, parent = self))
ref_nm <- private$idf_env()$object[J(id_ref), on = "object_id", object_name]
setattr(res, "names", c(obj$object_name, ref_nm))
res
}
# }}}
# idf_search_object {{{
idf_search_object <- function(self, private, pattern, class = NULL, ignore.case = FALSE,
perl = FALSE, fixed = FALSE, useBytes = FALSE) {
if (!is.null(class) && anyDuplicated(class)) {
abort("Class should not contain any duplication.")
}
obj <- get_idf_object(private$idd_env(), private$idf_env(), class)
obj <- obj[grepl(pattern, object_name, ignore.case = ignore.case, perl = perl,
fixed = fixed, useBytes = useBytes)
]
if (!nrow(obj)) {
verbose_info("No matched result found.")
return(invisible())
}
idf_return_matched(self, private, obj)
}
# }}}
# idf_return_matched {{{
idf_return_matched <- function(self, private, matched, object_id) {
if (is.numeric(matched)) {
matched <- private$idf_env()$object[J(matched), on = "object_id", nomatch = NULL]
}
res <- apply2(matched$object_id, matched$class_id, IdfObject$new, list(parent = self))
setattr(res, "names", matched$object_name)[]
}
# }}}
# idf_dup {{{
idf_dup <- function(self, private, ...) {
obj <- expand_idf_dots_name(private$idd_env(), private$idf_env(), ...)
dup <- dup_idf_object(private$idd_env(), private$idf_env(), obj)
# log
private$log_new_order(dup$changed)
private$log_unsaved()
private$log_new_uuid()
private$update_idf_env(dup)
idf_return_matched(self, private, dup$changed)
}
# }}}
# idf_add {{{
idf_add <- function(self, private, ..., .default = TRUE, .all = FALSE, .env = parent.frame()) {
l <- expand_idf_dots_value(private$idd_env(), private$idf_env(), ...,
.type = "class", .complete = TRUE, .all = .all,
.scalar = FALSE, .pair = TRUE, .ref_assign = TRUE,
.unique = FALSE, .empty = TRUE, .default = .default, .env = .env
)
add <- add_idf_object(private$idd_env(), private$idf_env(),
l$object, l$value, default = FALSE, unique = FALSE, empty = .all)
if (!length(add$changed)) return(invisible(NULL))
# log
private$log_new_order(add$changed)
private$log_unsaved()
private$log_new_uuid()
private$update_idf_env(add)
idf_return_matched(self, private, add$changed)
}
# }}}
# idf_set {{{
idf_set <- function(self, private, ..., .default = TRUE, .empty = FALSE, .env = parent.frame()) {
l <- expand_idf_dots_value(private$idd_env(), private$idf_env(), ...,
.type = "object", .complete = TRUE, .all = FALSE,
.scalar = FALSE, .pair = TRUE, .ref_assign = TRUE,
.unique = TRUE, .empty = TRUE, .default = .default, .env = .env
)
set <- set_idf_object(private$idd_env(), private$idf_env(),
l$object, l$value, empty = .empty)
# log
private$log_add_order(c(set$changed, set$updated))
private$log_unsaved()
private$log_new_uuid()
private$update_idf_env(set)
idf_return_matched(self, private, set$changed)
}
# }}}
# idf_del {{{
idf_del <- function(self, private, ..., .ref_by = FALSE, .ref_to = FALSE, .recursive = FALSE, .force = FALSE) {
obj <- expand_idf_dots_name(private$idd_env(), private$idf_env(), ..., .keep_name = FALSE)
del <- del_idf_object(private$idd_env(), private$idf_env(), obj,
ref_to = .ref_to, ref_by = .ref_by, recursive = .recursive, force = .force)
# log
private$log_del_order(del$changed)
private$log_unsaved()
private$log_new_uuid()
private$update_idf_env(del)
invisible(self)
}
# }}}
# idf_purge {{{
idf_purge <- function(self, private, object = NULL, class = NULL, group = NULL) {
obj <- get_idf_object_multi_scope(private$idd_env(), private$idf_env(), object, class, group)
purge <- purge_idf_object(private$idd_env(), private$idf_env(), obj)
if (!length(purge$changed)) return(invisible(self))
# log
private$log_del_order(purge$changed)
private$log_unsaved()
private$log_new_uuid()
private$update_idf_env(purge)
invisible(self)
}
# }}}
# idf_duplicated {{{
idf_duplicated <- function(self, private, object = NULL, class = NULL, group = NULL) {
obj <- get_idf_object_multi_scope(private$idd_env(), private$idf_env(), object, class, group)
duplicated_idf_object(private$idd_env(), private$idf_env(), obj)[
, list(class = class_name, id = object_id, name = object_name, duplicate = unique_object_id)]
}
# }}}
# idf_unique {{{
idf_unique <- function(self, private, object = NULL, class = NULL, group = NULL) {
obj <- get_idf_object_multi_scope(private$idd_env(), private$idf_env(), object, class, group)
uni <- unique_idf_object(private$idd_env(), private$idf_env(), obj)
if (!length(uni$changed)) return(invisible(self))
# log
private$log_del_order(uni$changed)
private$log_unsaved()
private$log_new_uuid()
private$update_idf_env(uni)
invisible(self)
}
# }}}
# idf_rename {{{
idf_rename <- function(self, private, ...) {
obj <- expand_idf_dots_name(private$idd_env(), private$idf_env(), ..., .keep_name = TRUE)
ren <- rename_idf_object(private$idd_env(), private$idf_env(), obj)
# log
private$log_add_order(c(ren$changed, ren$updated))
private$log_unsaved()
private$log_new_uuid()
private$update_idf_env(ren)
idf_return_matched(self, private, ren$changed)
}
# }}}
# idf_insert {{{
idf_insert <- function(self, private, ..., .unique = TRUE, .empty = FALSE) {
l <- expand_idf_dots_object(private$idd_env(), private$idf_env(), ...,
.unique = FALSE, .strict = TRUE)
# ignore Version object
if (any(is_ver <- l$object$class_id == 1L)) {
if (in_verbose()) {
m <- l$object[class_id == 1L, paste0(" #", rleid, "| Object [", object_id, "] --> Class 'Version'", collapse = "\n")]
verbose_info("'Version' objects in input below have been automatically skipped:\n", m)
}
id <- l$object$object_id[is_ver]
l$object <- l$object[!J(1L), on = "class_id"]
l$value <- l$value[!J(id), on = "object_id"]
if (!nrow(l$object)) {
verbose_info("After removing Version objects, nothing to add.")
return(invisible())
}
}
# assign new rleid to make sure it can be used as an identifier
l$object[, new_rleid := rleid(rleid, object_id)]
l$value[, new_rleid := rleid(rleid, object_id)]
set(l$object, NULL, "rleid", NULL)
set(l$value, NULL, "rleid", NULL)
setnames(l$object, "new_rleid", "rleid")
setnames(l$value, "new_rleid", "rleid")
ins <- add_idf_object(private$idd_env(), private$idf_env(), l$object, l$value,
default = FALSE, unique = .unique, empty = .empty
)
if (!length(ins$changed)) return(invisible())
# log
private$log_new_order(ins$changed)
private$log_unsaved()
private$log_new_uuid()
private$update_idf_env(ins)
idf_return_matched(self, private, ins$changed)
}
# }}}
# idf_search_value {{{
idf_search_value <- function(self, private, pattern, class = NULL, ignore.case = FALSE,
perl = FALSE, fixed = FALSE, useBytes = FALSE) {
l <- expand_idf_regex(private$idd_env(), private$idf_env(), pattern,
replacement = NULL, class, ignore.case, perl, fixed, useBytes)
if (!nrow(l$object)) {
verbose_info("No matched result found.")
return(invisible())
}
idf_return_matched(self, private, l$object)
}
# }}}
# idf_replace_value {{{
idf_replace_value <- function(self, private, pattern, replacement, class = NULL,
ignore.case = FALSE, perl = FALSE, fixed = FALSE,
useBytes = FALSE) {
l <- expand_idf_regex(private$idd_env(), private$idf_env(), pattern,
replacement, class, ignore.case, perl, fixed, useBytes)
if (!nrow(l$object)) {
verbose_info("No matched result found.")
return(invisible())
}
rep <- set_idf_object(private$idd_env(), private$idf_env(), l$object, l$value, empty = FALSE)
# log
private$log_add_order(c(rep$changed, rep$updated))
private$log_unsaved()
private$log_new_uuid()
private$update_idf_env(rep)
idf_return_matched(self, private, rep$changed)
}
# }}}
# idf_paste {{{
idf_paste <- function(self, private, in_ip = FALSE, ver = NULL, unique = TRUE, empty = FALSE) {
if (is.null(ver)) ver <- private$m_version
l <- read_idfeditor_copy(private$idd_env(), private$idf_env(), version = ver, in_ip = in_ip)
pst <- add_idf_object(private$idd_env(), private$idf_env(),
l$object, l$value, default = FALSE, unique = unique, empty = empty)
if (!length(pst$changed)) return(invisible())
# log
private$log_new_order(pst$changed)
private$log_unsaved()
private$log_new_uuid()
private$update_idf_env(pst)
idf_return_matched(self, private, pst$changed)
}
# }}}
# idf_load {{{
idf_load <- function(self, private, ..., .unique = TRUE, .default = TRUE, .empty = FALSE) {
l <- expand_idf_dots_literal(private$idd_env(), private$idf_env(), ...,
.default = .default, .exact = FALSE
)
ld <- add_idf_object(private$idd_env(), private$idf_env(),
l$object, l$value, default = FALSE, unique = .unique, empty = .empty)
if (!length(ld$changed)) return(invisible())
# log
private$log_new_order(ld$changed)
private$log_unsaved()
private$log_new_uuid()
private$update_idf_env(ld)
idf_return_matched(self, private, ld$changed)
}
# }}}
# idf_update {{{
idf_update <- function(self, private, ..., .default = TRUE, .empty = FALSE) {
l <- expand_idf_dots_literal(private$idd_env(), private$idf_env(), ...,
.default = .default, .exact = TRUE
)
upd <- set_idf_object(private$idd_env(), private$idf_env(),
l$object, l$value, empty = .empty)
# log
private$log_add_order(c(upd$changed, upd$updated))
private$log_unsaved()
private$log_new_uuid()
private$update_idf_env(upd)
idf_return_matched(self, private, upd$changed)
}
# }}}
# idf_validate {{{
idf_validate <- function(self, private, level = eplusr_option("validate_level")) {
validate_on_level(private$idd_env(), private$idf_env(), level = level)
}
# }}}
# idf_is_valid {{{
idf_is_valid <- function(self, private, level = eplusr_option("validate_level")) {
count_check_error(validate_on_level(private$idd_env(), private$idf_env(), level = level)) == 0L
}
# }}}
# idf_to_string {{{
idf_to_string <- function(self, private, which = NULL, class = NULL,
comment = TRUE, header = TRUE, format = eplusr_option("save_format"),
leading = 4L, sep_at = 29L) {
if (format == "asis") format <- private$m_log$save_format
get_idf_string(private$idd_env(), private$idf_env(), private$m_log$order,
class, which, comment = comment, header = header, format = format,
leading = leading, sep_at = sep_at
)
}
# }}}
# idf_to_table {{{
idf_to_table <- function(self, private, which = NULL, class = NULL, string_value = TRUE, unit = FALSE, wide = FALSE, align = FALSE, all = FALSE, group_ext = c("none", "group", "index"), force = FALSE, init = FALSE) {
get_idf_table(private$idd_env(), private$idf_env(), class = class, object = which,
string_value = string_value, unit = unit, wide = wide, align = align,
all = all, group_ext = group_ext, force = force, init = init)
}
# }}}
# idf_save {{{
idf_save <- function(self, private, path = NULL, format = eplusr_option("save_format"),
overwrite = FALSE, copy_external = TRUE) {
if (is.null(path)) {
if (is.null(private$m_path)) {
abort("The Idf object is not created from local file. Please give the path to save.", "idf_not_local")
} else {
path <- private$m_path
}
}
if (format == "asis") format <- private$m_log$save_format
oldpath <- private$m_path %||% path
path <- save_idf(private$idd_env(), private$idf_env(), private$m_log$order,
path = path, in_ip = private$m_log$view_in_ip, format = format,
overwrite = overwrite, copy_external = copy_external, oldpath = oldpath)
# if values are updated, assign new uuid
if (attr(path, "path_updated")) private$log_new_uuid()
attr(path, "path_updated") <- NULL
# log saved
private$log_saved()
# change path
private$m_path <- normalizePath(path)
invisible(path)
}
# }}}
# idf_run {{{
idf_run <- function(self, private, epw, dir = NULL, wait = TRUE,
force = FALSE, copy_external = FALSE, echo = wait) {
# check if the model is still running
old <- private$m_log$job
if (!inherits(old, "EplusJob")) {
private$m_log$job <- EplusJob$new(self, epw)
# recreate job if the model has been changed since last ran
} else if (private$uuid() != get_priv_env(private$m_log$job)$cached_seed_uuid()) {
private$m_log$job <- EplusJob$new(self, epw)
}
private$m_log$job$run(epw = epw, dir = dir, wait = wait, force = force,
echo = echo, copy_external = copy_external
)
}
# }}}
# idf_last_job {{{
idf_last_job <- function(self, private) {
private$m_log$job
}
# }}}
# idf_geom {{{
idf_geom <- function(self, private) {
if (is.null(private$m_log$geom)) {
private$m_log$geom <- idf_geometry(self)
}
private$m_log$geom
}
# }}}
# idf_view {{{
idf_view <- function(self, private, new = FALSE, render_by = "surface_type",
wireframe = TRUE, x_ray = FALSE, axis = TRUE) {
idf_geom(self, private)$view(new = new, render_by = render_by,
axis = axis, wireframe = wireframe, x_ray = x_ray)
}
# }}}
# idf_print {{{
idf_print <- function(self, private, zoom = c("object", "class", "group", "field"), order = FALSE) {
zoom <- match.arg(zoom)
cli::cat_rule("EnergPlus Input Data File", line = 1)
if (is.null(private$m_path)) path <- cli::style_bold(cli::bg_red("NOT LOCAL")) else path <- surround(private$m_path)
cli::cat_line(cli::ansi_strtrim(c(
paste0(" ", cli::symbol$bullet, " Path: ", path),
paste0(" ", cli::symbol$bullet, " Version: ", surround(private$m_version))
)))
cli::cat_line()
if (zoom == "group") {
brief <- TRUE
nest <- TRUE
component <- c("group", "class")
dt <- private$idd_env()$class[
J(private$idf_env()$object$class_id), on = "class_id", mult = "first",
.SD, .SDcols = c("class_id", "class_name", "group_id")]
add_joined_cols(private$idd_env()$group, dt, "group_id", "group_name")
} else if (zoom == "class") {
brief <- TRUE
nest <- TRUE
component <- c("group", "class", "object")
dt <- private$idf_env()$object[, .SD, .SDcols = c("class_id", "object_id", "object_name")]
add_joined_cols(private$idd_env()$class, dt, "class_id", c("class_name", "group_id"))
add_joined_cols(private$idd_env()$group, dt, "group_id", "group_name")
} else if (zoom == "object") {
brief <- FALSE
nest <- if (order) FALSE else TRUE
component <- c("class", "object")
dt <- private$idf_env()$object[, .SD, .SDcols = c("class_id", "object_id", "object_name")]
add_joined_cols(private$idd_env()$class, dt, "class_id", c("class_name"))
} else {
brief <- FALSE
nest <- if (order) FALSE else TRUE
component <- c("class", "object", "value")
add_idf_format_cols(private$idd_env(), private$idf_env())
on.exit(del_idf_format_cols(private$idd_env(), private$idf_env()), add = TRUE)
add_joined_cols(private$idd_env()$field, private$idf_env()$value, "field_id", "type_enum")
on.exit(set(private$idf_env()$value, NULL, c("type_enum"), NULL), add = TRUE)
add_joined_cols(private$idf_env()$object, private$idf_env()$value, "object_id", "object_name")
on.exit(set(private$idf_env()$value, NULL, c("object_name"), NULL), add = TRUE)
dt <- private$idf_env()$value
}
out <- unlist(format_objects(dt, component, brief = brief, nest = nest, order = nest)$out, use.names = FALSE)
# remove tailing space
if (zoom != "group") out <- out[-length(out)]
cli::cat_line(cli::ansi_strtrim(out))
invisible(self)
}
# }}}
# idf_deep_clone {{{
idf_deep_clone <- function(self, private, name, value) {
if (is_idd(value)) {
value
} else if (is.environment(value)) {
l <- as.list.environment(value)
# copy data.table is necessary here
l <- lapply(l, function(x) if (inherits(x, "data.table")) copy(x) else x)
list2env(l)
} else {
value
}
}
# }}}
# idf_add_output_sqlite {{{
idf_add_output_sqlite <- function(idf) {
if (!is_idf(idf)) idf <- read_idf(idf)
added <- FALSE
if (idf$is_valid_class("Output:SQLite")) {
sql <- idf$objects_in_class("Output:SQLite")[[1L]]
type <- toupper(sql$value()[[1]])
if (type != "SIMPLEANDTABULAR") {
sql$set("SimpleAndTabular")
verbose_info("Setting 'Option Type' in ",
"'Output:SQLite' to from ", surround(type), " to 'SimpleAndTabular'.")
added <- TRUE
}
} else {
idf$add(Output_SQLite = list("SimpleAndTabular"))
verbose_info("Adding an object in class 'Output:SQLite' and setting its ",
"'Option Type' to 'SimpleAndTabular' in order to create SQLite output file.")
added <- TRUE
}
added
}
# }}}
# idf_add_output_vardict {{{
idf_add_output_vardict <- function(idf) {
if (!is_idf(idf)) idf <- read_idf(idf)
added <- FALSE
if (idf$is_valid_class("Output:VariableDictionary")) {
dict <- idf$objects_in_class("Output:VariableDictionary")[[1L]]
key <- toupper(dict$value()[[1]])
if (!key %chin% c("IDF", "REGULAR")) {
dict$set("IDF")
verbose_info("Setting 'Key Field' in ",
"'Output:VariableDictionary' to from ", surround(key), " to 'IDF'.")
added <- TRUE
}
} else {
with_silent(idf$add(Output_VariableDictionary = list("IDF")))
verbose_info("Adding an object in class 'Output:VariableDictionary' and setting its ",
"'Key Field' to 'IDF' in order to create RDD and MDD output file.")
added <- TRUE
}
added
}
# }}}
# idf_set_output_files {{{
idf_set_output_files <- function(idf, sql = FALSE, dict = FALSE) {
if (!is_idf(idf)) idf <- read_idf(idf)
modified <- FALSE
cls <- "Output:Control:Files"
if (idf$version() < "9.4" || !idf$is_valid_class(cls)) return(modified)
obj <- idf$object_unique(cls)
val <- obj$value(all = TRUE)
if (sql && is.na(val[["Output SQLite"]]) || tolower(val[["Output SQLite"]]) == "no") {
verbose_info("Setting 'Output SQLite' in ", surround(cls), " from 'No' to 'Yes'")
val[["Output SQLite"]] <- "Yes"
modified <- TRUE
}
if (dict && is.na(val[["Output RDD"]]) || tolower(val[["Output RDD"]]) == "no") {
verbose_info("Setting 'Output RDD' in ", surround(cls), " from 'No' to 'Yes'")
val[["Output RDD"]] <- "Yes"
modified <- TRUE
}
if (dict && is.na(val[["Output MDD"]]) || tolower(val[["Output MDD"]]) == "no") {
verbose_info("Setting 'Output MDD' in ", surround(cls), " from 'No' to 'Yes'")
val[["Output MDD"]] <- "Yes"
modified <- TRUE
}
if (modified) obj$set(val[sprintf("Output %s", c("SQLite", "RDD", "MDD"))])
modified
}
# }}}
# idf_has_hvactemplate {{{
idf_has_hvactemplate <- function(idf) {
if (!is_idf(idf)) idf <- read_idf(idf)
cls <- c(
"HVACTemplate:Thermostat",
"HVACTemplate:Zone:IdealLoadsAirSystem",
"HVACTemplate:Zone:BaseboardHeat",
"HVACTemplate:Zone:FanCoil",
"HVACTemplate:Zone:PTAC",
"HVACTemplate:Zone:PTHP",
"HVACTemplate:Zone:WaterToAirHeatPump",
"HVACTemplate:Zone:VRF",
"HVACTemplate:Zone:Unitary",
"HVACTemplate:Zone:VAV",
"HVACTemplate:Zone:VAV:FanPowered",
"HVACTemplate:Zone:VAV:HeatAndCool",
"HVACTemplate:Zone:ConstantVolume",
"HVACTemplate:Zone:DualDuct",
"HVACTemplate:System:VRF",
"HVACTemplate:System:Unitary",
"HVACTemplate:System:UnitaryHeatPump:AirToAir",
"HVACTemplate:System:UnitarySystem",
"HVACTemplate:System:VAV",
"HVACTemplate:System:PackagedVAV",
"HVACTemplate:System:ConstantVolume",
"HVACTemplate:System:DualDuct",
"HVACTemplate:System:DedicatedOutdoorAir",
"HVACTemplate:Plant:ChilledWaterLoop",
"HVACTemplate:Plant:Chiller",
"HVACTemplate:Plant:Chiller:ObjectReference",
"HVACTemplate:Plant:Tower",
"HVACTemplate:Plant:Tower:ObjectReference",
"HVACTemplate:Plant:HotWaterLoop",
"HVACTemplate:Plant:Boiler",
"HVACTemplate:Plant:Boiler:ObjectReference",
"HVACTemplate:Plant:MixedWaterLoop"
)
any(idf$is_valid_class(cls))
}
# }}}
#' Read an EnergyPlus Input Data File (IDF)
#'
#' `read_idf` takes an EnergyPlus Input Data File (IDF) as input and returns an
#' `Idf` object. For more details on `Idf` object, please see [Idf] class.
#'
#' @param path Either a path, a connection, or literal data (either a single
#' string or a raw vector) to an EnergyPlus Input Data File (IDF). If a
#' file path, that file usually has a extension `.idf`.
#' @param idd Any acceptable input of [use_idd()]. If `NULL`, which is the
#' default, the version of IDF will be passed to [use_idd()]. If the
#' input is an `.ddy` file which does not have a version field, the
#' latest version of [Idf] cached will be used.
#' @param encoding The file encoding of input IDD. Should be one of `"unknown"`,
#' `"Latin-1" and `"UTF-8"`. The default is `"unknown"` which means that
#' the file is encoded in the native encoding.
#'
#' @details
#' Currently, Imf file is not fully supported. All EpMacro lines will be treated
#' as normal comments of the nearest downwards object. If input is an Imf file,
#' a warning will be given during parsing. It is recommended to convert the Imf
#' file to an Idf file and use [ParametricJob] class to conduct
#' parametric analysis.
#'
#' @return An [Idf] object.
#' @examples
#' \dontrun{
#' # example model shipped with eplusr from EnergyPlus v8.8
#' idf_path <- system.file("extdata/1ZoneUncontrolled.idf", package = "eplusr") # v8.8
#'
#' # if neither EnergyPlus v8.8 nor Idd v8.8 was found, error will occur
#' # if EnergyPlus v8.8 is found but Idd v8.8 was not, `Energy+.idd` in EnergyPlus
#' # installation folder will be used for pasing
#' # if Idd v8.8 is found, it will be used automatically
#' is_avail_eplus("8.8")
#' is_avail_idd("8.8")
#'
#' read_idf(idf_path)
#'
#' # argument `idd` can be specified explicitly using `use_idd()`
#' read_idf(idf_path, idd = use_idd("8.8"))
#'
#' # you can set `download` arugment to "auto" in `use_idd()` if you want to
#' # automatically download corresponding IDD file when necessary
#' read_idf(idf_path, use_idd("8.8", download = "auto"))
#'
#' # Besides use a path to an IDF file, you can also provide IDF in literal
#' # string format
#' idf_string <-
#' "
#' Version, 8.8;
#' Building,
#' Building; !- Name
#' "
#'
#' read_idf(idf_string, use_idd("8.8", download = "auto"))
#' }
#' @seealso [Idf] class for modifying EnergyPlus model. [use_idd()] and
#' [download_idd()] for downloading and parsing EnergyPlus IDD file.
#' [use_eplus()] for configuring which version of EnergyPlus to use.
#' @export
#' @author Hongyuan Jia
# read_idf {{{
read_idf <- function(path, idd = NULL, encoding = "unknown") {
Idf$new(path, idd, encoding = encoding)
}
# }}}
# replace_objects_in_class {{{
replace_objects_in_class <- function(self, private, class, value, unique_object = FALSE) {
exist <- self$is_valid_class(class)
# if NULL, delete all objects in class
if (is.null(value)) {
if (exist) self$del(self$object_id(class, simplify = TRUE))
# if a character vector or a data.frame, use `$load()`
} else if (is.character(value) || is.data.frame(value)) {
if (exist) {
# get current objects
obj_main <- get_idf_object(private$idd_env(), private$idf_env(), class)
# temporary rename objects to bypass unique name checking
obj_dt <- private$idf_env()$object
obj_dt[J(obj_main$object_id), on = "object_id",
object_name_lower := paste0(object_name_lower, stri_rand_strings(length(object_name_lower), 15L))
]
}
# disable unique object checking
chk <- level_checks()
if (exist && unique_object) chk$unique_object <- FALSE
# get new object data
l <- expand_idf_dots_literal(private$idd_env(), private$idf_env(), value)
# stop if not from the same class
cls_in <- private$idd_env()$class$class_name[l$object$class_id]
if (any(cls_in != class)) {
if (exist) {
# get back original object names
obj_dt[J(obj_main$object_id), on = "object_id",
object_name_lower := stri_trans_tolower(object_name)
]
}
invld_cls <- cls_in[cls_in != class]
abort(paste0("Input IdfObjects should from class ", surround(class), ". ",
"Invalid input class: ", collapse(invld_cls)
))
}
# if everything looks good, add new objects
l <- add_idf_object(private$idd_env(), private$idf_env(), l$object, l$value,
default = TRUE, unique = FALSE, empty = FALSE, level = chk
)
private$update_idf_env(l)
private$log_new_order(l$changed)
private$log_unsaved()
private$log_new_uuid()
# delete original objects
if (exist) {
with_silent(self$del(obj_main$object_id, .force = TRUE))
}
# if a list of IdfObjects, use `$insert()`
} else if (checkmate::test_list(value, "IdfObject", any.missing = FALSE)) {
# check if input is from the same model
# get uuid if idf
uuid_main <- private$uuid()
# get uuids of input
uuid_in <- vcapply(value, function(obj) .subset2(.subset2(get_priv_env(obj), "log_env")(), "uuid"))
# get id of input
obj_id_in <- viapply(value, function(obj) .subset2(get_priv_env(obj), "m_object_id"))
# ignore ones that is from the same idf
if (exist) {
obj_main <- get_idf_object(private$idd_env(), private$idf_env(), class)
same_num <- length(value) == nrow(obj_main)
same_id <- obj_id_in %in% obj_main$object_id
} else {
same_num <- FALSE
same_id <- FALSE
}
# direct return the idf
if (all(uuid_main == uuid_in) && same_num && all(same_id)) return(invisible(self))
# stop if not from the same class
cls_id_in <- viapply(value, function(obj) .subset2(get_priv_env(obj), "m_class_id"))
cls_in <- private$idd_env()$class$class_name[cls_id_in]
if (any(cls_in != class)) {
invld_cls <- vcapply(value[cls_in != class], function(obj) .subset2(obj, "class_name")())
msg <- paste0(" #", which(cls_in != class), "| <IdfObject> --> Class: ", surround(invld_cls),
collapse = "\n"
)
abort(paste0("Input IdfObjects should from class '", class, "'. Invalid input:\n", msg))
}
# ignore same objects and insert new ones
if (any(!same_id)) .subset2(self, "insert")(value[!same_id], .unique = FALSE)
# delete objects that are not included in input
if (exist && length(id_del <- setdiff(obj_main$object_id, obj_id_in))) {
invisible(self$del(id_del, .force = TRUE))
}
} else {
mes <- if (unique_object) "an IdfObject" else "a list of IdfObjects"
abort(paste0("Value should be ", mes, ", a character vector or a data.frame. ",
"Input class: ", surround(class(value)[[1]]), "."
))
}
invisible(self)
}
# }}}
#' @export
# .DollarNames.Idf {{{
.DollarNames.Idf <- function(x, pattern = "") {
grep(pattern, c(x$class_name(), names(x)), value = TRUE)
}
# }}}
#' @export
# $.Idf {{{
`$.Idf` <- function(x, i) {
if (i %chin% ls(x)) return(NextMethod())
private <- get_priv_env(x)
cls_id <- chmatch(i, private$idd_env()$class$class_name_us)
if (is.na(cls_id)) cls_id <- chmatch(i, private$idd_env()$class$class_name)
# skip if not a valid IDD class name
if (is.na(cls_id)) return(NextMethod())
# skip if not an existing IDF class name
if (!cls_id %in% private$idf_env()$object$class_id) return(NextMethod())
cls_nm <- private$idd_env()$class$class_name[cls_id]
if (private$idd_env()$class$unique_object[cls_id]) {
.subset2(x, "object_unique")(cls_nm)
} else {
.subset2(x, "objects_in_class")(cls_nm)
}
}
# }}}
#' @export
# [[.Idf {{{
`[[.Idf` <- function(x, i) {
if (!checkmate::test_string(i)) return(NextMethod())
if (i %chin% ls(x)) return(NextMethod())
private <- get_priv_env(x)
cls_id <- chmatch(i, private$idd_env()$class$class_name)
# skip if not a valid IDD class name
if (is.na(cls_id)) return(NextMethod())
# skip if not an existing IDF class name
if (!cls_id %in% private$idf_env()$object$class_id) return(NextMethod())
cls_nm <- private$idd_env()$class$class_name[cls_id]
if (private$idd_env()$class$unique_object[cls_id]) {
.subset2(x, "object_unique")(cls_nm)
} else {
.subset2(x, "objects_in_class")(cls_nm)
}
}
# }}}
#' @export
# $<-.Idf {{{
`$<-.Idf` <- function(x, name, value) {
# all field names start with a capital letter
if (!substr(name, 1, 1) %chin% LETTERS && name %chin% ls(x)) return(NextMethod())
self <- get_self_env(x)
private <- get_priv_env(x)
# match both normal and underscore class names
cls_id <- chmatch(name, private$idd_env()$class$class_name)
if (is.na(cls_id)) cls_id <- chmatch(name, private$idd_env()$class$class_name_us)
# skip if not a valid IDD class name
if (is.na(cls_id)) return(NextMethod())
cls_nm <- private$idd_env()$class$class_name[cls_id]
uni <- private$idd_env()$class$unique_object[cls_id]
if (uni && is_idfobject(value)) value <- list(value)
replace_objects_in_class(self, private, cls_nm, value, uni)
invisible(x)
}
# }}}
#' @export
# [[<-.Idf {{{
`[[<-.Idf` <- function(x, name, value) {
if (!checkmate::test_string(name)) return(NextMethod())
# all field names start with a capital letter
if (!substr(name, 1, 1) %chin% LETTERS && name %chin% ls(x)) return(NextMethod())
self <- get_self_env(x)
private <- get_priv_env(x)
# match only normal class names
cls_id <- chmatch(name, private$idd_env()$class$class_name)
# skip if not a valid IDD class name
if (is.na(cls_id)) return(NextMethod())
cls_nm <- private$idd_env()$class$class_name[cls_id]
uni <- private$idd_env()$class$unique_object[cls_id]
replace_objects_in_class(self, private, cls_nm, value, uni)
invisible(x)
}
# }}}
#' @export
# print.Idf {{{
print.Idf <- function(x, zoom = "class", order = TRUE, ...) {
x$print(zoom = zoom, order = order)
invisible(x)
}
# }}}
#' @export
# plot.Idf {{{
plot.Idf <- function(x, new = FALSE, render_by = "surface_type", wireframe = TRUE,
x_ray = FALSE, axis = TRUE, ...) {
x$view(new = new, render_by = render_by, axis = axis, wireframe = wireframe, x_ray = x_ray)
}
# }}}
#' @export
# str.idf {{{
str.Idf <- function(object, zoom = "class", ...) {
object$print(zoom)
}
# }}}
#' @export
# ==.Idf {{{
`==.Idf` <- function(e1, e2) {
if (!is_idf(e2)) return(FALSE)
identical(get_priv_env(e1)$uuid(), get_priv_env(e2)$uuid())
}
# }}}
#' @export
# !=.Idf {{{
`!=.Idf` <- function(e1, e2) {
Negate(`==.Idf`)(e1, e2)
}
# }}}
#' Format an Idf Object
#'
#' Format an [Idf] object.
#'
#' @param x An [Idf] object.
#' @param comment If `FALSE`, all comments will not be included. Default: `TRUE`.
#' @param header If `FALSE`, the header will not be included. Default: `TRUE`.
#' @param format Specific format used when formatting. For details, please see
#' `$save()`. Default: `eplusr_option("save_format")`
#' @param leading Leading spaces added to each field. Default: `4L`.
#' @param sep_at The character width to separate value string and field string.
#' Default: `29L` which is the same as IDF Editor.
#' @param ... Further arguments passed to or from other methods.
#' @return A single length string.
#' @examples
#' \dontrun{
#' idf_path <- system.file("extdata/1ZoneUncontrolled.idf", package = "eplusr")
#' cat(format(read_idf(idf_path, use_idd(8.8, "auto")), leading = 0))
#' }
#' @export
#' @author Hongyuan Jia
# format.idf {{{
format.Idf <- function(x, comment = TRUE, header = TRUE,
format = eplusr_option("save_format"),
leading = 4L, sep_at = 29L, ...) {
paste0(
x$to_string(comment = comment, header = header, format = format,
leading = leading, sep_at = sep_at, ...
),
collapse = "\n"
)
}
# }}}
#' Coerce an Idf object into a Character Vector
#'
#' Coerce an [Idf] object into a character vector.
#'
#' @inheritParams format.Idf
#' @return A character vector.
#' @examples
#' \dontrun{
#' idf_path <- system.file("extdata/1ZoneUncontrolled.idf", package = "eplusr")
#' as.character(read_idf(idf_path, use_idd(8.8, "auto")), leading = 0)
#' }
#' @export
#' @author Hongyuan Jia
# as.character.Idf {{{
as.character.Idf <- function(x, comment = TRUE, header = TRUE,
format = eplusr_option("save_format"),
leading = 4L, sep_at = 29L, ...) {
x$to_string(comment = comment, header = header, format = format,
leading = leading, sep_at = sep_at, ...
)
}
# }}}
#' Create an Empty Idf
#'
#' `empty_idf()` takes a valid IDD version and creates an empty [Idf] object
#' that only contains a Version object.
#'
#' @param ver Any acceptable input of [use_idd()]. If `latest`, which is the
#' default, the latest IDD released version is used.
#' @return An [Idf] object
#' @export
#' @examples
#' \dontrun{
#' if (is_avail_idd(8.8)) empty_idf(8.8)
#' }
#'
# empty_idf {{{
empty_idf <- function(ver = "latest") {
ver <- standardize_ver(ver)
text <- paste0("Version,", ver[, 1L:2L], ";\n")
read_idf(text, ver)
}
# }}}
# vim: set fdm=marker:
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.