Skip to content

Commit

Permalink
Merge pull request #183 from ecobee/dev
Browse files Browse the repository at this point in the history
pre-release 0.4.1
  • Loading branch information
tomstesco authored Apr 30, 2021
2 parents e2a352a + a538f1e commit 562fd28
Show file tree
Hide file tree
Showing 18 changed files with 256 additions and 124 deletions.
6 changes: 4 additions & 2 deletions .env.template
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# docker and package info:
PACKAGE_NAME=building-controls-simulator
VERSION_TAG=0.4.0-alpha
VERSION_TAG=0.4.1-alpha
DOCKERHUB_REPOSITORY=tstesco
MAINTAINER=tom.stesco@gmail.com
USER_NAME=bcs
Expand Down Expand Up @@ -42,6 +42,8 @@ SIMULATION_EPW_DIR=${WEATHER_DIR}/simulation_epw
JUPYTER_LOG_DIR=${DOCKER_HOME_DIR}/jupyter_lab_logs
LOCAL_CACHE_DIR=${DOCKER_PACKAGE_DIR}/data
TEST_DIR=
ACADOS_SOURCE_DIR=${ACADOS_DIR}
BLASFEO_MAIN_FOLDER="${EXT_DIR}/blasfeo"
HPIPM_MAIN_FOLDER="${EXT_DIR}/hpipm"
ACADOS_DIR=${EXT_DIR}/acados
ACADOS_SOURCE_DIR=${ACADOS_DIR}
LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:${HPIPM_MAIN_FOLDER}/lib:${BLASFEO_MAIN_FOLDER}/lib:${ACADOS_DIR}/lib
58 changes: 45 additions & 13 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,12 @@ RUN sudo apt-get update && sudo apt-get upgrade -y \
xz-utils \
zlib1g-dev \
unzip \
python2.7 \
python3-dev \
python3-distutils \
subversion \
p7zip-full \
bc \
gfortran \
&& sudo rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

