-
Notifications
You must be signed in to change notification settings - Fork 14
OpenStudio Model Setup
To use OpenStudio models with Alfalfa, it must be within an OpenStudio Workflow (OSW). If a user just has an OpenStudio Model (OSM), this section will show how a simple OSW can be created that utilizes the user's OSM and one OpenStudio measure (described in detail here) for Alfalfa. If a user already has an OSW, they can integrate the measure described in detail here into their existing workflow.
This measure modifies an OpenStudio model by creating readable and writeable points in that OpenStudio Model so that Alfalfa can create its ExternalInterface
objects. This DOES NOT define how the appropriate tags should be applied to 'type' something. That is TODO.
The following steps should be taken in the OpenStudio model in order to expose readable points from simulation to Alfalfa to acquire ExternalInterface
objects needed for co-simulation, as well as Project Haystack points with tags associated with the readable points:
- Create an
Output:Variable
object for the simulation point whose value you wish to read - Set the
Output:Variable
object attributes including name, reporting frequency, and key value as required - Set the
exportToBCVTB
method to true
A ruby measure method for this is as follows:
def create_output(model, var, key, name, freq)
# create reporting output variable
new_var = OpenStudio::Model::OutputVariable.new(
var, # from variable dictionary (eplusout.rdd)
model
)
new_var.setName(name)
new_var.setReportingFrequency(freq) # Detailed, Timestep, Hourly, Daily, Monthly, RunPeriod, Annual
new_var.setKeyValue(key) # from variable dictionary (eplusout.rdd)
new_var.setExportToBCVTB(true)
end
The following steps should be taken in the OpenStudio model in order to expose writeable points in simulation to Alfalfa in order to acquire ExternalInterface
objects needed for co-simulation, as well as Project Haystack points and tags associated with the writeable points.
- Create two (2)
EnergyManagementSystem:GlobalVariable
objects for the simulation actuator point you wish to expose - Set the name of the first object (e.g.
Fan_Mass_Flow
) - Set the name of the second object the same as the first but include
_Enable
(e.g.Fan_Mass_Flow_Enable
) - Set
exportToBCVTB
method to true for both
A ruby measure method for this is as follows:
def create_input(model, name, freq)
# create global variable
global_var = OpenStudio::Model::EnergyManagementSystemGlobalVariable.new(
model,
name
)
global_var.setExportToBCVTB(true)
# create EMS output variable of global variable
ems_out_var = OpenStudio::Model::EnergyManagementSystemOutputVariable.new(
model,
global_var
)
ems_out_var.setName(name + '_EMS_Value')
ems_out_var.setUpdateFrequency('SystemTimestep')
# create reporting output variable of EMS output variable of global variable
global_out_var = OpenStudio::Model::OutputVariable.new(
ems_out_var.nameString(),
model
)
global_out_var.setName(name + '_Value')
global_out_var.setReportingFrequency(freq) # Detailed, Timestep, Hourly, Daily, Monthly, RunPeriod, Annual
global_out_var.setKeyValue('EMS')
global_out_var.setExportToBCVTB(true)
# create enable of global variable
global_var_enable = OpenStudio::Model::EnergyManagementSystemGlobalVariable.new(
model,
name + "_Enable"
)
global_var_enable.setExportToBCVTB(true)
# create EMS output variable of enable global variable
ems_out_var_enable = OpenStudio::Model::EnergyManagementSystemOutputVariable.new(
model,
global_var_enable
)
ems_out_var_enable.setName(name + '_Enable_EMS_Value')
ems_out_var_enable.setUpdateFrequency('SystemTimestep')
# create reporting output variable of EMS output variable of enable global variable
global_out_var_enable = OpenStudio::Model::OutputVariable.new(
ems_out_var_enable.nameString(),
model
)
global_out_var_enable.setName(name + '_Enable_Value')
global_out_var_enable.setReportingFrequency(freq) # Detailed, Timestep, Hourly, Daily, Monthly, RunPeriod, Annual
global_out_var_enable.setKeyValue('EMS')
global_out_var_enable.setExportToBCVTB(true)
end
The EMS feature in EnergyPlus provides a way to develop custom control and modeling routines for EnergyPlus models. Typically, a user will want to write EnergyPlus EMS Programs that change the operation of a model based on the value of the writeable point (Alfalfa input) via EMS actuators. EMS provides high-level, supervisory control to override selected aspects of EnergyPlus modeling. A small programming language called EnergyPlus Runtime Language (ERL) is used to describe the control algorithms. EnergyPlus interprets and executes the ERL program as the model is being run.
The following step should be taken in the OpenStudio model EnergyPlus EMS Programs and Subroutines that reference this writeable point:
- EnergyPlus EMS Programs and Subroutines should reference the
<NAME>_Enable
variable by String name whose Boolean value (True or False) dictates whether or not EnergyPlus should write the value from Alfalfa to the associated EnergyPlus EMS actuator. For example:
IF Fan_Mass_Flow_enable == 1
SET EMS_fanmassflow_actuator = Fan_Mass_Flow
ELSE,
SET EMS_fanmassflow_actuator = NULL
ENDIF,
Where 1 is True and 0 is False.
Often, a user will want to test their EnergyPlus EMS Programs locally (e.g., run the OSW stand-alone). This can be helpful not only to make sure the workflow completes successfully but also to verify that the EMS program is operating as desired. However, the writeable points (Alfalfa inputs) described above will not have an initialized value if the OSW is run outside of Alfalfa. To alleviate this issue, a user can expand the create_input
method above to write small EMS programs that initialize the variables and allow the OSW to be run stand-alone without crashing.
def create_input(model, name, freq, init_val)
# [...]
# same as create_input method above
# [...]
# add EMS initialization program
init_prgm = OpenStudio::Model::EnergyManagementSystemProgram.new(model)
init_prgm.setName('Init_' + name)
init_prgm.addLine("SET #{name} = #{init_val}")
init_prgm.addLine("SET #{name}_Enable = 1")
# add EMS initialization program calling manager
init_prgm_mngr = OpenStudio::Model::EnergyManagementSystemProgramCallingManager.new(model)
init_prgm_mngr.setName('Init ' + name)
init_prgm_mngr.setCallingPoint('BeginNewEnvironment')
init_prgm_mngr.addProgram(init_prgm)
end
These initializations will NOT extend to Alfalfa; instead, Alfalfa will initialize the values to nil
and the <NAME>_Enable
to 0
. If the user sets the value of a writeable point (Alfalfa input) the <NAME>_Enable
will automatically be set to 1
.
If your model includes python measures which have package dependencies you need to tell alfalfa what the dependencies are so that it can install them for your project. To do this you just need to create a python requirements file called requirements.txt
Contents of an example requirements.txt (most basic is just the names of each of your packages).
sklearn
pysindy
CoolProp
Note: you may be running a different version of python from alfalfa so specifying exact versions of packages may not work properly.
For EnergyPlus, a Python Plugin is a user-defined Python class that overrides a premade EnergyPlusPlugin base class. Thus, a user plugin should always include:
from pyenergyplus.plugin import EnergyPlusPlugin
Including this means that when EnergyPlus processes the Python file, it will always find this imported base class. Then create a derived class that inherits this base class, and write custom functions. This will be illustrated with a minimal example. A minimal plugin does not actually have to interact with EnergyPlus. It is typical for a plugin to read data from output variables and write data with actuators, but it is not necessary. Consider this:
from pyenergyplus.plugin import EnergyPlusPlugin
class MinimalPlugin(EnergyPlusPlugin):
def on_begin_timestep_before_predictor(self, state):
return 0
The user will want to create a directory named resources
at the same level as their measure.rb
file. Inside that directory, the user will want to add their EnergyPlus Python file (could be minimal example script above, but more typically with reading data from output variables and writing data with actuators).
The OpenStudio (EnergyPlus) measure to would be:
# start the measure
class PythonEMS < OpenStudio::Ruleset::WorkspaceUserScript
# human readable name
def name
return 'Python EMS'
end
# human readable description
def description
return 'Add python EMS to IDF'
end
# human readable description of modeling approach
def modeler_description
return 'Add python EMS to IDF'
end
# define the arguments that the user will input
def arguments(ws)
args = OpenStudio::Ruleset::OSArgumentVector.new
# argument for python script name
py_name = OpenStudio::Ruleset::OSArgument.makeStringArgument(
'py_name',
true
)
py_name.setDisplayName('Python Script Name')
py_name.setDescription('Name of script with extension (e.g., myPlugin.py)')
args << py_name
return args
end
# define what happens when the measure is run
def run(ws, runner, usr_args)
# call the parent class method
super(ws, runner, usr_args)
# use the built-in error checking
return false unless runner.validateUserArguments(
arguments(ws),
usr_args
)
# assign the user inputs to variables
py_name = runner.getStringArgumentValue(
'py_name',
usr_args
)
# define python script dir
py_dir = "#{__dir__}/resources"
# make sure python script exists
unless File.exist?("#{py_dir}/#{py_name}")
runner.registerError("Could not find file at #{py_dir}/#{py_name}.")
return false
end
# add python plugin search paths
n = OpenStudio::IdfObject.new('PythonPlugin_SearchPaths'.to_IddObjectType)
n.setString(0, 'Python Plugin Search Paths')
n.setString(1, 'Yes')
n.setString(2, 'Yes')
# set site packages location depending on operating system
if (RUBY_PLATFORM =~ /linux/) != nil
n.setString(3, '/usr/local/lib/python3.7/dist-packages')
elsif (RUBY_PLATFORM =~ /darwin/) != nil
n.setString(3, '/usr/local/lib/python3.7/site-packages')
elsif (RUBY_PLATFORM =~ /cygwin|mswin|mingw|bccwin|wince|emx/) != nil
h = ENV['USERPROFILE'].gsub('\\', '/')
n.setString(3, "#{h}/AppData/Local/Programs/Python/Python37/Lib/site-packages")
end
# add python dir
n.setString(4, py_dir)
ws.addObject(n)
# add python plugin instance
n = OpenStudio::IdfObject.new('PythonPlugin_Instance'.to_IddObjectType)
n.setString(0, 'Python Plugin Instance Name')
n.setString(1, 'No')
n.setString(2, py_name.sub('.py', ''))
n.setString(3, 'PluginClassName')
ws.addObject(n)
end
end
# register the measure to be used by the application
PythonEMS.new.registerWithApplication
The run method of the measure would then be something like:
def run(model, runner, user_args)
# call the parent class method
super(model, runner, user_args)
# alfalfa outputs
create_output(
model,
"Facility Total Purchased Electricity Energy", # from variable dictionary (eplusout.rdd)
"Whole Building", # from report data dictionary (eplusout.rdd)
"MyElectricMeter",
"Timestep" # Detailed, Timestep, Hourly, Daily, Monthly, RunPeriod, Annual
)
# alfalfa inputs
create_input(
model,
"MyInput",
"Timestep" # Detailed, Timestep, Hourly, Daily, Monthly, RunPeriod, Annual
)
return true
end
Alfalfa's OpenStudio support is exclusively through an OpenStudio Workflow (OSW). An OSW can be designed using the OpenStudio application or by correctly defining a directory structure with the necessary components. For those familiar with creating workflows, this is the standard directory structure. It generally takes the form:
- path/to/my_model.osm
- path/to/my_model/workflow.osw
- path/to/my_model/measures/…[insert measure directories]
- path/to/my_model/files/desired_weather_file.epw
An example of the OSW is also provided below, with the steps being the measure described above:
{
"seed_file" : "myModel.osm",
"weather_file": "USA_CO_Denver.Intl.AP.725650_TMY3.epw",
"measure_paths": [
"./measures/"
],
"file_paths": [
"./weather/"
],
"run_directory": "./run/",
"steps" : [
{
"measure_dir_name": "alfalfa_measure",
"name": "Alfalfa Measure",
"description": "Add variable(s) for Alfalfa",
"modeler_description": "Add EMS global variable(s) required for Alfalfa"
}
]
}
For existing workflows, simply add the measure described above to your measures directory and the step to your OSW (preferably as one of the first measures in the workflow).
To incorporate the Python measure example above, insert this into your OSW:
{
"measure_dir_name" : "python_ems",
"name" : "Python EMS",
"description" : "Add python EMS to IDF",
"modeler_description" : "Add python EMS to IDF",
"arguments" : {
"py_name" : "myPlugin.py"
}
}
Before uploading to Alfalfa, it is a good idea to test the OSW locally. Typically, an EnergyPlus EMS Program that is meant to interface with Alfalfa will not 'do' anything when being tested locally, as the actuators in the program are set up to respond to changes in writable point (Alfalfa input) values. To navigate around this, placeholder EMS programs can be written that are only for local testing. This can be accomplished by adding temporary code to the run
method above that will change the value of the writable point (Alfalfa input) values based on some arbitrary input (such as time of day or outdoor temperature).
def run(model, runner, user_args)
# [...]
# same as run method above
# [...]
# add EMS test program
test_prgm = OpenStudio::Model::EnergyManagementSystemProgram.new(model)
test_prgm.setName('Test_Pgm')
test_prgm.addLine('SET hr = HOUR') # HOUR is built-in EMS variable with a value of 0–23 (whole hours only)
test_prgm.addLine('IF ((hr >= 12) && (hr < 13))') # between noon and one
test_prgm.addLine('SET <NAME> = <VALUE1>') # writable point (Alfalfa input)
test_prgm.addLine('ELSE')
test_prgm.addLine('SET <NAME> = <VALUE2>') # writable point (Alfalfa input)
test_prgm.addLine('ENDIF')
# add EMS test program calling manager
test_prgm_mngr = OpenStudio::Model::EnergyManagementSystemProgramCallingManager.new(model)
test_prgm_mngr.setName('Test_Mgr')
test_prgm_mngr.setCallingPoint('BeginTimestepBeforePredictor')
test_prgm_mngr.addProgram(test_prgm)
return true
end
Set <VALUE1>
and <VALUE2>
as desired and run the OSW locally, typically with a command like:
/path/to/openstudio_binary run --workflow /path/to/my.osw
Then verify that the model behaved as desired, typically by confirming with a set of timeseries EnergyPlus Output:Variable
objects from the EnergyPlus variable dictionary (eplusout.rdd). Once local testing is complete, make sure to remove this temporary code from the run
method otherwise collisions with Alfalfa may occur.
Once the workflow has been setup, the model and its associated files simply need to be zipped together. To do this, select the following:
- The measure directory containing the measure(s)
- the measure described above if the user only has an OpenStudio model
- all the measures (including the measure described above) if the user already has an OpenStudio workflow
- The weather directory containing the weather file
- The OpenStudio model (.osm)
- The OpenStudio workflow (.osw)
Then (typically) right click, and the operating system will provide options for compressing. This zipped (.zip) folder can then be directly uploaded to Alfalfa.
- Getting Started with Model Measures Part 1: Creating Inputs and Outputs
- Getting Started with Model Measures Part 2: Creating Actuators
- Getting Started with EnergyPlus Measures Part 1: Creating Inputs and Outputs
- Getting Started with EnergyPlus Measures Part 2: Creating Actuators
- How to Configure an OpenStudio Model
- How to Configure Measures for Use with Alfalfa Ruby Gem
- How to Create Inputs and Outputs With Measures
- How to Run URBANopt Output Models in Alfalfa
- How to Migrate EnergyPlus Python Plugins
- How to Integrate Python based Electric Vehicle Models with OpenStudio Workflows
- How to Locally Test OpenStudio Models
- Required Structure of OpenStudio Workflow
- List of Automatically Generated Energyplus Points
- Alfalfa EnergyPlus Mixin Methods
- Getting Started with Uploading and Running a Model Using Python
- Getting Started with Uploading and Running a Model Using the UI
- How to Install Alfalfa Client
- How to Preprocess and Upload a Model
- How to Step Through a Simulation
- How to View Historical Data in Grafana
- How to Configure an Alias
- How to Troubleshoot Models