Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,7 @@ if(BUILD_TESTING)
endif()

add_subdirectory(src/ConvertInputFormat)
add_subdirectory(src/ReadVars)

if(BUILD_FORTRAN)
include(CMakeAddFortranSubdirectory)
Expand All @@ -370,10 +371,6 @@ if(BUILD_FORTRAN)
src/ExpandObjects
PROJECT ExpandObjects
NO_EXTERNAL_INSTALL)
cmake_add_fortran_subdirectory(
src/ReadVars
PROJECT ReadVars
NO_EXTERNAL_INSTALL)
set(TRANSITION_EXTRA_FLAGS "-DCMAKE_VERSION_MAJOR:STRING=${CMAKE_VERSION_MAJOR}" "-DCMAKE_VERSION_MINOR:STRING=${CMAKE_VERSION_MINOR}"
"-DCMAKE_VERSION_PATCH:STRING=${CMAKE_VERSION_PATCH}")
cmake_add_fortran_subdirectory(
Expand Down
3 changes: 1 addition & 2 deletions cmake/Install.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ set(CPACK_INSTALL_CMAKE_PROJECTS "${PROJECT_BINARY_DIR};EnergyPlus;ALL;/")

if(BUILD_FORTRAN)
list(APPEND CPACK_INSTALL_CMAKE_PROJECTS "${PROJECT_BINARY_DIR}/src/ExpandObjects/;ExpandObjects;ALL;/")
list(APPEND CPACK_INSTALL_CMAKE_PROJECTS "${PROJECT_BINARY_DIR}/src/ReadVars/;ReadVars;ALL;/")
list(APPEND CPACK_INSTALL_CMAKE_PROJECTS "${PROJECT_BINARY_DIR}/src/Transition/;Transition;ALL;/")
list(APPEND CPACK_INSTALL_CMAKE_PROJECTS "${PROJECT_BINARY_DIR}/src/Basement/;Basement;ALL;/")
list(APPEND CPACK_INSTALL_CMAKE_PROJECTS "${PROJECT_BINARY_DIR}/src/HVAC-Diagram/;HVAC-Diagram;ALL;/")
Expand Down Expand Up @@ -699,7 +698,7 @@ include(CPackIFW)

#cpack_add_component(AuxiliaryPrograms
#DISPLAY_NAME "Auxiliary Programs"
#DESCRIPTION "The suite of Fortran auxiliary programs such as ReadVarsESO, ExpandObjects, etc"
#DESCRIPTION "The suite of auxiliary programs such as ReadVarsESO, ExpandObjects, etc"
#REQUIRED
#)

Expand Down
12 changes: 5 additions & 7 deletions cmake/ProjectMacros.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -88,23 +88,21 @@ function(ADD_SIMULATION_TEST)
set(ANNUAL_SIMULATION false)
endif()

# Note JM 2018-11-23: -r means "Call ReadVarEso", which unless you actually have BUILD_FORTRAN=TRUE shouldn't exist
# -r means "Call ReadVarsESO".
if(ANNUAL_SIMULATION)
set(ENERGYPLUS_FLAGS "${ADD_SIM_TEST_ENERGYPLUS_FLAGS} -a")
else()
set(ENERGYPLUS_FLAGS "${ADD_SIM_TEST_ENERGYPLUS_FLAGS} -D")
endif()

# Add -r flag if BUILD_FORTRAN is on, regardless of whether we run regression/performance tests
# So that it'll produce the CSV output automatically for convenience
# Preserve the historical test behavior: when auxiliary Fortran tools are enabled,
# also ask EnergyPlus to produce CSV output through ReadVarsESO.
if(BUILD_FORTRAN)
set(ENERGYPLUS_FLAGS "${ENERGYPLUS_FLAGS} -r")
else()
# Now, if you don't have BUILD_FORTRAN, but you actually need that because of regression/performance testing, we issue messages

if(ADD_SIM_TEST_PERFORMANCE)
# For performance testing, it's more problematic, because that'll cut on the ReadVarEso time
message(WARNING "Will not be able to call ReadVarEso unless BUILD_FORTRAN=TRUE, skipping flag -r.")
# For performance testing, changing whether ReadVarsESO is included changes the measured time.
message(WARNING "BUILD_FORTRAN=FALSE, preserving historical behavior and skipping automatic ReadVarsESO execution.")
endif()
endif()

Expand Down
4 changes: 2 additions & 2 deletions cmake/RunReverseDD.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,8 @@ foreach(M V IN ZIP_LISTS MODE VAR)
list(FIND ENERGYPLUS_FLAGS_LIST -r READVARS_RESULT)

if("${READVARS_RESULT}" GREATER -1)
find_program(READVARS_EXE ReadVarsESO PATHS "${PRODUCT_PATH}" NO_DEFAULT_PATH NO_CMAKE_ENVIRONMENT_PATH NO_CMAKE_PATH NO_SYSTEM_ENVIRONMENT_PATH
NO_CMAKE_SYSTEM_PATH NO_CMAKE_FIND_ROOT_PATH)
find_program(READVARS_EXE NAMES ReadVarsESO ReadVarsESO.bat PATHS "${PRODUCT_PATH}" NO_DEFAULT_PATH NO_CMAKE_ENVIRONMENT_PATH NO_CMAKE_PATH
NO_SYSTEM_ENVIRONMENT_PATH NO_CMAKE_SYSTEM_PATH NO_CMAKE_FIND_ROOT_PATH)
# Move to executable directory
execute_process(COMMAND ${CMAKE_COMMAND} -E copy_if_different "${READVARS_EXE}" "${EXE_PATH}")
endif()
Expand Down
4 changes: 2 additions & 2 deletions cmake/RunSimulation.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,8 @@ if(BUILD_FORTRAN)
list(FIND ENERGYPLUS_FLAGS_LIST -r READVARS_RESULT)

if("${READVARS_RESULT}" GREATER -1)
find_program(READVARS_EXE ReadVarsESO PATHS "${PRODUCT_PATH}" NO_DEFAULT_PATH NO_CMAKE_ENVIRONMENT_PATH NO_CMAKE_PATH NO_SYSTEM_ENVIRONMENT_PATH
NO_CMAKE_SYSTEM_PATH NO_CMAKE_FIND_ROOT_PATH)
find_program(READVARS_EXE NAMES ReadVarsESO ReadVarsESO.bat PATHS "${PRODUCT_PATH}" NO_DEFAULT_PATH NO_CMAKE_ENVIRONMENT_PATH NO_CMAKE_PATH
NO_SYSTEM_ENVIRONMENT_PATH NO_CMAKE_SYSTEM_PATH NO_CMAKE_FIND_ROOT_PATH)
# Move to executable directory
execute_process(COMMAND ${CMAKE_COMMAND} -E copy_if_different "${READVARS_EXE}" "${EXE_PATH}")
endif()
Expand Down
115 changes: 115 additions & 0 deletions design/FY2026/NFP-ReadVars-Python-Replacement.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
Replace Fortran ReadVarsESO with a Python Equivalent
================

Jason W. DeGraw, ORNL

- Original Date: 06/18/2026
- Revision Date: N/A

## Justification for New Feature ##

ReadVarsESO is a post-processing utility used to convert EnergyPlus ESO and MTR output files into delimited tabular output (primarily into comma-separated value or CSV form). The current implementation is written in modern Fortran, requiring a Fortran build dependency for a utility that is somewhat independent of the simulation engine. The utility is invoked by the engine if the `-r` flag is passed on the command line, so there is a limited relationship between the engine and the utility. Python has been a requirement for EnergyPlus development for some time now and is a somewhat easier dependency to manage.

This proposal replaces the Fortran ReadVarsESO program with a vanilla Python implementation, where "vanilla" means that there are no dependencies outside of what comes with standard Python. The goal is to preserve the existing command-line behavior for current workflows while making the tool easier to maintain, test, package, and extend. The script has outperformed the Fortran executable in all testing thus far.

## E-mail and Conference Call Conclusions ##

N/A

## Overview ##

The new ReadVarsESO implementation is a Python script distributed with EnergyPlus and wrapped by platform-appropriate launcher scripts. Existing RVI/MVI based conversion workflows should continue to work, including frequency filters, unlimited column handling, and fixed header behavior.

The Python version provides a modern subcommand-based interface for direct inspection and conversion of ESO/MTR files. Initial modern commands include listing available output variables and reading data directly to CSV without requiring a separate RVI/MVI file.

### Legacy Behavior ###

The historical ReadVarsESO interface is primarily file-oriented. When called with no arguments, it reads `eplusout.eso`, selects all available variables from the ESO data dictionary, and writes `eplusout.csv`. When called with an RVI/MVI file, the control file identifies the input ESO/MTR file, the output file, and the requested variables.

Legacy variable requests may be made by report number or by matching variable names from the data dictionary. The RVI/MVI file can also exclude variables by prefixing report numbers or variable requests with `~`. Command-line options select broad output frequencies such as detailed/timestep, hourly, daily, monthly, and annual/run-period output. Existing options also include limited versus unlimited column handling and `fixheader` behavior.

The legacy converter writes delimited output using the output file extension to determine the delimiter: comma-separated output for CSV, tab-separated output for TAB files, and space-separated output for TXT files. It also writes a `readvars.audit` file and preserves the long-standing header, timestamp, and row formatting used by existing workflows.

### Enhancements in the Python version ###

The Python replacement adds a modern subcommand interface while retaining the legacy interface. The new `list` command inspects an ESO/MTR data dictionary and reports the available report number, frequency, key, variable name, units, and legacy label. It supports table, CSV, and JSON output, plus filtering by reporting frequency and search text. Internal timestamp dictionary records, such as annual calendar-year records used only to label report periods, are omitted from the modern list output.

The new `read` command converts an ESO/MTR file directly to CSV without requiring an RVI/MVI file. Calling `ReadVarsESO read <input-file>` selects all user-reportable variables and writes a CSV file next to the input using the same base file name. The output file can be configured with `--output`, and conversion can be narrowed with the same frequency and search filters used by `list`.

The modern commands are intended to be easier to script and discover. They avoid legacy audit-file side effects where practical, provide structured output for tooling, and make it possible for users to inspect available variables before selecting data to convert.

### Performance of the Python version ###

To investigate the performance of the Python version, a test file with an existing RVI file was selected: 5ZoneDetailedIceStorage.idf. This model includes 35 output variables and the RVI has 34 entries, and an annual simulation of the model results in an ESO file that is approximately 7.3 MB in size. The Python and Fortran versions were each run ten times on an small Windows desktop system running on a Intel Core i7-8700 @ 3.20GHz processor, resulting in the following timings:

| | Fortran (ms) | Python (ms) |
|:-------:|:------------:|:-----------:|
| 1 | 4720.9429 | 924.1084 |
| 2 | 4649.1618 | 832.2611 |
| 3 | 4635.4731 | 831.3671 |
| 4 | 4647.0617 | 885.8676 |
| 5 | 5082.1201 | 840.9368 |
| 6 | 4631.1113 | 843.4879 |
| 7 | 4633.1652 | 835.8188 |
| 8 | 4719.2226 | 844.0984 |
| 9 | 4794.5117 | 843.9699 |
| 10 | 4707.4851 | 839.8434 |
| Average | 4722.02555 | 852.1759 |
| Std Dev | 137.3253939 | 29.61428 |

The Python script is roughly five times faster than the Fortran executable for this test case.

## Approach ##

The implementation ports the existing ReadVarsESO behavior to a Python script with no third-party package dependencies. CMake copies the Python script and wrapper into the EnergyPlus runtime and install locations in place of building a Fortran executable.

The legacy interface will remain available for compatibility. A modern interface will be added alongside it, including:

- `list` to inspect available report variables, keys, units, and reporting frequencies.
- `read` to convert an ESO/MTR file directly to CSV.
- Command options for output file selection and basic filtering by frequency or search text.

The modern commands avoid legacy audit-file side effects where practical and omits internal timestamp dictionary records that are not user-reportable output variables. Shared parsing and conversion routines will be used where possible so that legacy and modern behavior stay aligned.

## Testing/Validation/Data Sources ##

Testing will include focused unit or script-level tests for:

- Legacy RVI/MVI conversion compatibility.
- Listing variables and filtering metadata records.
- Listing variables in table, CSV, and JSON formats.
- Direct modern CSV conversion using the `read` command.
- Configurable output paths and basic frequency/search filtering.
- Default `read` behavior that converts all user-reportable variables when only an input file is supplied.

Existing EnergyPlus regression workflows that invoke ReadVarsESO should continue to pass using the Python wrapper.

## Input Output Reference Documentation ##

The Input Output Reference is not expected to require changes because this feature does not add, remove, or modify EnergyPlus input objects.

The auxiliary programs documentation should be updated to describe both the legacy ReadVarsESO usage and the modern `list` and `read` subcommands.

## Input Description ##

No IDD, epJSON schema, or input object changes are required.

## Outputs Description ##

No simulation output variables are added or changed.

ReadVarsESO output files should remain compatible with existing CSV, TAB, and TXT post-processing workflows for the legacy interface. The modern `read` command will produce CSV output directly, with the output file name configurable by command-line option.

## Engineering Reference ##

No engineering reference changes are expected. This is a post-processing utility replacement and does not alter simulation algorithms.

## Example File and Transition Changes ##

No example file changes are expected.

No IDF transition changes are required. Packaging and scripts that directly invoke the old executable name may need updates to call the Python wrapper, while existing command-line arguments should remain compatible through that wrapper.

## References ##

N/A
12 changes: 6 additions & 6 deletions scripts/Epl-run.bat
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,8 @@
: 5. If available Copy %1.rvi (post processor commands) into Eplusout.inp
: If available Copy %1.mvi (post processor commands) into eplusmtr.inp
: or create appropriate input to get meter output from eplusout.mtr
: 6. Execute ReadVarsESO.exe (the Post Processing Program)
: Execute ReadVarsESO.exe (the Post Processing Program) for meter output
: 6. Execute ReadVarsESO (the Post Processing Program)
: Execute ReadVarsESO (the Post Processing Program) for meter output
: 7. Copy Eplusout.* to %1.*
: 8. Clean up directory.
:
Expand Down Expand Up @@ -386,12 +386,12 @@ IF EXIST ip.err DEL ip.err
IF EXIST convert.txt DEL convert.txt
:skipConv

IF EXIST eplusout.inp "%program_path%postprocess\ReadVarsESO.exe" eplusout.inp %rvset%
IF NOT EXIST eplusout.inp "%program_path%postprocess\ReadVarsESO.exe" " " %rvset%
IF EXIST eplusmtr.inp "%program_path%postprocess\ReadVarsESO.exe" eplusmtr.inp %rvset%
IF EXIST eplusout.inp "%program_path%postprocess\ReadVarsESO.bat" eplusout.inp %rvset%
IF NOT EXIST eplusout.inp "%program_path%postprocess\ReadVarsESO.bat" " " %rvset%
IF EXIST eplusmtr.inp "%program_path%postprocess\ReadVarsESO.bat" eplusmtr.inp %rvset%
IF NOT EXIST eplusmtr.inp echo eplusout.mtr >test.mvi
IF NOT EXIST eplusmtr.inp echo eplusmtr.csv >>test.mvi
IF NOT EXIST eplusmtr.inp "%program_path%postprocess\ReadVarsESO.exe" test.mvi %rvset%
IF NOT EXIST eplusmtr.inp "%program_path%postprocess\ReadVarsESO.bat" test.mvi %rvset%
:skipReadVars

"%program_path%postprocess\HVAC-Diagram.exe"
Expand Down
12 changes: 6 additions & 6 deletions scripts/RunEPlus.bat
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,10 @@
: Run the Slab preprocessor program if necessary
: 5. Execute EnergyPlus
: 6. If available Copy %1.rvi (post processor commands) into Eplusout.inp
: 7. Execute ReadVarsESO.exe (the Post Processing Program)
: 7. Execute ReadVarsESO (the Post Processing Program)
: 8. If available Copy %1.mvi (post processor commands) into test.mvi
: or create appropriate input to get meter output from eplusout.mtr
: 9. Execute ReadVarsESO.exe (the Post Processing Program) for meter output
: 9. Execute ReadVarsESO (the Post Processing Program) for meter output
: 10. Copy Eplusout.* to %1.*
: 11. Clean up working directory.
:
Expand Down Expand Up @@ -324,12 +324,12 @@ if %maxcol%==nolimit SET rvset=unlimited
: readvars creates audit in append mode. start it off
echo %date% %time% ReadVars >readvars.audit

IF EXIST eplusout.inp %post_proc%ReadVarsESO.exe eplusout.inp %rvset%
IF NOT EXIST eplusout.inp %post_proc%ReadVarsESO.exe " " %rvset%
IF EXIST eplusmtr.inp %post_proc%ReadVarsESO.exe eplusmtr.inp %rvset%
IF EXIST eplusout.inp %post_proc%ReadVarsESO.bat eplusout.inp %rvset%
IF NOT EXIST eplusout.inp %post_proc%ReadVarsESO.bat " " %rvset%
IF EXIST eplusmtr.inp %post_proc%ReadVarsESO.bat eplusmtr.inp %rvset%
IF NOT EXIST eplusmtr.inp echo eplusout.mtr >test.mvi
IF NOT EXIST eplusmtr.inp echo eplusmtr.csv >>test.mvi
IF NOT EXIST eplusmtr.inp %post_proc%ReadVarsESO.exe test.mvi %rvset%
IF NOT EXIST eplusmtr.inp %post_proc%ReadVarsESO.bat test.mvi %rvset%
:skipReadVars

IF EXIST eplusout.bnd %post_proc%HVAC-Diagram.exe
Expand Down
4 changes: 2 additions & 2 deletions scripts/RunReadESO.bat
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
set rvpath=
if EXIST eplusout.inp goto :inp
rem produces all variables in .eso file to .csv
%rvpath%readvarseso.exe
%rvpath%ReadVarsESO.bat
goto :done
:inp
rem reads variable specifications from input file
%rvpath%ReadVarsESO.exe eplusout.inp
%rvpath%ReadVarsESO.bat eplusout.inp
:done
set rvpath=

29 changes: 23 additions & 6 deletions src/EnergyPlus/CommandLineInterface.cc
Original file line number Diff line number Diff line change
Expand Up @@ -939,18 +939,35 @@ state.dataStrGlobals->inputFilePath='{:g}',

int runReadVarsESO(EnergyPlusData &state)
{
fs::path readVarsPath = (state.dataStrGlobals->exeDirectoryPath / "ReadVarsESO").replace_extension(FileSystem::exeExtension);
auto findReadVarsPath = [](fs::path const &directory) -> fs::path {
std::vector<fs::path> candidates;
#ifdef _WIN32
candidates.emplace_back(directory / "ReadVarsESO.bat");
candidates.emplace_back(directory / "ReadVarsESO.exe");
#else
candidates.emplace_back(directory / "ReadVarsESO");
#endif
for (auto const &candidate : candidates) {
if (FileSystem::fileExists(candidate)) {
return candidate;
}
}
return {};
};

fs::path readVarsPath = findReadVarsPath(state.dataStrGlobals->exeDirectoryPath);

if (!FileSystem::fileExists(readVarsPath)) {
readVarsPath = (state.dataStrGlobals->exeDirectoryPath / "PostProcess" / "ReadVarsESO").replace_extension(FileSystem::exeExtension);
if (!FileSystem::fileExists(readVarsPath)) {
if (readVarsPath.empty()) {
readVarsPath = findReadVarsPath(state.dataStrGlobals->exeDirectoryPath / "PostProcess");
if (readVarsPath.empty()) {
// should report the error differently if the user is calling into E+ through EXE or DLL
if (state.dataGlobal->eplusRunningViaAPI) {
DisplayString(
state,
"ERROR: Could not find ReadVarsESO executable. When calling through C API, make sure to call setEnergyPlusRootDirectory");
"ERROR: Could not find ReadVarsESO program. When calling through C API, make sure to call setEnergyPlusRootDirectory");
} else {
DisplayString(state, std::format("ERROR: Could not find ReadVarsESO executable: {}.", FileSystem::getAbsolutePath(readVarsPath)));
DisplayString(state, std::format("ERROR: Could not find ReadVarsESO program under: {}.",
FileSystem::getAbsolutePath(state.dataStrGlobals->exeDirectoryPath)));
}
return static_cast<int>(ReturnCodes::Failure);
}
Expand Down
2 changes: 1 addition & 1 deletion src/EnergyPlus/FileSystem.cc
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ namespace FileSystem {
// Note: on Windows, system(command) will already send the command through "cmd /C command"
// cf C:\Program Files (x86)\Windows Kits\10\Source\10.0.17763.0\ucrt\exec
// Ends up calling something that looks like the following:
// cmd /C ""C:\path\to\ReadVarsESO.exe" "A folder with spaces\1ZoneUncontrolled.mvi" unlimited"
// cmd /C ""C:\path\to\ReadVarsESO.bat" "A folder with spaces\1ZoneUncontrolled.mvi" unlimited"
// On Windows, system() already returns the launched process's exit code directly.
return system(("\"" + command + "\"").c_str());
#else
Expand Down
Loading
Loading