# install nodejs and npm (for plotly)
Expand All @@ -75,12 +75,11 @@ RUN sudo apt-get update && sudo apt-get upgrade -y \
# install FMUComplianceChecker
# install EnergyPlusToFMU
# download and extract PyFMI release
# note: PyFMI 2.7.4 is latest release that doesnt require Assimulo which is unnecessary
# because we dont use builtin PyFMI ODE simulation capabilities
RUN curl -sL https://deb.nodesource.com/setup_12.x | sudo bash - \
&& sudo apt-get update && sudo apt-get install -y nodejs \
&& curl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer | bash \
&& pyenv update && pyenv install 3.8.6 \
&& pyenv update && pyenv install 3.8.9 \
&& mkdir "${LIB_DIR}" && mkdir "${EXT_DIR}" \
&& cd "${EXT_DIR}" \
&& wget "https://github.com/modelon-community/fmi-library/archive/2.2.3.zip" \
Expand All @@ -97,13 +96,38 @@ RUN curl -sL https://deb.nodesource.com/setup_12.x | sudo bash - \
&& mv "FMUChecker-2.0.4-linux64" "FMUComplianceChecker" \
&& mkdir "fmu" \
&& cd "${EXT_DIR}" \
&& wget "https://github.com/lbl-srg/EnergyPlusToFMU/archive/v3.0.0.zip" \
&& unzip "v3.0.0.zip" && rm "v3.0.0.zip" \
&& wget "https://github.com/lbl-srg/EnergyPlusToFMU/archive/refs/tags/v3.1.0.zip" \
&& unzip "v3.1.0.zip" && rm "v3.1.0.zip" \
# install sundials 4.1.0 is latest supported (dep of assimulo)
&& cd "${EXT_DIR}" \
&& wget "https://github.com/modelon-community/PyFMI/archive/PyFMI-2.7.4.tar.gz" \
&& tar -xzf "PyFMI-2.7.4.tar.gz" \
&& mv "${EXT_DIR}/PyFMI-PyFMI-2.7.4" "${EXT_DIR}/PyFMI" \
&& rm -rf "${EXT_DIR}/PyFMI-PyFMI-2.7.4" "PyFMI-2.7.4.tar.gz" \
&& wget "https://github.com/LLNL/sundials/releases/download/v4.1.0/sundials-4.1.0.tar.gz" \
&& tar -xzf "sundials-4.1.0.tar.gz" && rm "sundials-4.1.0.tar.gz" \
&& cd "sundials-4.1.0" \
&& mkdir "build" \
&& cd "build" \
&& cmake -DCMAKE_INSTALL_PREFIX="${EXT_DIR}/sundials" .. \
&& make install \
# intsall lapack and blas (dep of assimulo)
&& cd "${EXT_DIR}" \
&& wget "https://github.com/Reference-LAPACK/lapack/archive/refs/tags/v3.9.1.tar.gz" \
&& tar -xzf "v3.9.1.tar.gz" && rm "v3.9.1.tar.gz" \
&& cd "lapack-3.9.1" \
&& mkdir build \
&& cd "build" \
&& cmake -DCMAKE_INSTALL_PREFIX="${EXT_DIR}/lapack" .. \
&& cmake --build . -j --target install \
# get Assimulo source
&& cd "${EXT_DIR}" \
&& wget "https://github.com/modelon-community/Assimulo/archive/refs/tags/Assimulo-3.2.5.tar.gz" \
&& tar -xzf "Assimulo-3.2.5.tar.gz" && rm "Assimulo-3.2.5.tar.gz" \
&& mv "${EXT_DIR}/Assimulo-Assimulo-3.2.5" "${EXT_DIR}/Assimulo-3.2.5" \
# get PyFMI source
&& cd "${EXT_DIR}" \
&& wget "https://github.com/modelon-community/PyFMI/archive/refs/tags/PyFMI-2.8.6.tar.gz" \
&& tar -xzf "PyFMI-2.8.6.tar.gz" && rm "PyFMI-2.8.6.tar.gz"\
&& mv "${EXT_DIR}/PyFMI-PyFMI-2.8.6" "${EXT_DIR}/PyFMI" \
# make PACKAGE_DIR and cleanup
&& cd "${LIB_DIR}" \
&& mkdir "${PACKAGE_DIR}" \
&& sudo rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

Expand All @@ -114,21 +138,27 @@ COPY ./ "${PACKAGE_DIR}"
# copied directory will not have user ownership by default
# install energyplus versions desired in `scripts/setup/install_ep.sh`
# install python dev environment
# copy .bashrc file to user home for use on startup. This can be further configured by user.
RUN sudo chown -R "${USER_NAME}" "${PACKAGE_DIR}" \
&& cd "${PACKAGE_DIR}" \
&& mv "${PACKAGE_DIR}/.vscode" "${LIB_DIR}/.vscode" \
&& sudo chmod +x "./scripts/setup/install_ep.sh" \
&& sudo ./scripts/setup/install_ep.sh "${ENERGYPLUS_INSTALL_DIR}" \
&& cd "${PACKAGE_DIR}" \
&& ${PYENV_ROOT}/versions/3.8.6/bin/python3.8 -m venv "${LIB_DIR}/${VENV_NAME}" \
&& ${PYENV_ROOT}/versions/3.8.9/bin/python3.8 -m venv "${LIB_DIR}/${VENV_NAME}" \
&& . "${LIB_DIR}/${VENV_NAME}/bin/activate" \
&& pip install --no-cache-dir --upgrade setuptools pip \
&& pip install --no-cache-dir -r "requirements.txt" \
# && pip install --no-cache-dir -r "requirements_unfixed.txt" \
# && pip install --no-cache-dir -r "requirements.txt" \
&& pip install --no-cache-dir -r "requirements_unfixed.txt" \
# install bcs
&& pip install --editable . \
# install Assimulo (dep of PyFMI 2.8+)
&& cd "${EXT_DIR}/Assimulo-3.2.5" \
&& python setup.py install --sundials-home="${HOME}/sundials" --blas-home="${HOME}/lapack/lib" --lapack-home="${HOME}/lapack" \
# install PyFMI
&& cd "${EXT_DIR}/PyFMI" \
&& python "setup.py" install --fmil-home="${FMIL_HOME}" \
&& cd "${PACKAGE_DIR}" \
&& . "scripts/setup/install_solvers.sh" \
&& cd "${EXT_DIR}" \
&& wget "https://github.com/RJT1990/pyflux/archive/0.4.15.zip" \
&& unzip "0.4.15.zip" && rm "0.4.15.zip" \
Expand All @@ -137,6 +167,7 @@ RUN sudo chown -R "${USER_NAME}" "${PACKAGE_DIR}" \

# install jupyter lab extensions for plotly
# if jupyter lab build fails with webpack optimization, set --minimize=False
# copy .rc files to user home for use on startup. This can be further configured by user.
RUN cd "${PACKAGE_DIR}" \
&& . "${LIB_DIR}/${VENV_NAME}/bin/activate" \
&& export NODE_OPTIONS="--max-old-space-size=8192" \
Expand All @@ -146,6 +177,7 @@ RUN cd "${PACKAGE_DIR}" \
&& jupyter lab build --dev-build=False --minimize=True \
&& unset NODE_OPTIONS \
&& cp "${PACKAGE_DIR}/scripts/setup/.bashrc" "$HOME/.bashrc" \
&& cp "${PACKAGE_DIR}/scripts/setup/.pdbrc" "$HOME/.pdbrc" \
&& chmod +x "${PACKAGE_DIR}/scripts/setup/jupyter_lab_bkgrnd.sh"

WORKDIR "${LIB_DIR}"
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,7 @@ Several dependencies are installed from source so these must be removed from the
```
PyFMI
pyflux
Assimulo
hpipm-python
```
Expand Down
3 changes: 0 additions & 3 deletions dev_docker.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,4 @@ docker-compose run \
--rm \
--service-ports \
--volume=${LOCAL_PACKAGE_DIR}:${DOCKER_PACKAGE_DIR}:consistent\
--volume=${LOCAL_CONTROLLER_DIR}:${DOCKER_CONTROLLER_DIR}:consistent \
--volume=${LOCAL_THERMAL_DIR}:${DOCKER_THERMAL_DIR}:consistent \
--volume=/Users/tom.s/.config/gcloud:${DOCKER_HOME_DIR}/.config/gcloud:ro \
building-controls-simulator bash
6 changes: 3 additions & 3 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@ defusedxml==0.7.1
docutils==0.16
entrypoints==0.3
eppy==0.5.56
flake8==3.9.0
fsspec==0.9.0
flake8==3.9.1
fsspec==2021.4.0
future==0.18.2
gcsfs==0.8.0
google-api-core==1.26.3
google-api-python-client==2.2.0
google-auth==1.28.1
google-auth==1.29.0
google-auth-httplib2==0.1.0
google-auth-oauthlib==0.4.4
google-cloud-bigquery==2.3.1
Expand Down
3 changes: 3 additions & 0 deletions scripts/setup/.pdbrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import IPython
# Interactive shell
alias interacti IPython.embed()
6 changes: 4 additions & 2 deletions scripts/setup/install_solvers.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
# https://github.com/giaf/hpipm
# https://github.com/giaf/blasfeo

if [ -z "${BLASFEO_MAIN_FOLDER}" ]; then;
if [ -z "${BLASFEO_MAIN_FOLDER}" ]; then
BLASFEO_MAIN_FOLDER="${EXT_DIR}/blasfeo"
fi

if [ -z "${HPIPM_MAIN_FOLDER}" ]; then;
if [ -z "${HPIPM_MAIN_FOLDER}" ]; then
HPIPM_MAIN_FOLDER="${EXT_DIR}/hpipm"
fi

Expand Down Expand Up @@ -36,3 +36,5 @@ echo "BLASFEO_MAIN_FOLDER=$BLASFEO_MAIN_FOLDER"
# export LD_LIBRARY_PATH
export LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${HPIPM_MAIN_FOLDER}/lib:${BLASFEO_MAIN_FOLDER}/lib"
echo "LD_LIBRARY_PATH=$LD_LIBRARY_PATH"

cd "${PACKAGE_DIR}"
3 changes: 1 addition & 2 deletions scripts/setup/jupyter_lab.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,8 @@ if [ ! -d "${JUPYTER_LOG_DIR}" ]; then mkdir "${JUPYTER_LOG_DIR}"; fi
echo "jupyter-lab accessable at: http://localhost:8888/lab"
echo "jupyter-lab logs are being stored in: ${JUPYTER_LOG_DIR}/${FNAME}"

cd "${LIB_DIR}"
. "${LIB_DIR}/${VENV_NAME}/bin/activate"
jupyter-lab --ip="0.0.0.0" --no-browser > "${JUPYTER_LOG_DIR}/${FNAME}"

echo "$!" > "${JUPYTER_LOG_DIR}/JUPYTER_SERVER_PID.txt"

set +eu +o pipefail
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# Semantic Versioning (https://semver.org/)
_MAJOR_VERSION = "0"
_MINOR_VERSION = "4"
_PATCH_VERSION = "0"
_PATCH_VERSION = "1"

_VERSION_SUFFIX = "alpha"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ class EnergyPlusBuildingModel(BuildingModel):
heat_on = attr.ib(default=False)
cool_on = attr.ib(default=False)

model_creation_step = attr.ib(default=True)

# for reference on how attr defaults wor for mutable types (e.g. list) see:
# https://www.attrs.org/en/stable/init.html#defaults
input_states = attr.ib()
Expand Down Expand Up @@ -124,7 +126,9 @@ def init_fmu_name(self):
@property
def fmu_name(self):
if not self.epw_path:
raise ValueError("Cannot name FMU without specifying weather file.")
raise ValueError(
"Cannot name FMU without specifying weather file."
)

# the full fmu name is unique per combination of:
# 1. IDF file
Expand Down Expand Up @@ -187,15 +191,17 @@ def create_model_fmu(
return self.fmu_path

def call_energy_plus_to_fmu(self):
cmd = f"python2.7 {self.eplustofmu_path}"
cmd = f"python {self.eplustofmu_path}"
cmd += f" -i {self.idf.idd_path}"
cmd += f" -w {self.epw_path}"
cmd += f" -a {self.fmi_version}"
cmd += f" -d {self.idf.idf_prep_path}"

proc = subprocess.run(shlex.split(cmd), stdout=subprocess.PIPE)
if not proc.stdout:
raise ValueError(f"Empty STDOUT. Invalid EnergyPlusToFMU cmd={cmd}")
raise ValueError(
f"Empty STDOUT. Invalid EnergyPlusToFMU cmd={cmd}"
)

# EnergyPlusToFMU puts fmu in cwd always, move out of cwd
shutil.move(
Expand All @@ -214,7 +220,9 @@ def initialize(
):
""""""
logger.info(f"Initializing EnergyPlusBuildingModel: {self.fmu_path}")
self.allocate_output_memory(t_start, t_end, t_step, data_spec, categories_dict)
self.allocate_output_memory(
t_start, t_end, t_step, data_spec, categories_dict
)
self.init_step_output()
self.fmu = pyfmi.load_fmu(fmu=self.fmu_path)
# EnergyPlusToFMU requires that FMU is initialized for multiples of
Expand Down Expand Up @@ -279,7 +287,9 @@ def allocate_output_memory(

# add fmu state variables
self.fmu_output[STATES.STEP_STATUS] = np.full(n_s, False, dtype="bool")
self.fmu_output[STATES.SIMULATION_TIME] = self.output[STATES.SIMULATION_TIME]
self.fmu_output[STATES.SIMULATION_TIME] = self.output[
STATES.SIMULATION_TIME
]

for k, v in self.idf.output_spec.items():
(
Expand Down Expand Up @@ -308,7 +318,9 @@ def do_step(
self.current_t_start = t_start

if not step_control_input:
raise ValueError("step_control_input={step_control_input} is empty.")
raise ValueError(
"step_control_input={step_control_input} is empty."
)

# integrate over simulation step in building model steps
# e.g. 300s simulation step in 5x 60s building model steps
Expand Down Expand Up @@ -346,22 +358,26 @@ def update_output(self, status, step_sensor_input):
self.output[STATES.THERMOSTAT_HUMIDITY][
self.current_t_idx
] = Conversions.relative_humidity_from_dewpoint(
temperature=self.output[STATES.THERMOSTAT_TEMPERATURE][self.current_t_idx],
temperature=self.output[STATES.THERMOSTAT_TEMPERATURE][
self.current_t_idx
],
dewpoint=self.get_tstat_dewpoint(),
)

# pass through motion
self.output[STATES.THERMOSTAT_MOTION][self.current_t_idx] = step_sensor_input[
STATES.THERMOSTAT_MOTION
]
self.output[STATES.THERMOSTAT_MOTION][
self.current_t_idx
] = step_sensor_input[STATES.THERMOSTAT_MOTION]

# get step_output
for state in self.output_states:
self.step_output[state] = self.output[state][self.current_t_idx]

def get_fmu_output_keys(self, eplus_key):
return [
k for k, v in self.idf.output_spec.items() if v["eplus_name"] == eplus_key
k
for k, v in self.idf.output_spec.items()
if v["eplus_name"] == eplus_key
]

def get_mean_temperature(self):
Expand All @@ -382,14 +398,18 @@ def get_rs_temperature(self):

def get_tstat_dewpoint(self):
"""tstat temperature is air temperature from zone containing tstat"""
fmu_name = f"{self.idf.thermostat_zone}_zone_mean_air_dewpoint_temperature"
fmu_name = (
f"{self.idf.thermostat_zone}_zone_mean_air_dewpoint_temperature"
)
return self.fmu_output[fmu_name][self.current_t_idx]

def get_mean_dewpoint(self):
return np.mean(
[
self.fmu_output[k][self.current_t_idx]
for k in self.get_fmu_output_keys("Zone Mean Air Dewpoint Temperature")
for k in self.get_fmu_output_keys(
"Zone Mean Air Dewpoint Temperature"
)
]
)

Expand All @@ -406,7 +426,9 @@ def get_iter_step_control_input(self, t_step, _iter, step_control_input):
if self.heat_on:
iter_step_control_input[heat_col] = min(
max(
step_control_input[heat_col] - self.step_size_seconds * _iter, 0
step_control_input[heat_col]
- self.step_size_seconds * _iter,
0,
),
self.step_size_seconds,
)
Expand All @@ -424,7 +446,9 @@ def get_iter_step_control_input(self, t_step, _iter, step_control_input):
if self.cool_on:
iter_step_control_input[cool_col] = min(
max(
step_control_input[cool_col] - self.step_size_seconds * _iter, 0
step_control_input[cool_col]
- self.step_size_seconds * _iter,
0,
),
self.step_size_seconds,
)
Expand Down Expand Up @@ -498,10 +522,14 @@ def make_directories():
_weather_dir = os.environ.get("WEATHER_DIR")

if not _idf_dir:
raise ValueError("Required environment variable: IDF_DIR is not defined.")
raise ValueError(
"Required environment variable: IDF_DIR is not defined."
)

if not _fmu_dir:
raise ValueError("Required environment variable: FMU_DIR is not defined.")
raise ValueError(
"Required environment variable: FMU_DIR is not defined."
)

if not _weather_dir:
raise ValueError(
Expand Down
Loading

0 comments on commit 562fd28

Please sign in to comment.