├── beobench ├── data │ ├── __init__.py │ ├── agents │ │ ├── __init__.py │ │ ├── rllib.py │ │ ├── sweep.py │ │ ├── random_action.py │ │ └── energym_controller.py │ ├── configs │ │ ├── __init__.py │ │ ├── energym_controller.yaml │ │ ├── gym_sinergym.yaml │ │ ├── method_random_action.yaml │ │ ├── archive │ │ │ ├── rewex02.yaml │ │ │ ├── rewex01.yaml │ │ │ ├── rewex01_grid02.yaml │ │ │ └── rewex01_grid.yaml │ │ ├── demo.yaml │ │ ├── energym_wrapper.yaml │ │ ├── gym_energym.yaml │ │ ├── test_energym.yaml │ │ ├── baselines │ │ │ ├── sinergym_figure4_random_agent.yaml │ │ │ ├── sinergym_figure4_dqn.yaml │ │ │ ├── boptest_arroyo2022_random_agent.yaml │ │ │ └── boptest_arroyo2022_dqn.yaml │ │ ├── method_ppo.yaml │ │ ├── gym_boptest.yaml │ │ └── default.yaml │ ├── dockerfiles │ │ ├── __init__.py │ │ ├── Dockerfile.beobench_install_pypi │ │ ├── Dockerfile.experiment │ │ ├── Dockerfile.beobench_install_local │ │ └── Dockerfile.requirements │ └── sweeps │ │ ├── sweep01.yaml │ │ └── sweep02.yaml ├── wrappers │ ├── __init__.py │ ├── energym.py │ └── general.py ├── integration │ ├── __init__.py │ ├── wandb.py │ └── rllib.py ├── experiment │ ├── __init__.py │ ├── provider.py │ ├── config_parser.py │ └── containers.py ├── constants.py ├── __init__.py ├── logging.py ├── cli.py └── utils.py ├── docs ├── history.rst ├── contributing.rst ├── _static │ ├── custom.css │ ├── beobench_logo.png │ ├── beobench_favicon.png │ ├── beobench_config_v1.png │ ├── beobench_favicon_v2.png │ ├── beobench_favicon_v3.png │ ├── experiment_run_flow.png │ ├── beobench_logo_v2_large.png │ ├── beobench_logo_v2_small.png │ └── beobench_architecture_horizontal_v1.png ├── support.rst ├── guides │ ├── index.rst │ ├── hyperparameter_tuning.rst │ ├── tips.rst │ ├── installation_linux.rst │ ├── configuration.rst │ ├── add_env.rst │ └── dev_env.rst ├── api.rst ├── snippets │ └── run_standard_experiment.rst ├── Makefile ├── index.rst ├── envs.rst ├── envs │ ├── BOPTEST_descriptions.rst │ ├── envs_list.rst │ ├── Energym_descriptions.rst │ └── Sinergym_descriptions.rst ├── getting_started.rst ├── credits.rst └── conf.py ├── tests ├── __init__.py ├── test_beobench.py ├── test_integrations.py ├── performance │ ├── energym_fixed_action.py │ └── test_energym.py ├── conftest.py └── test_run.py ├── .gitmodules ├── AUTHORS.rst ├── .github ├── ISSUE_TEMPLATE.md └── workflows │ ├── black.yaml │ ├── pylint.yaml │ ├── tox.yaml │ └── pypi.yaml ├── pyproject.toml ├── .pre-commit-config.yaml ├── requirements ├── environment.yml ├── dev_requirements.txt ├── doc_requirements.txt └── fixed_requirements_dev.txt ├── tox.ini ├── .editorconfig ├── MANIFEST.in ├── setup.cfg ├── .readthedocs.yml ├── PYPI_README.rst ├── LICENSE ├── CITATION.cff ├── .dockerignore ├── .gitignore ├── setup.py ├── Makefile ├── .devcontainer ├── Dockerfile ├── remote │ └── .devcontainer │ │ └── devcontainer.json └── devcontainer.json ├── CONTRIBUTING.rst ├── notebooks └── utils │ └── nb001_create_envs_table.ipynb ├── HISTORY.rst └── pylintrc /beobench/data/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /beobench/data/agents/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /beobench/data/configs/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /beobench/wrappers/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/history.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../HISTORY.rst 2 | -------------------------------------------------------------------------------- /docs/contributing.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../CONTRIBUTING.rst 2 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | """Unit test package for beobench.""" 2 | -------------------------------------------------------------------------------- /beobench/integration/__init__.py: -------------------------------------------------------------------------------- 1 | """Subpackage with integrations""" 2 | -------------------------------------------------------------------------------- /docs/_static/custom.css: -------------------------------------------------------------------------------- 1 | .container-xl { 2 | max-width: 1100px; 3 | } -------------------------------------------------------------------------------- /beobench/experiment/__init__.py: -------------------------------------------------------------------------------- 1 | """Subpackage with tools to run experiments""" 2 | -------------------------------------------------------------------------------- /beobench/data/dockerfiles/__init__.py: -------------------------------------------------------------------------------- 1 | """Subpackage with dockerfiles for experiments.""" 2 | -------------------------------------------------------------------------------- /docs/_static/beobench_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdnfn/beobench/HEAD/docs/_static/beobench_logo.png -------------------------------------------------------------------------------- /docs/_static/beobench_favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdnfn/beobench/HEAD/docs/_static/beobench_favicon.png -------------------------------------------------------------------------------- /docs/_static/beobench_config_v1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdnfn/beobench/HEAD/docs/_static/beobench_config_v1.png -------------------------------------------------------------------------------- /docs/_static/beobench_favicon_v2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdnfn/beobench/HEAD/docs/_static/beobench_favicon_v2.png -------------------------------------------------------------------------------- /docs/_static/beobench_favicon_v3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdnfn/beobench/HEAD/docs/_static/beobench_favicon_v3.png -------------------------------------------------------------------------------- /docs/_static/experiment_run_flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdnfn/beobench/HEAD/docs/_static/experiment_run_flow.png -------------------------------------------------------------------------------- /docs/_static/beobench_logo_v2_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdnfn/beobench/HEAD/docs/_static/beobench_logo_v2_large.png -------------------------------------------------------------------------------- /docs/_static/beobench_logo_v2_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdnfn/beobench/HEAD/docs/_static/beobench_logo_v2_small.png -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "beobench_contrib"] 2 | path = beobench/beobench_contrib 3 | url = https://github.com/rdnfn/beobench_contrib.git 4 | -------------------------------------------------------------------------------- /AUTHORS.rst: -------------------------------------------------------------------------------- 1 | Code Contributors 2 | ----------------- 3 | * Arduin Findeis (https://github.com/rdnfn) 4 | * Scott Jeen (https://github.com/enjeeneer) 5 | -------------------------------------------------------------------------------- /docs/_static/beobench_architecture_horizontal_v1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdnfn/beobench/HEAD/docs/_static/beobench_architecture_horizontal_v1.png -------------------------------------------------------------------------------- /beobench/data/agents/rllib.py: -------------------------------------------------------------------------------- 1 | """RLlib agent.""" 2 | 3 | import beobench.integration.rllib 4 | from beobench.experiment.provider import config 5 | 6 | beobench.integration.rllib.run_in_tune(config) 7 | -------------------------------------------------------------------------------- /docs/support.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | Support 3 | ======= 4 | 5 | Need help using Beobench or want to discuss the toolkit? Reach out via ``contact-gh (at) arduin.io`` and we are very happy to help either via email or in a call. -------------------------------------------------------------------------------- /beobench/data/configs/energym_controller.yaml: -------------------------------------------------------------------------------- 1 | # Random action agent config 2 | 3 | agent: 4 | origin: energym_controller 5 | config: 6 | config: 7 | horizon: 480 8 | general: 9 | wandb_group: energym_controller -------------------------------------------------------------------------------- /beobench/data/dockerfiles/Dockerfile.beobench_install_pypi: -------------------------------------------------------------------------------- 1 | ARG PREV_IMAGE 2 | 3 | FROM ${PREV_IMAGE} 4 | # install beobench 5 | ARG EXTRAS="extended,rllib" 6 | RUN pip --disable-pip-version-check --no-cache-dir install "beobench[${EXTRAS}]" -------------------------------------------------------------------------------- /docs/guides/index.rst: -------------------------------------------------------------------------------- 1 | ====== 2 | Guides 3 | ====== 4 | 5 | Welcome to beobench's usage guides! 6 | 7 | .. toctree:: 8 | :maxdepth: 2 9 | 10 | configuration 11 | add_env 12 | installation_linux 13 | tips 14 | dev_env -------------------------------------------------------------------------------- /beobench/data/configs/gym_sinergym.yaml: -------------------------------------------------------------------------------- 1 | # Sinergym default config 2 | 3 | env: 4 | gym: sinergym 5 | config: 6 | # Sinergym env name 7 | name: Eplus-5Zone-hot-continuous-v1 8 | # whether to normalise the observations 9 | normalize: False -------------------------------------------------------------------------------- /beobench/data/dockerfiles/Dockerfile.experiment: -------------------------------------------------------------------------------- 1 | ARG GYM_IMAGE 2 | FROM ${GYM_IMAGE} 3 | 4 | # add env creator 5 | COPY env_creator.py /opt/beobench/experiment_setup/ 6 | # add env creator to python path 7 | ENV PYTHONPATH "${PYTHONPATH}:/opt/beobench/experiment_setup/" -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Problem 2 | 3 | Describe what you were trying to get done. 4 | Tell us what happened, what went wrong, and what you expected to happen. 5 | 6 | ### Potential Solution 7 | 8 | Describe a potential solution to the problem, if you have any. 9 | -------------------------------------------------------------------------------- /beobench/data/configs/method_random_action.yaml: -------------------------------------------------------------------------------- 1 | # Random action agent config 2 | 3 | agent: 4 | origin: random_action 5 | config: 6 | stop: 7 | timesteps_total: 35040 8 | config: 9 | horizon: 480 10 | general: 11 | wandb_group: random_action -------------------------------------------------------------------------------- /docs/api.rst: -------------------------------------------------------------------------------- 1 | API 2 | === 3 | 4 | .. autosummary:: 5 | :toctree: generated 6 | :recursive: 7 | 8 | beobench.core 9 | beobench.constants 10 | beobench.experiment 11 | beobench.installer 12 | beobench.integrations 13 | beobench.utils 14 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools>=42", 4 | "wheel" 5 | ] 6 | build-backend = "setuptools.build_meta" 7 | 8 | [tool.pytest.ini_options] 9 | markers = [ 10 | "slow: marks tests as slow (deselect with '-m \"not slow\"')", 11 | ] -------------------------------------------------------------------------------- /beobench/data/dockerfiles/Dockerfile.beobench_install_local: -------------------------------------------------------------------------------- 1 | ARG PREV_IMAGE 2 | 3 | FROM ${PREV_IMAGE} 4 | # install beobench 5 | ARG EXTRAS="extended,rllib" 6 | COPY . /tmp/beobench_repo/ 7 | RUN pip --disable-pip-version-check --no-cache-dir install "/tmp/beobench_repo[${EXTRAS}]" -------------------------------------------------------------------------------- /docs/snippets/run_standard_experiment.rst: -------------------------------------------------------------------------------- 1 | .. tabs:: 2 | 3 | .. code-tab:: console Console 4 | 5 | beobench run --config config.yaml 6 | 7 | .. code-tab:: python 8 | 9 | import beobench 10 | 11 | beobench.run(config = "config.yaml") -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/psf/black 3 | rev: 22.3.0 4 | hooks: 5 | - id: black 6 | language_version: python3.9 7 | - repo: https://github.com/pycqa/pylint 8 | rev: v2.11.1 9 | hooks: 10 | - id: pylint 11 | -------------------------------------------------------------------------------- /beobench/data/sweeps/sweep01.yaml: -------------------------------------------------------------------------------- 1 | program: beobench 2 | method: grid 3 | command: 4 | - ${program} 5 | - run 6 | - ${args} 7 | parameters: 8 | config: 9 | value: 10 | - ./beobench/data/configs/rewex01.yaml 11 | - ./wandb.beo.yaml 12 | dev-path: 13 | value: . -------------------------------------------------------------------------------- /beobench/data/dockerfiles/Dockerfile.requirements: -------------------------------------------------------------------------------- 1 | ARG PREV_IMAGE 2 | 3 | FROM ${PREV_IMAGE} 4 | # install beobench 5 | ARG REQUIREMENTS 6 | COPY ${REQUIREMENTS} /tmp/pip-tmp/requirements.txt 7 | RUN pip --disable-pip-version-check --no-cache-dir install -r /tmp/pip-tmp/requirements.txt \ 8 | && rm -rf /tmp/pip-tmp -------------------------------------------------------------------------------- /.github/workflows/black.yaml: -------------------------------------------------------------------------------- 1 | name: Black style check 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | lint: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v3 10 | with: 11 | submodules: recursive 12 | - uses: psf/black@stable 13 | with: 14 | version: 22.3 -------------------------------------------------------------------------------- /beobench/data/configs/archive/rewex02.yaml: -------------------------------------------------------------------------------- 1 | # REWEX Experiment 02 2 | 3 | # environment config 4 | env: 5 | name: MixedUseFanFCU-v0 6 | gym: energym 7 | config: 8 | days: 365 9 | energym_environment: MixedUseFanFCU-v0 10 | gym_kwargs: 11 | max_episode_length: 35040 12 | normalize: true 13 | step_period: 15 14 | weather: GRC_A_Athens -------------------------------------------------------------------------------- /requirements/environment.yml: -------------------------------------------------------------------------------- 1 | # To create the dev environment use 2 | # conda env create --prefix=./env -f requirements/environment.yml 3 | # 4 | # To remove env 5 | # conda env remove -p ./env 6 | 7 | channels: 8 | - conda-forge 9 | - defaults 10 | dependencies: 11 | - python=3.9.6 12 | - pip 13 | - pip: 14 | - -r requirements_dev.txt 15 | #- -e ./.. -------------------------------------------------------------------------------- /beobench/data/configs/demo.yaml: -------------------------------------------------------------------------------- 1 | agent: 2 | origin: random_action 3 | config: 4 | stop: 5 | timesteps_total: 10 6 | env: 7 | gym: sinergym 8 | config: 9 | name: Eplus-5Zone-hot-continuous-v1 10 | wrappers: 11 | - origin: general 12 | class: WandbLogger 13 | general: 14 | wandb_entity: beobench 15 | wandb_project: demo 16 | # wandb_api_key: HIDDEN -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py39, py38, py37, py36, flake8 3 | 4 | [travis] 5 | python = 6 | 3.9: py39 7 | 3.8: py38 8 | 3.7: py37 9 | 3.6: py36 10 | 11 | [testenv:flake8] 12 | basepython = python 13 | deps = flake8 14 | commands = flake8 beobench tests 15 | 16 | [testenv] 17 | #setenv = 18 | # PYTHONPATH = {toxinidir} 19 | 20 | deps = pytest 21 | commands = pytest -s 22 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | indent_size = 4 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | charset = utf-8 11 | end_of_line = lf 12 | 13 | [*.bat] 14 | indent_style = tab 15 | end_of_line = crlf 16 | 17 | [LICENSE] 18 | insert_final_newline = false 19 | 20 | [Makefile] 21 | indent_style = tab 22 | -------------------------------------------------------------------------------- /requirements/dev_requirements.txt: -------------------------------------------------------------------------------- 1 | 2 | pip 3 | bump2version 4 | wheel 5 | watchdog 6 | flake8 7 | tox 8 | coverage 9 | Sphinx 10 | twine 11 | 12 | # Dev tools 13 | black 14 | pre-commit 15 | pylint 16 | click # command line interface 17 | build # PyPA build tool 18 | 19 | # Jupyter notebooks 20 | jupyterlab 21 | 22 | # for rst development (used by vscode) 23 | doc8 24 | rstcheck 25 | 26 | # logging 27 | loguru -------------------------------------------------------------------------------- /tests/test_beobench.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """Tests for `beobench` package.""" 4 | 5 | 6 | import unittest 7 | 8 | 9 | class Testbeobench(unittest.TestCase): 10 | """Tests for `beobench` package.""" 11 | 12 | def setUp(self): 13 | """Set up test fixtures, if any.""" 14 | 15 | def tearDown(self): 16 | """Tear down test fixtures, if any.""" 17 | 18 | def test_000_something(self): 19 | """Test something.""" 20 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include AUTHORS.rst 2 | include CONTRIBUTING.rst 3 | include HISTORY.rst 4 | include LICENSE 5 | include README.rst 6 | include PYPI_README.rst 7 | 8 | recursive-include tests * 9 | recursive-include beobench/beobench_contrib/gyms/ * 10 | 11 | recursive-include beobench/data/configs * 12 | recursive-include beobench/data/agents * 13 | recursive-include beobench/data/dockerfiles * 14 | 15 | recursive-exclude docs *.rst conf.py Makefile make.bat *.jpg *.png *.gif 16 | recursive-exclude * __pycache__ 17 | recursive-exclude * *.py[co] 18 | -------------------------------------------------------------------------------- /requirements/doc_requirements.txt: -------------------------------------------------------------------------------- 1 | # theme 2 | sphinx-book-theme==0.3.2 3 | 4 | # for code tabs 5 | sphinx-tabs 6 | 7 | # markdown parsing (m2r2 should be used only as long as docutils<0.17) 8 | # currently not used 9 | # myst-parser 10 | # m2r2 11 | 12 | # with docutils>=0.18 admonitions sometimes do not end 13 | docutils==0.16 14 | 15 | # Because of the following bug 16 | # https://github.com/readthedocs/readthedocs.org/issues/9038 17 | Jinja2<3.1 18 | 19 | # For allowing type hints 20 | sphinx-autodoc-typehints 21 | 22 | # logging 23 | loguru -------------------------------------------------------------------------------- /beobench/integration/wandb.py: -------------------------------------------------------------------------------- 1 | """Module with tools for using Beobench with wandb.""" 2 | 3 | import wandb 4 | 5 | 6 | def log_eps_data(eps_dict: dict) -> None: 7 | """Log episode data to wandb. 8 | 9 | To be used with concatenated episode data from get_cross_episodes_data(). 10 | 11 | Args: 12 | eps_dict (dict): episode data 13 | """ 14 | 15 | eps_dict_len = len(list(eps_dict.values())[0]) 16 | for i in range(eps_dict_len): 17 | single_eps_dict = {key: values[i] for key, values in eps_dict.items()} 18 | wandb.log({"env_step": i, **single_eps_dict}) 19 | -------------------------------------------------------------------------------- /beobench/data/configs/energym_wrapper.yaml: -------------------------------------------------------------------------------- 1 | wrappers: 2 | - origin: general 3 | class: FixDictActs 4 | config: 5 | fixed_actions: 6 | P1_onoff_HP_sp: 1 7 | P2_onoff_HP_sp: 1 8 | P3_onoff_HP_sp: 1 9 | P4_onoff_HP_sp: 1 10 | Bd_Ch_EV1Bat_sp: 1. 11 | Bd_Ch_EV2Bat_sp: 1. 12 | P1_T_Thermostat_sp: 0. 13 | P2_T_Thermostat_sp: 0. 14 | P3_T_Thermostat_sp: 0. 15 | P4_T_Thermostat_sp: 0. 16 | - origin: energym 17 | class: CustomReward 18 | config: 19 | info_obs_weights: 20 | Fa_E_HVAC: -0.0001 21 | - origin: general 22 | class: WandbLogger 23 | config: 24 | log_freq: 1 25 | -------------------------------------------------------------------------------- /docs/guides/hyperparameter_tuning.rst: -------------------------------------------------------------------------------- 1 | Hyperparameter Tuning 2 | --------------------- 3 | 4 | There multiple different ways with which hyperparameters 5 | can be tuned in experiments involving Beobench environments. 6 | In this page we illustrate the usage of the `W&B Sweeps API 7 | `_ in combination with Beobench, but there 8 | are other options as well (e.g. `Ray Tune `_). 9 | 10 | W&B Sweeps 11 | ^^^^^^^^^^ 12 | 13 | Make sure that the W&B ``wandb`` Python package is installed. Follow the 14 | `official quickstart `_ if it is 15 | not installed yet. 16 | 17 | *Under construction.* 18 | 19 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = python -msphinx 7 | SPHINXPROJ = beobench 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /.github/workflows/pylint.yaml: -------------------------------------------------------------------------------- 1 | name: Pylint check 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | python-version: ["3.9"] 11 | steps: 12 | - uses: actions/checkout@v3 13 | with: 14 | submodules: recursive 15 | - name: Set up Python ${{ matrix.python-version }} 16 | uses: actions/setup-python@v3 17 | with: 18 | python-version: ${{ matrix.python-version }} 19 | - name: Install dependencies 20 | run: | 21 | python -m pip install --upgrade pip 22 | pip install pylint 23 | - name: Analysing the code with pylint 24 | run: | 25 | pylint $(git ls-files '*.py') 26 | -------------------------------------------------------------------------------- /beobench/data/configs/gym_energym.yaml: -------------------------------------------------------------------------------- 1 | # Energym default config 2 | 3 | env: 4 | gym: energym 5 | config: 6 | # Number of simulation days 7 | days: 365 8 | # Name of energym environment 9 | name: Apartments2Thermal-v0 10 | gym_kwargs: 11 | # Maximum number of timesteps in one episode 12 | max_episode_length: 35040 # corresponds to a year of 15 min steps 13 | # Whether state and action spaces are normalized 14 | normalize: true 15 | # Number of real-world minutes between timesteps in building simulation 16 | step_period: 15 17 | # Whether to allow a complete environment reset 18 | ignore_reset: True 19 | # Weather file to use for the scenario (possible files depend on env chosen) 20 | weather: ESP_CT_Barcelona -------------------------------------------------------------------------------- /docs/guides/tips.rst: -------------------------------------------------------------------------------- 1 | Miscellaneous tips 2 | ------------------ 3 | 4 | Running experiment in background 5 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 6 | 7 | Once you're up and running with Beobench, you will likely eventually want to start running experiments in the background. One way of doing this on Linux is using ``nohup``: 8 | 9 | .. code-block:: console 10 | 11 | nohup beobench run -c config.yaml & 12 | 13 | This will save the console output of your experiment to `nohup.out` in your current directory. 14 | 15 | If you have setup the Beobench development environment, you may want to start your experiments inside the devcontainer. You can do this using: 16 | 17 | .. code-block:: console 18 | 19 | nohup docker exec beobench run -c config.yaml & 20 | 21 | -------------------------------------------------------------------------------- /beobench/data/sweeps/sweep02.yaml: -------------------------------------------------------------------------------- 1 | # Sweep 02 2 | # Run with the command 3 | # beobench run -c beobench/experiment/definitions/rewex01.yaml -d . --use-gpu --docker-shm-size 28gb 4 | 5 | # Some of the descriptions of RLlib config values are taken from 6 | # https://docs.ray.io/en/latest/rllib/rllib-training.html 7 | 8 | # agent config 9 | agent: 10 | origin: beobench/data/agents/sweep.py 11 | config: 12 | base_config: /tmp/beobench/configs/rewex01.yaml 13 | sweep_config: 14 | method: grid 15 | parameters: 16 | agent.config.lr: 17 | values: [0.0005, 0.0001, 0.00005] 18 | env: 19 | gym: energym 20 | general: 21 | beobench_extras: "extended,rllib" 22 | docker_flags: 23 | - -v 24 | - /beobench/beobench/data/configs/rewex01.yaml:/tmp/beobench/configs/rewex01.yaml:ro -------------------------------------------------------------------------------- /beobench/constants.py: -------------------------------------------------------------------------------- 1 | """Module with various configuration constants""" 2 | 3 | import pathlib 4 | 5 | USER_CONFIG_PATH = pathlib.Path("./.beobench.yml") 6 | 7 | # available gym-framework integrations 8 | AVAILABLE_INTEGRATIONS = [ 9 | "boptest", 10 | "sinergym", 11 | "sinergym_minimal", 12 | "energym", 13 | ] 14 | 15 | # available agent scripts 16 | AVAILABLE_AGENTS = [ 17 | "rllib", 18 | "random_action", 19 | "energym_controller", 20 | ] 21 | 22 | AVAILABLE_WRAPPERS = [ 23 | "general", 24 | "energym", 25 | ] 26 | 27 | # read-only dir in container 28 | CONTAINER_RO_DIR = pathlib.Path("/root/beobench_configs") 29 | 30 | # output data dir in container 31 | CONTAINER_DATA_DIR = pathlib.Path("/root/beobench_results") 32 | RAY_LOCAL_DIR_IN_CONTAINER = CONTAINER_DATA_DIR / "ray_results" 33 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 0.5.4 3 | commit = True 4 | tag = True 5 | 6 | [bumpversion:file:setup.py] 7 | search = version = "{current_version}" 8 | replace = version = "{new_version}" 9 | 10 | [bumpversion:file:beobench/__init__.py] 11 | search = __version__ = "{current_version}" 12 | replace = __version__ = "{new_version}" 13 | 14 | [bumpversion:file:CITATION.cff] 15 | search = version: {current_version} 16 | replace = version: {new_version} 17 | 18 | [bumpversion:file:beobench/data/configs/default.yaml] 19 | search = version: {current_version} 20 | replace = version: {new_version} 21 | 22 | [bdist_wheel] 23 | universal = 1 24 | 25 | [flake8] 26 | exclude = docsv 27 | max-line-length = 88 28 | docstring-convention = google 29 | extend-ignore = 30 | E203, 31 | 32 | [doc8] 33 | max-line-length = 2000 34 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yaml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Set the version of Python and other tools you might need 9 | build: 10 | os: ubuntu-20.04 11 | tools: 12 | python: "3.9" 13 | 14 | # Build documentation in the docs/ directory with Sphinx 15 | sphinx: 16 | configuration: docs/conf.py 17 | 18 | # If using Sphinx, optionally build your docs in additional formats such as PDF 19 | formats: 20 | - pdf 21 | 22 | # Optionally declare the Python requirements required to build your docs 23 | python: 24 | install: 25 | - method: pip 26 | path: . 27 | - requirements: requirements/doc_requirements.txt 28 | 29 | # Ensure the beobench_contrib submodule is included 30 | submodules: 31 | include: all -------------------------------------------------------------------------------- /beobench/__init__.py: -------------------------------------------------------------------------------- 1 | """Top-level package for beobench.""" 2 | 3 | __author__ = """Beobench authors""" 4 | __email__ = "-" 5 | __version__ = "0.5.4" 6 | 7 | import os 8 | from loguru import logger 9 | 10 | from beobench.utils import restart 11 | from beobench.experiment.scheduler import run 12 | 13 | # throw error message if trying to use Beobench on windows 14 | if os.name == "nt": 15 | logger.critical( 16 | "ERROR: you appear to try to run Beobench directly on Windows. " 17 | "Beobench CANNOT be run directly on Windows. \n\n" 18 | "However, you can run Beobench inside Windows Subsystem for Linux (WSL)" 19 | " on a Windows machine. " 20 | "See the following link: " 21 | "https://docs.microsoft.com/en-us/windows/wsl/install. " 22 | "Beobench will still execute initially but will fail eventually." 23 | ) 24 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | ************* 2 | Beobench docs 3 | ************* 4 | 5 | .. warning:: 6 | 7 | Beobench is only receiving minor maintenance updates at this point. Therefore, Beobench may also no longer support the latest versions of the integrated building simulation tools. If you would like to use the latest versions of these tools, it is recommended to use them directly without Beobench. 8 | 9 | 10 | .. include:: ../README.rst 11 | :start-after: start-in-sphinx-docs 12 | :end-before: end-in-sphinx-docs 13 | 14 | Contents 15 | ======== 16 | 17 | .. toctree:: 18 | :maxdepth: 2 19 | :includehidden: 20 | 21 | Home 22 | getting_started 23 | envs 24 | guides/index 25 | contributing 26 | credits 27 | history 28 | support 29 | 30 | Indices and tables 31 | ------------------ 32 | * :ref:`genindex` 33 | * :ref:`modindex` 34 | * :ref:`search` 35 | -------------------------------------------------------------------------------- /beobench/data/configs/archive/rewex01.yaml: -------------------------------------------------------------------------------- 1 | # REWEX Experiment 01 2 | # Energym environment configuration 3 | 4 | env: 5 | name: Apartments2Thermal-v0 6 | gym: energym 7 | config: 8 | # Number of simulation days 9 | days: 365 10 | # Name of energym environment 11 | energym_environment: Apartments2Thermal-v0 12 | gym_kwargs: 13 | # Maximum number of timesteps in one episode 14 | max_episode_length: 525600 # corresponds to a year of 3 min steps 15 | # User-provided flag to require state and action spaces to be normalized 16 | normalize: true 17 | # Number of real-world minutes between timesteps in building simulation 18 | step_period: 3 19 | # Whether to allow a complete environment reset 20 | ignore_reset: True 21 | power_in_reward: [Fa_E_HVAC] 22 | # Weather file to use for the scenario (possible files depend on env chosen) 23 | weather: ESP_CT_Barcelona -------------------------------------------------------------------------------- /docs/guides/installation_linux.rst: -------------------------------------------------------------------------------- 1 | Additional installation steps on Linux 2 | ----------------------------------------- 3 | 4 | Docker behaves differently depending on your OS. In Linux, 5 | many docker commands require additional privileges that can 6 | be granted by adding ``sudo`` in front of the command. 7 | Beobench relies on docker for most of its functionality. 8 | Therefore, there are two options to get beobench to work 9 | on a Linux system: 10 | 11 | 1. Always use ``sudo`` in front of beobench commands to grant 12 | the relevant privileges required for docker (note that this 13 | has not been tested) 14 | 2. *Recommended:* follow the official post-installation steps 15 | to `manage docker as a non-root user 16 | `_ to enable running docker 17 | without ``sudo``. As the linked documentation points out, 18 | this carries a certain security risk. 19 | -------------------------------------------------------------------------------- /PYPI_README.rst: -------------------------------------------------------------------------------- 1 | A toolkit providing easy and unified access to building control environments for reinforcement learning (RL). Compared to other domains, `RL environments for building control `_ tend to be more difficult to install and handle. Most environments require the user to either manually install a building simulator (e.g. `EnergyPlus `_) or to manually manage Docker containers. This can be tedious. 2 | 3 | Beobench was created to make building control environments easier to use and experiments more reproducible. Beobench uses Docker to manage all environment dependencies in the background so that the user doesn't have to. A standardised API allows the user to easily configure experiments and evaluate new RL agents on building control environments. 4 | 5 | For more information go to the `documentation `_ and the `GitHub code repository `_. -------------------------------------------------------------------------------- /tests/test_integrations.py: -------------------------------------------------------------------------------- 1 | """Tests for Beobench integrations like Sinergym and Energym.""" 2 | 3 | import pytest 4 | import beobench.experiment.config_parser 5 | import beobench 6 | 7 | CMD_CONFIG = { 8 | "dev_path": ".", 9 | "force_build": True, 10 | } 11 | 12 | 13 | @pytest.mark.slow 14 | @pytest.mark.parametrize("gym_name", ["sinergym", "energym", "boptest"]) 15 | def test_gym(rand_agent_config, gym_name): 16 | """Run random agent beobench experiment with specified gym integration.""" 17 | 18 | # get gym integration config and combine with random agent config 19 | env_config = beobench.experiment.config_parser.get_standard_config( 20 | f"gym_{gym_name}" 21 | ) 22 | config = beobench.experiment.config_parser.parse([rand_agent_config, env_config]) 23 | 24 | # limit num timesteps taken during test 25 | config["agent"]["config"]["stop"]["timesteps_total"] = 10 26 | 27 | # running actual experiment 28 | beobench.run(config=config, **CMD_CONFIG) 29 | -------------------------------------------------------------------------------- /docs/guides/configuration.rst: -------------------------------------------------------------------------------- 1 | YAML configuration 2 | ================== 3 | 4 | Beobench uses `YAML files `_ to define experiment configurations. The file below is the default configuration for every Beobench experiment. The comments above each setting describe its functionality. 5 | 6 | .. literalinclude:: ../../beobench/data/configs/default.yaml 7 | :language: yaml 8 | 9 | 10 | Gym-specific configurations 11 | --------------------------- 12 | 13 | Each gym framework integration has its own configuration setup. See below for the default environment configurations of the three supported frameworks. 14 | 15 | BOPTEST 16 | ^^^^^^^ 17 | 18 | .. literalinclude:: ../../beobench/data/configs/gym_boptest.yaml 19 | :language: yaml 20 | 21 | 22 | Energym 23 | ^^^^^^^ 24 | 25 | .. literalinclude:: ../../beobench/data/configs/gym_energym.yaml 26 | :language: YAML 27 | 28 | Sinergym 29 | ^^^^^^^^ 30 | 31 | .. literalinclude:: ../../beobench/data/configs/gym_sinergym.yaml 32 | :language: yaml -------------------------------------------------------------------------------- /beobench/data/configs/test_energym.yaml: -------------------------------------------------------------------------------- 1 | # Simple test case for energym integration 2 | 3 | # agent config 4 | agent: 5 | origin: random_action # either path to agent script or name of standard agent 6 | config: 7 | stop: 8 | timesteps_total: 10 9 | config: 10 | horizon: 96 11 | 12 | # environment config 13 | env: 14 | gym: energym 15 | config: 16 | # Number of simulation days 17 | days: 365 18 | # Name of energym environment 19 | name: Apartments2Thermal-v0 20 | gym_kwargs: 21 | # Maximum number of timesteps in one episode 22 | max_episode_length: 35040 # corresponds to a year of 15 min steps 23 | # User-provided flag to require state and action spaces to be normalized 24 | normalize: true 25 | # Number of real-world minutes between timesteps in building simulation 26 | step_period: 15 27 | # Whether to allow a complete environment reset 28 | ignore_reset: True 29 | # Weather file to use for the scenario (possible files depend on env chosen) 30 | weather: ESP_CT_Barcelona -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021, Beobench authors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /tests/performance/energym_fixed_action.py: -------------------------------------------------------------------------------- 1 | """Simple fixed action agent for time testing.""" 2 | 3 | from beobench.experiment.provider import config, create_env 4 | from beobench.constants import CONTAINER_DATA_DIR 5 | import timeit 6 | 7 | env = create_env() 8 | 9 | action = config["agent"]["config"]["action"] 10 | num_steps = config["agent"]["config"]["num_steps"] 11 | use_native_env = config["agent"]["config"]["use_native_env"] 12 | 13 | print("Beobench: fixed actions being taken.") 14 | 15 | if use_native_env: 16 | 17 | def take_steps(): 18 | for _ in range(num_steps): 19 | env.env.step(action) 20 | 21 | else: 22 | 23 | def take_steps(): 24 | for _ in range(num_steps): 25 | env.step(action) 26 | 27 | 28 | step_time = timeit.timeit(take_steps, number=1) 29 | with open( 30 | CONTAINER_DATA_DIR / "perf_test_results.txt", "a", encoding="utf-8" 31 | ) as text_file: 32 | text_file.write( 33 | f" Performance, time for taking {num_steps} in env: {step_time} seconds\n" 34 | ) 35 | print("Performance test, step time (beobench): ", step_time) 36 | 37 | print("Beobench: fixed action test completed.") 38 | 39 | env.close() 40 | -------------------------------------------------------------------------------- /beobench/data/configs/baselines/sinergym_figure4_random_agent.yaml: -------------------------------------------------------------------------------- 1 | # Reproduction of experiment shown in Figure 4 of Sinergym paper. 2 | # The paper can be found at: https://dl.acm.org/doi/abs/10.1145/3486611.3488729 3 | # 4 | # Some of the descriptions of RLlib config values are taken from 5 | # https://docs.ray.io/en/latest/rllib/rllib-training.html 6 | # 7 | # From environment output we can deduce that the step size is 15 minutes 8 | # (In january there are 2976 steps / 31 days -> 96 steps per day 9 | # -> step size of 15 min) 10 | 11 | env: 12 | gym: sinergym 13 | name: Eplus-5Zone-hot-discrete-v1 14 | config: 15 | name: Eplus-5Zone-hot-discrete-v1 16 | # whether to normalise the observations 17 | normalize: True 18 | agent: 19 | origin: random_action 20 | config: 21 | config: 22 | horizon: 96 23 | stop: 24 | timesteps_total: 105120 # 35040 25 | imitate_rllib_env_checks: True 26 | wrappers: 27 | - origin: general 28 | class: WandbLogger 29 | config: 30 | log_freq: 1 31 | summary_metric_keys: 32 | - env.returns.reward 33 | - env.returns.info.total_power 34 | - env.returns.info.comfort_penalty 35 | general: 36 | wandb_project: sinergym_fig4_baseline 37 | wandb_group: random_action 38 | num_samples: 5 39 | 40 | -------------------------------------------------------------------------------- /.github/workflows/tox.yaml: -------------------------------------------------------------------------------- 1 | name: Tox tests 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | main-test: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | python: ["3.7", "3.8", "3.9"] 11 | steps: 12 | - uses: actions/checkout@v3 13 | with: 14 | submodules: recursive 15 | - name: Setup Python 16 | uses: actions/setup-python@v3 17 | with: 18 | python-version: ${{ matrix.python }} 19 | - name: Install tox and any other packages 20 | run: pip install tox pytest 21 | - name: Run tox 22 | # Run tox using the version of Python in `PATH` 23 | run: tox -e py 24 | 25 | # Test of Docker-in-Docker usage 26 | # Disabled because of the following issue: 27 | # https://github.com/rdnfn/beobench/issues/85 28 | # 29 | # docker-v19_03-test: 30 | # runs-on: ubuntu-latest 31 | # container: docker:19.03-dind 32 | # strategy: 33 | # matrix: 34 | # python: ["3.8"] 35 | # steps: 36 | # - run: apk add --upgrade --no-cache -U git 37 | # - run: apk add --no-cache -U python3 py3-pip 38 | # - uses: actions/checkout@v3 39 | # with: 40 | # submodules: recursive 41 | # - run: pip install pytest 42 | # - run: pip install . 43 | # - run: pytest -s -------------------------------------------------------------------------------- /beobench/wrappers/energym.py: -------------------------------------------------------------------------------- 1 | """Environment wrappers for energym environments.""" 2 | 3 | import gym 4 | 5 | 6 | class CustomReward(gym.Wrapper): 7 | """Wrapper to customize reward of energym environments.""" 8 | 9 | def __init__(self, env: gym.Env, info_obs_weights: dict): 10 | """Wrapper to customize reward of energym environments. 11 | 12 | Args: 13 | env (gym.Env): environment to be wrapped. 14 | info_obs_weights (dict): dictionary with keys matching the info[obs] values 15 | to be combined as a linear combination with the weights given. E.g. 16 | {'power_ev':0.4, 'power_hvac':0.6} will make the env.step() method 17 | return a negative reward signal computed by 18 | 19 | ``` 20 | info['obs']['power_ev'] * 0.4 + info['obs']['power_hvac'] * 0.6 21 | ``` 22 | """ 23 | super().__init__(env) 24 | self.info_obs_weights = info_obs_weights 25 | 26 | def step(self, action): 27 | obs, _, done, info = self.env.step(action) 28 | 29 | reward = sum( # pylint: disable=consider-using-generator 30 | [info["obs"][key] * value for key, value in self.info_obs_weights.items()] 31 | ) 32 | return obs, reward, done, info 33 | -------------------------------------------------------------------------------- /docs/envs.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Environments 3 | ============ 4 | 5 | 6 | Overview 7 | -------- 8 | 9 | Beobench provides easy access to a large number of building control environments for reinforcement learning. Each environment comes from one of the integrated frameworks `BOPTEST `_, `Energym `_ and `Sinergym `_. The list below shows links to all environments available out-of-the-box. 10 | 11 | .. admonition:: Available Environments 12 | 13 | .. include:: envs/envs_list.rst 14 | 15 | ---- 16 | 17 | BOPTEST 18 | ------- 19 | 20 | .. include:: ../beobench/beobench_contrib/gyms/boptest/README.rst 21 | :start-line: 3 22 | 23 | 24 | Environments 25 | ^^^^^^^^^^^^ 26 | 27 | .. include:: envs/BOPTEST_descriptions.rst 28 | 29 | 30 | ---- 31 | 32 | Energym 33 | ------- 34 | 35 | .. include:: ../beobench/beobench_contrib/gyms/energym/README.rst 36 | :start-line: 3 37 | 38 | 39 | Environments 40 | ^^^^^^^^^^^^ 41 | 42 | .. include:: envs/Energym_descriptions.rst 43 | 44 | ---- 45 | 46 | Sinergym 47 | -------- 48 | 49 | .. include:: ../beobench/beobench_contrib/gyms/sinergym/README.rst 50 | :start-line: 3 51 | 52 | 53 | Environments 54 | ^^^^^^^^^^^^ 55 | 56 | .. include:: envs/Sinergym_descriptions.rst -------------------------------------------------------------------------------- /.github/workflows/pypi.yaml: -------------------------------------------------------------------------------- 1 | # This workflow will upload a Python Package using Twine when a release is created 2 | # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries 3 | 4 | # This workflow uses actions that are not certified by GitHub. 5 | # They are provided by a third-party and are governed by 6 | # separate terms of service, privacy policy, and support 7 | # documentation. 8 | 9 | name: Publish package to PyPI 10 | 11 | on: 12 | release: 13 | types: [published] 14 | 15 | permissions: 16 | contents: read 17 | 18 | jobs: 19 | deploy: 20 | 21 | runs-on: ubuntu-latest 22 | 23 | steps: 24 | - uses: actions/checkout@v3 25 | with: 26 | submodules: recursive 27 | - name: Set up Python 28 | uses: actions/setup-python@v3 29 | with: 30 | python-version: '3.9' 31 | - name: Install dependencies 32 | run: | 33 | python -m pip install --upgrade pip 34 | pip install build 35 | - name: Build package 36 | run: python -m build 37 | - name: Publish package 38 | if: startsWith(github.ref, 'refs/tags') 39 | uses: pypa/gh-action-pypi-publish@release/v1 40 | with: 41 | user: __token__ 42 | password: ${{ secrets.PYPI_API_TOKEN }} 43 | -------------------------------------------------------------------------------- /beobench/data/agents/sweep.py: -------------------------------------------------------------------------------- 1 | """Script to run wandb sweeps via Beobench.""" 2 | 3 | import wandb 4 | import beobench 5 | import beobench.experiment.provider 6 | import beobench.experiment.config_parser 7 | import beobench.utils 8 | 9 | # Get the base config of experiment 10 | base_config = beobench.experiment.provider.config["agent"]["config"]["base_config"] 11 | base_config = beobench.experiment.config_parser.parse(base_config) 12 | 13 | 14 | def sweep_func(): 15 | 16 | wandb.init(dir="/root/ray_results/") 17 | print("initialised wandb inside sweep func.") 18 | 19 | # loading config params from sweep and combining them with base config 20 | tmp_config = dict(wandb.config) 21 | print("Config given by wandb", tmp_config) 22 | tmp_config = beobench.utils.merge_dicts( 23 | a=base_config, 24 | b=tmp_config, 25 | let_b_overrule_a=True, 26 | ) 27 | print("Config given by sweep:", tmp_config) 28 | beobench.run( 29 | config=tmp_config, 30 | no_additional_container=True, 31 | ) 32 | 33 | 34 | # Setting up sweep config 35 | sweep_config = beobench.experiment.provider.config["agent"]["config"]["sweep_config"] 36 | sweep_config["program"] = "beobench" 37 | 38 | # Starting sweep 39 | sweep_id = wandb.sweep(sweep_config) 40 | 41 | # Creating agent (for sweep) 42 | wandb.agent(sweep_id, function=sweep_func) 43 | -------------------------------------------------------------------------------- /CITATION.cff: -------------------------------------------------------------------------------- 1 | # This CITATION.cff file was generated with cffinit. 2 | # Visit https://bit.ly/cffinit to generate yours today! 3 | 4 | cff-version: 1.2.0 5 | title: >- 6 | Beobench: A Toolkit for Unified Access to Building 7 | Simulations for Reinforcement Learning 8 | message: >- 9 | If you use this software, please cite it using the 10 | metadata from this file. 11 | type: software 12 | version: 0.5.4 13 | url: https://github.com/rdnfn/beobench 14 | authors: 15 | - given-names: Arduin 16 | family-names: Findeis 17 | - given-names: Fiodar 18 | family-names: Kazhamiaka 19 | - given-names: Scott 20 | family-names: Jeen 21 | - given-names: Srinivasan 22 | family-names: Keshav 23 | preferred-citation: 24 | authors: 25 | - given-names: Arduin 26 | family-names: Findeis 27 | - given-names: Fiodar 28 | family-names: Kazhamiaka 29 | - given-names: Scott 30 | family-names: Jeen 31 | - given-names: Srinivasan 32 | family-names: Keshav 33 | title: >- 34 | Beobench: A Toolkit for Unified Access to Building 35 | Simulations for Reinforcement Learning 36 | type: conference-paper 37 | year: 2021 38 | start: 374 39 | end: 382 40 | collection-title: Proceedings of the Thirteenth ACM International Conference on Future Energy Systems 41 | doi: 10.1145/3538637.3538866 42 | conference: 43 | name: e-Energy '22 44 | location: Virtual Event 45 | 46 | -------------------------------------------------------------------------------- /beobench/logging.py: -------------------------------------------------------------------------------- 1 | """Logging utilities for Beobench.""" 2 | 3 | from loguru import logger 4 | import sys 5 | 6 | 7 | def setup(include_time=False) -> None: 8 | """Setup Beobench loguru logging setup.""" 9 | if include_time: 10 | time_str = "[{time:YYYY-MM-DD, HH:mm:ss.SSSS}] " 11 | else: 12 | time_str = "" 13 | logger.remove() 14 | logger.level("INFO", color="") 15 | logger.add( 16 | sys.stdout, 17 | colorize=True, 18 | format=( 19 | "Beobench " 20 | "⚡️" 21 | f"{time_str}" 22 | "{message}" 23 | ), 24 | ) 25 | 26 | 27 | def log_subprocess(pipe, process_name="subprocess"): 28 | """Log subprocess pipe. 29 | 30 | Adapted from from https://stackoverflow.com/a/21978778. 31 | 32 | Color setting of context is described in https://stackoverflow.com/a/33206814. 33 | """ 34 | lines = [] 35 | max_len = 5000 36 | 37 | for i, line in enumerate(iter(pipe.readline, b"")): # b'\n'-separated lines 38 | context = f"\033[34m{process_name}:\033[0m" # .decode("ascii") 39 | line = line.decode("utf-8").rstrip() 40 | logger.info(f"{context} {line}") 41 | if i < max_len: 42 | lines.append(line) 43 | else: 44 | lines = lines[1:].append(line) 45 | 46 | output = "\n".join(lines) 47 | return output 48 | -------------------------------------------------------------------------------- /requirements/fixed_requirements_dev.txt: -------------------------------------------------------------------------------- 1 | alabaster==0.7.12 2 | Babel==2.9.1 3 | backports.entry-points-selectable==1.1.0 4 | -e git+https://github.com/rdnfn/beobench.git@249aaf9c392362b28e25e6963a391415ea8a31b0#egg=beobench 5 | black==21.9b0 6 | bleach==4.1.0 7 | bump2version==1.0.1 8 | certifi==2021.10.8 9 | cfgv==3.3.1 10 | charset-normalizer==2.0.7 11 | click==8.0.3 12 | colorama==0.4.4 13 | coverage==6.0.2 14 | distlib==0.3.3 15 | docutils==0.17.1 16 | filelock==3.3.1 17 | flake8==4.0.1 18 | identify==2.3.1 19 | idna==3.3 20 | imagesize==1.2.0 21 | importlib-metadata==4.8.1 22 | Jinja2==3.0.2 23 | keyring==23.2.1 24 | MarkupSafe==2.0.1 25 | mccabe==0.6.1 26 | mypy-extensions==0.4.3 27 | nodeenv==1.6.0 28 | packaging==21.0 29 | pathspec==0.9.0 30 | pkginfo==1.7.1 31 | platformdirs==2.4.0 32 | pluggy==1.0.0 33 | pre-commit==2.15.0 34 | py==1.10.0 35 | pycodestyle==2.8.0 36 | pyflakes==2.4.0 37 | Pygments==2.10.0 38 | pyparsing==3.0.3 39 | pytz==2021.3 40 | PyYAML==6.0 41 | readme-renderer==30.0 42 | regex==2021.10.23 43 | requests==2.26.0 44 | requests-toolbelt==0.9.1 45 | rfc3986==1.5.0 46 | six==1.16.0 47 | snowballstemmer==2.1.0 48 | Sphinx==4.2.0 49 | sphinxcontrib-applehelp==1.0.2 50 | sphinxcontrib-devhelp==1.0.2 51 | sphinxcontrib-htmlhelp==2.0.0 52 | sphinxcontrib-jsmath==1.0.1 53 | sphinxcontrib-qthelp==1.0.3 54 | sphinxcontrib-serializinghtml==1.1.5 55 | toml==0.10.2 56 | tomli==1.2.2 57 | tox==3.24.4 58 | tqdm==4.62.3 59 | twine==3.4.2 60 | typing-extensions==3.10.0.2 61 | urllib3==1.26.7 62 | virtualenv==20.9.0 63 | watchdog==2.1.6 64 | webencodings==0.5.1 65 | zipp==3.6.0 66 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | """Test configuration with fixtures.""" 2 | 3 | import pytest 4 | 5 | import beobench.experiment.config_parser 6 | 7 | collect_ignore_glob = ["performance"] 8 | 9 | AGENT_SB3 = """ 10 | import stable_baselines3 11 | """ 12 | 13 | AGENT_BROKEN = """ 14 | raise ValueError 15 | """ 16 | 17 | 18 | REQUIREMENTS_SB3 = """ 19 | stable-baselines3[extra] 20 | """ 21 | 22 | 23 | def create_tmp_file(folder, name, content, factory): 24 | file_path = factory.mktemp(folder) / name 25 | file_path.write_text(content) 26 | return file_path 27 | 28 | 29 | @pytest.fixture 30 | def run_config(): 31 | return beobench.experiment.config_parser.get_standard_config("test_energym") 32 | 33 | 34 | @pytest.fixture 35 | def rand_agent_config(): 36 | return beobench.experiment.config_parser.get_standard_config("method_random_action") 37 | 38 | 39 | @pytest.fixture(scope="session") 40 | def agent_sb3(tmp_path_factory): 41 | agent_file_path = create_tmp_file( 42 | "agent_tmp", "agent_sb3.py", AGENT_SB3, tmp_path_factory 43 | ) 44 | return agent_file_path 45 | 46 | 47 | @pytest.fixture(scope="session") 48 | def agent_broken(tmp_path_factory): 49 | agent_file_path = create_tmp_file( 50 | "agent_tmp", "agent_broken.py", AGENT_BROKEN, tmp_path_factory 51 | ) 52 | return agent_file_path 53 | 54 | 55 | @pytest.fixture(scope="session") 56 | def requirements_sb3(tmp_path_factory): 57 | agent_file_path = create_tmp_file( 58 | "requirements_tmp", "requirements.txt", REQUIREMENTS_SB3, tmp_path_factory 59 | ) 60 | return agent_file_path 61 | -------------------------------------------------------------------------------- /docs/envs/BOPTEST_descriptions.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | .. _env-bestest_air: 4 | 5 | ``bestest_air`` 6 | """"""""""""""" 7 | 8 | :Type: residential building 9 | :More info: `framework docs `_ 10 | 11 | 12 | .. _env-bestest_hydronic: 13 | 14 | ``bestest_hydronic`` 15 | """""""""""""""""""" 16 | 17 | :Type: residential building 18 | :More info: `framework docs `_ 19 | 20 | 21 | .. _env-bestest_hydronic_heat_pump: 22 | 23 | ``bestest_hydronic_heat_pump`` 24 | """""""""""""""""""""""""""""" 25 | 26 | :Type: residential building 27 | :More info: `framework docs `_ 28 | 29 | 30 | .. _env-multizone_residential_hydronic: 31 | 32 | ``multizone_residential_hydronic`` 33 | """""""""""""""""""""""""""""""""" 34 | 35 | :Type: residential building 36 | :More info: `framework docs `_ 37 | 38 | 39 | .. _env-singlezone_commercial_hydronic: 40 | 41 | ``singlezone_commercial_hydronic`` 42 | """""""""""""""""""""""""""""""""" 43 | 44 | :Type: office building 45 | :More info: `framework docs `_ 46 | -------------------------------------------------------------------------------- /docs/getting_started.rst: -------------------------------------------------------------------------------- 1 | 2 | .. _sec_quickstart: 3 | 4 | Getting started 5 | =============== 6 | 7 | .. tip:: 8 | This section is (almost) identical to the quickstart section in the repository README. If you already read that one, you may want to skip to the :doc:`advanced usage guides`. 9 | 10 | .. Note that below we import multiple separate parts from the main repo readme. This separation is done to allow us to add rst elements inbetween that can't be parsed by GitHubs rst parser. 11 | 12 | .. include:: ../README.rst 13 | :start-after: start-qs-sec1 14 | :end-before: end-qs-sec1 15 | 16 | 17 | .. admonition:: OS support 18 | 19 | - **Linux:** recommended and tested (Ubuntu 20.04). 20 | - **Windows:** use via `Windows Subsystem for Linux (WSL) `_ recommended. 21 | - **macOS:** experimental support for Apple silicon systems — only intended for development purposes (not running experiments). Intel-based macOS support untested. 22 | 23 | 24 | .. include:: ../README.rst 25 | :start-after: start-qs-sec2 26 | :end-before: end-qs-sec2 27 | 28 | .. note:: 29 | 30 | We can use these two imports *regardless* of the gym framework we are using. This invariability allows us to create agent scripts that work across frameworks. 31 | 32 | .. include:: ../README.rst 33 | :start-after: start-qs-sec3 34 | :end-before: end-qs-sec3 35 | 36 | 37 | Given the configuration and agent script above, we can run the experiment using the command: 38 | 39 | .. include:: ./snippets/run_standard_experiment.rst 40 | 41 | .. include:: ../README.rst 42 | :start-after: start-qs-sec4 43 | :end-before: end-qs-sec4 -------------------------------------------------------------------------------- /beobench/data/configs/method_ppo.yaml: -------------------------------------------------------------------------------- 1 | # Config for RLlib-based PPO agent 2 | 3 | # Some of the descriptions of RLlib config values are taken from 4 | # https://docs.ray.io/en/latest/rllib/rllib-training.html 5 | 6 | agent: 7 | origin: rllib # either path to agent script or name of agent library (rllib) 8 | config: # given to ray.tune.run() as arguments (since rllib set before) 9 | run_or_experiment: PPO 10 | log_to_file: True 11 | stop: 12 | timesteps_total: 35040 # 1 year w 15 min timesteps 13 | config: 14 | lr: 0.005 15 | model: 16 | fcnet_activation: relu 17 | fcnet_hiddens: [256,256,256,256] 18 | post_fcnet_activation: tanh 19 | batch_mode: complete_episodes 20 | gamma: 0.999999 21 | # Number of steps after which the episode is forced to terminate. Defaults 22 | # to `env.spec.max_episode_steps` (if present) for Gym envs. 23 | horizon: 480 24 | # Calculate rewards but don't reset the environment when the horizon is 25 | # hit. This allows value estimation and RNN state to span across logical 26 | # episodes denoted by horizon. This only has an effect if horizon != inf. 27 | soft_horizon: True 28 | # Number of timesteps collected for each SGD round. This defines the size 29 | # of each SGD epoch. 30 | train_batch_size: 480 # 1/5 day of 3min steps 31 | # Total SGD batch size across all devices for SGD. This defines the 32 | # minibatch size within each epoch. 33 | sgd_minibatch_size: 96 34 | metrics_num_episodes_for_smoothing: 1 35 | framework: torch 36 | log_level: "WARNING" 37 | num_workers: 1 # this is required for energym to work (can fail silently otherwise) 38 | num_gpus: 1 39 | general: 40 | wandb_group: ppo -------------------------------------------------------------------------------- /beobench/data/configs/gym_boptest.yaml: -------------------------------------------------------------------------------- 1 | # BOPTEST default config 2 | # Some of the descriptions taken from 3 | # https://github.com/ibpsa/project1-boptest-gym/blob/master/boptestGymEnv.py 4 | 5 | env: 6 | gym: boptest 7 | config: 8 | name: bestest_hydronic_heat_pump 9 | # whether to normalise the observations and actions 10 | normalize: True 11 | discretize: True 12 | gym_kwargs: 13 | actions: ["oveHeaPumY_u"] 14 | # Dictionary mapping observation keys to a tuple with the lower 15 | # and upper bound of each observation. Observation keys must 16 | # belong either to the set of measurements or to the set of 17 | # forecasting variables of the BOPTEST test case. Contrary to 18 | # the actions, the expected minimum and maximum values of the 19 | # measurement and forecasting variables are not provided from 20 | # the BOPTEST framework, although they are still relevant here 21 | # e.g. for normalization or discretization. Therefore, these 22 | # bounds need to be provided by the user. 23 | # If `time` is included as an observation, the time in seconds 24 | # will be passed to the agent. This is the remainder time from 25 | # the beginning of the episode and for periods of the length 26 | # specified in the upper bound of the time feature. 27 | observations: 28 | reaTZon_y: [280.0, 310.0] 29 | # Set to True if desired to use a random start time for each episode 30 | random_start_time: True 31 | # Maximum duration of each episode in seconds 32 | max_episode_length: 31536000 # one year in seconds 33 | # Desired simulation period to initialize each episode 34 | warmup_period: 10 35 | # Sampling time in seconds 36 | step_period: 900 # = 15min -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | 58 | # Flask stuff: 59 | instance/ 60 | .webassets-cache 61 | 62 | # Scrapy stuff: 63 | .scrapy 64 | 65 | # Sphinx documentation 66 | docs/_build/ 67 | 68 | # PyBuilder 69 | target/ 70 | 71 | # Jupyter Notebook 72 | .ipynb_checkpoints 73 | 74 | # pyenv 75 | .python-version 76 | 77 | # celery beat schedule file 78 | celerybeat-schedule 79 | 80 | # SageMath parsed files 81 | *.sage.py 82 | 83 | # dotenv 84 | .env 85 | 86 | # virtualenv 87 | .venv 88 | venv/ 89 | ENV/ 90 | 91 | # Spyder project settings 92 | .spyderproject 93 | .spyproject 94 | 95 | # Rope project settings 96 | .ropeproject 97 | 98 | # mkdocs documentation 99 | /site 100 | 101 | # mypy 102 | .mypy_cache/ 103 | 104 | # IDE settings 105 | .vscode/ 106 | .idea/ 107 | 108 | # Apple 109 | *.DS_Store 110 | 111 | # beobench 112 | beobench_results 113 | -------------------------------------------------------------------------------- /docs/credits.rst: -------------------------------------------------------------------------------- 1 | =================== 2 | Credits and license 3 | =================== 4 | 5 | .. include:: ../AUTHORS.rst 6 | 7 | Further acknowledgements 8 | ------------------------ 9 | 10 | The initial package structure was created using Cookiecutter_ and the `audreyr/cookiecutter-pypackage`_ project template. 11 | 12 | .. _Cookiecutter: https://github.com/audreyr/cookiecutter 13 | .. _`audreyr/cookiecutter-pypackage`: https://github.com/audreyr/cookiecutter-pypackage 14 | 15 | License 16 | ------- 17 | 18 | The code in this repository is published under MIT license (see ``LICENSE`` file). 19 | 20 | Further, the icons used in the environment list in the documentation are licensed under the following MIT license: 21 | 22 | .. code-block:: txt 23 | 24 | MIT License 25 | 26 | Copyright (c) 2020 Paweł Kuna 27 | 28 | Permission is hereby granted, free of charge, to any person obtaining a copy 29 | of this software and associated documentation files (the "Software"), to deal 30 | in the Software without restriction, including without limitation the rights 31 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 32 | copies of the Software, and to permit persons to whom the Software is 33 | furnished to do so, subject to the following conditions: 34 | 35 | The above copyright notice and this permission notice shall be included in all 36 | copies or substantial portions of the Software. 37 | 38 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 39 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 40 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 41 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 42 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 43 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 44 | SOFTWARE. 45 | -------------------------------------------------------------------------------- /tests/test_run.py: -------------------------------------------------------------------------------- 1 | """Module to test main run command.""" 2 | 3 | import pytest 4 | import subprocess 5 | 6 | import beobench 7 | 8 | CMD_CONFIG = { 9 | "dev_path": ".", 10 | "force_build": True, 11 | } 12 | 13 | 14 | @pytest.mark.slow 15 | def test_run_local(run_config): 16 | """Run beobench experiment using current state of beobench in 17 | experiment container.""" 18 | beobench.run(config=run_config, **CMD_CONFIG) 19 | 20 | 21 | @pytest.mark.skip( 22 | reason=( 23 | "This test may fail on breaking changes between versions," 24 | " thus not running by default." 25 | ) 26 | ) 27 | @pytest.mark.slow 28 | def test_run_pypi(run_config): 29 | """Run beobench experiment using latest pypi-beobench in experiment container.""" 30 | beobench.run(config=run_config) 31 | 32 | 33 | @pytest.mark.slow 34 | def test_null_env_config(): 35 | """Run beobench experiment using latest pypi-beobench in experiment container.""" 36 | config = { 37 | "env": {"config": None}, 38 | "agent": {"config": {"stop": {"timesteps_total": 10}}}, 39 | } 40 | beobench.run(config=config, **CMD_CONFIG) 41 | 42 | 43 | @pytest.mark.slow 44 | def test_reqs_install(agent_sb3, requirements_sb3): 45 | config = { 46 | "agent": { 47 | "origin": str(agent_sb3), 48 | "requirements": str(requirements_sb3), 49 | "config": {"stop": {"timesteps_total": 10}}, 50 | }, 51 | } 52 | beobench.run(config=config, **CMD_CONFIG) 53 | 54 | 55 | @pytest.mark.slow 56 | def test_broken_agent_install(agent_broken, requirements_sb3): 57 | """Ensure broken agent error propagates to the main script.""" 58 | config = { 59 | "agent": { 60 | "origin": str(agent_broken), 61 | "requirements": str(requirements_sb3), 62 | "config": {"stop": {"timesteps_total": 10}}, 63 | }, 64 | } 65 | with pytest.raises(subprocess.CalledProcessError): 66 | beobench.run(config=config, **CMD_CONFIG) 67 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | 58 | # Flask stuff: 59 | instance/ 60 | .webassets-cache 61 | 62 | # Scrapy stuff: 63 | .scrapy 64 | 65 | # Sphinx documentation 66 | docs/_build/ 67 | 68 | # PyBuilder 69 | target/ 70 | 71 | # Jupyter Notebook 72 | .ipynb_checkpoints 73 | 74 | # pyenv 75 | .python-version 76 | 77 | # celery beat schedule file 78 | celerybeat-schedule 79 | 80 | # SageMath parsed files 81 | *.sage.py 82 | 83 | # dotenv 84 | .env 85 | 86 | # virtualenv 87 | .venv 88 | venv/ 89 | ENV/ 90 | 91 | # Spyder project settings 92 | .spyderproject 93 | .spyproject 94 | 95 | # Rope project settings 96 | .ropeproject 97 | 98 | # mkdocs documentation 99 | /site 100 | 101 | # mypy 102 | .mypy_cache/ 103 | 104 | # IDE settings 105 | .vscode/ 106 | .idea/ 107 | 108 | # Apple 109 | *.DS_Store 110 | 111 | # Ray RLLib 112 | tmp/ 113 | wandb/ 114 | 115 | # Nohup 116 | *nohup.out 117 | docs/generated/ 118 | 119 | #beobench 120 | beobench_results* 121 | notebooks/archive 122 | .beobench.yml 123 | perf_tests* -------------------------------------------------------------------------------- /docs/envs/envs_list.rst: -------------------------------------------------------------------------------- 1 | :BOPTEST: 2 | - `bestest_air`_ 3 | - `bestest_hydronic`_ 4 | - `bestest_hydronic_heat_pump`_ 5 | - `multizone_residential_hydronic`_ 6 | - `singlezone_commercial_hydronic`_ 7 | 8 | ---- 9 | 10 | :Energym: 11 | - `Apartments2Thermal-v0`_ 12 | - `Apartments2Grid-v0`_ 13 | - `ApartmentsThermal-v0`_ 14 | - `ApartmentsGrid-v0`_ 15 | - `OfficesThermostat-v0`_ 16 | - `MixedUseFanFCU-v0`_ 17 | - `SeminarcenterThermostat-v0`_ 18 | - `SeminarcenterFull-v0`_ 19 | - `SimpleHouseRad-v0`_ 20 | - `SimpleHouseRSla-v0`_ 21 | - `SwissHouseRSlaW2W-v0`_ 22 | - `SwissHouseRSlaA2W-v0`_ 23 | - `SwissHouseRSlaTank-v0`_ 24 | - `SwissHouseRSlaTankDhw-v0`_ 25 | 26 | ---- 27 | 28 | :Sinergym: 29 | - `Eplus-demo-v1`_ 30 | - `Eplus-5Zone-hot-discrete-v1`_ 31 | - `Eplus-5Zone-mixed-discrete-v1`_ 32 | - `Eplus-5Zone-cool-discrete-v1`_ 33 | - `Eplus-5Zone-hot-continuous-v1`_ 34 | - `Eplus-5Zone-mixed-continuous-v1`_ 35 | - `Eplus-5Zone-cool-continuous-v1`_ 36 | - `Eplus-5Zone-hot-discrete-stochastic-v1`_ 37 | - `Eplus-5Zone-mixed-discrete-stochastic-v1`_ 38 | - `Eplus-5Zone-cool-discrete-stochastic-v1`_ 39 | - `Eplus-5Zone-hot-continuous-stochastic-v1`_ 40 | - `Eplus-5Zone-mixed-continuous-stochastic-v1`_ 41 | - `Eplus-5Zone-cool-continuous-stochastic-v1`_ 42 | - `Eplus-datacenter-discrete-v1`_ 43 | - `Eplus-datacenter-continuous-v1`_ 44 | - `Eplus-datacenter-discrete-stochastic-v1`_ 45 | - `Eplus-datacenter-continuous-stochastic-v1`_ 46 | - `Eplus-IWMullion-discrete-v1`_ 47 | - `Eplus-IWMullion-continuous-v1`_ 48 | - `Eplus-IWMullion-discrete-stochastic-v1`_ 49 | - `Eplus-IWMullion-continuous-stochastic-v1`_ 50 | 51 | .. |office| image:: https://raw.githubusercontent.com/tabler/tabler-icons/master/icons/building-skyscraper.svg 52 | .. |home| image:: https://raw.githubusercontent.com/tabler/tabler-icons/master/icons/home.svg 53 | .. |industry| image:: https://raw.githubusercontent.com/tabler/tabler-icons/master/icons/building-factory.svg 54 | -------------------------------------------------------------------------------- /beobench/data/configs/baselines/sinergym_figure4_dqn.yaml: -------------------------------------------------------------------------------- 1 | # Reproduction of experiment shown in Figure 4 of Sinergym paper. 2 | # The paper can be found at: https://dl.acm.org/doi/abs/10.1145/3486611.3488729 3 | # 4 | # Some of the descriptions of RLlib config values are taken from 5 | # https://docs.ray.io/en/latest/rllib/rllib-training.html 6 | # 7 | # From environment output we can deduce that the step size is 15 minutes 8 | # (In january there are 2976 steps / 31 days -> 96 steps per day 9 | # -> step size of 15 min) 10 | 11 | env: 12 | gym: sinergym 13 | name: Eplus-5Zone-hot-discrete-v1 14 | config: 15 | name: Eplus-5Zone-hot-discrete-v1 16 | # whether to normalise the observations 17 | normalize: True 18 | agent: 19 | origin: rllib 20 | config: 21 | run_or_experiment: DQN 22 | config: 23 | lr: 0.0001 24 | gamma: 0.99 25 | # Number of steps after which the episode is forced to terminate. Defaults 26 | # to `env.spec.max_episode_steps` (if present) for Gym envs. 27 | horizon: 24 # one week 672 = 96 * 7 # other previous values: 96 # 10000 # 28 | # Calculate rewards but don't reset the environment when the horizon is 29 | # hit. This allows value estimation and RNN state to span across logical 30 | # episodes denoted by horizon. This only has an effect if horizon != inf. 31 | soft_horizon: True 32 | num_workers: 1 # this is required, otherwise effectively assuming simulator. 33 | # Training batch size, if applicable. Should be >= rollout_fragment_length. 34 | # Samples batches will be concatenated together to a batch of this size, 35 | # which is then passed to SGD. 36 | train_batch_size: 24 37 | stop: 38 | timesteps_total: 105120 # = 3 years # 35040 # = 365 * 96 (full year) 39 | wrappers: 40 | - origin: general 41 | class: WandbLogger 42 | config: 43 | log_freq: 1 44 | summary_metric_keys: 45 | - env.returns.reward 46 | - env.returns.info.total_power 47 | - env.returns.info.comfort_penalty 48 | general: 49 | wandb_project: sinergym_fig4_baseline 50 | num_samples: 5 51 | 52 | -------------------------------------------------------------------------------- /beobench/experiment/provider.py: -------------------------------------------------------------------------------- 1 | """ The experiment provider provides access to environments inside containers.""" 2 | 3 | import beobench.experiment.config_parser 4 | import importlib 5 | from beobench.constants import CONTAINER_RO_DIR, AVAILABLE_WRAPPERS 6 | 7 | try: 8 | import env_creator # pylint: disable=import-outside-toplevel,import-error 9 | except ImportError as e: 10 | raise ImportError( 11 | ( 12 | "Cannot import env_creator module. Is Beobench being executed" 13 | "inside a Beobench experiment container?" 14 | ) 15 | ) from e 16 | 17 | config = beobench.experiment.config_parser.parse(CONTAINER_RO_DIR / "config.yaml") 18 | 19 | 20 | def create_env(env_config: dict = None) -> object: 21 | """Create environment. 22 | 23 | Create environment from Beobench integration currently being used. 24 | This only works INSIDE a beobench experiment container. 25 | 26 | Args: 27 | env_config (dict, optional): env configuration. Defaults to None. 28 | 29 | Returns: 30 | object: environment instance 31 | """ 32 | 33 | # Access env_creator script that is only available inside 34 | # experiment container. 35 | if env_config is None: 36 | env_config = config["env"]["config"] 37 | 38 | env = env_creator.create_env(env_config) 39 | 40 | for wrapper_dict in config["wrappers"]: 41 | wrapper = _get_wrapper(wrapper_dict) 42 | if "config" in wrapper_dict.keys(): 43 | wrapper_config = wrapper_dict["config"] 44 | else: 45 | wrapper_config = {} 46 | env = wrapper(env, **wrapper_config) 47 | 48 | return env 49 | 50 | 51 | def _get_wrapper(wrapper_dict): 52 | origin = wrapper_dict["origin"] 53 | 54 | if origin in AVAILABLE_WRAPPERS: 55 | wrapper_module_str = "beobench.wrappers." + origin 56 | wrapper_module = importlib.import_module(wrapper_module_str) 57 | wrapper_class = getattr(wrapper_module, wrapper_dict["class"]) 58 | return wrapper_class 59 | else: 60 | raise NotImplementedError("Non-standard wrappers are currently not supported.") 61 | -------------------------------------------------------------------------------- /beobench/data/agents/random_action.py: -------------------------------------------------------------------------------- 1 | """Random agent for testing beobench experiment containers""" 2 | 3 | import wandb 4 | import numpy as np 5 | 6 | from beobench.experiment.provider import config, create_env 7 | 8 | # Setting up experiment tracking via wandb 9 | wandb_used = config["general"]["wandb_project"] is not None 10 | if wandb_used: 11 | wandb.init( 12 | config={"beobench": config}, 13 | project=config["general"]["wandb_project"], 14 | entity=config["general"]["wandb_entity"], 15 | group=config["general"]["wandb_group"], 16 | name="random_agent_" + wandb.util.generate_id(), 17 | ) 18 | 19 | # Determining length of experiment (if set) 20 | try: 21 | num_timesteps = config["agent"]["config"]["stop"]["timesteps_total"] 22 | except KeyError: 23 | num_timesteps = 10 24 | 25 | # Determine max length of an episode 26 | try: 27 | horizon = config["agent"]["config"]["config"]["horizon"] 28 | except KeyError: 29 | horizon = 1000 30 | 31 | try: 32 | imitate_rllib_env_checks = config["agent"]["config"]["imitate_rllib_env_checks"] 33 | except KeyError: 34 | imitate_rllib_env_checks = False 35 | 36 | 37 | print("Random agent: starting test.") 38 | 39 | env = create_env() 40 | 41 | if imitate_rllib_env_checks: 42 | # RLlib appears to reset and take single action in env 43 | # this may be to check compliance of env with space etc. 44 | env.reset() 45 | action = env.action_space.sample() 46 | _, _, _, _ = env.step(action) 47 | 48 | 49 | observation = env.reset() 50 | 51 | num_steps_per_ep = 0 52 | episode = 0 53 | ep_rewards = [] 54 | 55 | infos = [] 56 | for _ in range(num_timesteps): 57 | episode += 1 58 | num_steps_per_ep += 1 59 | 60 | action = env.action_space.sample() 61 | observation, reward, done, info = env.step(action) 62 | 63 | infos.append(info) 64 | 65 | ep_rewards.append(reward) 66 | 67 | if done or num_steps_per_ep >= horizon: 68 | 69 | if wandb_used: 70 | wandb.log({"episode_reward_mean": np.sum(ep_rewards), "step": episode}) 71 | 72 | num_steps_per_ep = 0 73 | ep_rewards = [] 74 | if done: 75 | observation = env.reset() 76 | env.close() 77 | 78 | print("Random agent: completed test.") 79 | -------------------------------------------------------------------------------- /beobench/data/configs/baselines/boptest_arroyo2022_random_agent.yaml: -------------------------------------------------------------------------------- 1 | # A first attempt at reproduction of experiments in the following paper by Arroyo et al. 2 | # https://lirias.kuleuven.be/retrieve/658452 3 | # 4 | # Some of the descriptions of RLlib config values are taken from 5 | # https://docs.ray.io/en/latest/rllib/rllib-training.html 6 | # other from 7 | # https://github.com/ibpsa/project1-boptest-gym/blob/master/boptestGymEnv.py 8 | 9 | env: 10 | gym: boptest 11 | name: bestest_hydronic_heat_pump 12 | config: 13 | boptest_testcase: bestest_hydronic_heat_pump 14 | # whether to normalise the observations and actions 15 | normalize: True 16 | gym_kwargs: 17 | actions: ["oveHeaPumY_u"] 18 | # Dictionary mapping observation keys to a tuple with the lower 19 | # and upper bound of each observation. Observation keys must 20 | # belong either to the set of measurements or to the set of 21 | # forecasting variables of the BOPTEST test case. Contrary to 22 | # the actions, the expected minimum and maximum values of the 23 | # measurement and forecasting variables are not provided from 24 | # the BOPTEST framework, although they are still relevant here 25 | # e.g. for normalization or discretization. Therefore, these 26 | # bounds need to be provided by the user. 27 | # If `time` is included as an observation, the time in seconds 28 | # will be passed to the agent. This is the remainder time from 29 | # the beginning of the episode and for periods of the length 30 | # specified in the upper bound of the time feature. 31 | observations: 32 | reaTZon_y: [280.0, 310.0] 33 | # Set to True if desired to use a random start time for each episode 34 | random_start_time: True 35 | # Maximum duration of each episode in seconds 36 | max_episode_length: 31536000 # one year in seconds 37 | # Desired simulation period to initialize each episode 38 | warmup_period: 10 39 | # Sampling time in seconds 40 | step_period: 900 # = 15min 41 | agent: 42 | origin: random_action 43 | config: 44 | config: 45 | horizon: 96 46 | stop: 47 | timesteps_total: 10000 48 | imitate_rllib_env_checks: True 49 | wrappers: 50 | - origin: general 51 | class: WandbLogger 52 | config: 53 | log_freq: 1 54 | summary_metric_keys: 55 | - env.returns.reward 56 | general: 57 | wandb_project: boptest_arroyo2022_baseline 58 | wandb_group: random_action 59 | num_samples: 1 -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """The setup script.""" 4 | 5 | from setuptools import setup, find_packages 6 | 7 | with open("PYPI_README.rst", encoding="UTF-8") as readme_file: 8 | readme = readme_file.read() 9 | 10 | with open("HISTORY.rst", encoding="UTF-8") as history_file: 11 | history = history_file.read() 12 | 13 | version = "0.5.4" # pylint: disable=invalid-name 14 | 15 | requirements = [ 16 | "docker", 17 | "click", 18 | "pyyaml", 19 | "importlib-resources", # backport of importlib.resources, required for Python<=3.8 20 | "loguru", 21 | ] 22 | 23 | # The extended requirements are only used inside experiment/gym containers 24 | extended_requirements = [ 25 | "gym", 26 | "wandb", 27 | ] 28 | 29 | rllib_requirements = [ 30 | "ray[rllib]<=2.1.0", # see https://github.com/ray-project/ray/pull/31331 31 | "numpy<1.24.0", # see https://github.com/ray-project/ray/issues/31293 32 | "torch", 33 | "gym", 34 | "wandb", 35 | ] 36 | 37 | setup( 38 | author="Beobench authors", 39 | python_requires=">=3.6", 40 | classifiers=[ 41 | "Development Status :: 3 - Alpha", 42 | "Intended Audience :: Developers", 43 | "License :: OSI Approved :: MIT License", 44 | "Natural Language :: English", 45 | "Programming Language :: Python :: 3", 46 | "Programming Language :: Python :: 3.6", 47 | "Programming Language :: Python :: 3.7", 48 | "Programming Language :: Python :: 3.8", 49 | "Programming Language :: Python :: 3.9", 50 | ], 51 | description="Beobench is a toolkit providing easy and unified access to building control environments for reinforcement learning (RL).", # pylint: disable=line-too-long 52 | install_requires=requirements, 53 | extras_require={ 54 | "extended": extended_requirements, 55 | "rllib": rllib_requirements, 56 | }, 57 | license="MIT license", 58 | long_description=readme + "\n\n" + history, 59 | include_package_data=True, 60 | keywords="beobench", 61 | name="beobench", 62 | packages=find_packages(include=["beobench", "beobench.*"]), 63 | entry_points={ 64 | "console_scripts": [ 65 | "beobench = beobench.cli:cli", 66 | ], 67 | }, 68 | test_suite="tests", 69 | project_urls={ 70 | "Documentation": "https://beobench.readthedocs.io/", 71 | "Code": "https://github.com/rdnfn/beobench", 72 | "Issue tracker": "https://github.com/rdnfn/beobench/issues", 73 | }, 74 | zip_safe=False, 75 | version=version, 76 | ) 77 | -------------------------------------------------------------------------------- /beobench/data/configs/archive/rewex01_grid02.yaml: -------------------------------------------------------------------------------- 1 | # REWEX Experiment 01 2 | # Run with the command 3 | # beobench run -c beobench/experiment/definitions/rewex01.yaml -d . --use-gpu --docker-shm-size 28gb 4 | 5 | # Some of the descriptions of RLlib config values are taken from 6 | # https://docs.ray.io/en/latest/rllib/rllib-training.html 7 | 8 | # agent config 9 | agent: 10 | origin: rllib # either path to agent script or name of agent library (rllib) 11 | config: # given to ray.tune.run() as arguments (since rllib set before) 12 | run_or_experiment: PPO 13 | stop: 14 | timesteps_total: 35040 15 | config: 16 | lr: 0.00005 17 | model: 18 | fcnet_activation: relu 19 | fcnet_hiddens: [256,256,256,256] 20 | post_fcnet_activation: tanh 21 | batch_mode: complete_episodes 22 | gamma: 0.999 23 | # Number of steps after which the episode is forced to terminate. Defaults 24 | # to `env.spec.max_episode_steps` (if present) for Gym envs. 25 | horizon: 96 26 | # Calculate rewards but don't reset the environment when the horizon is 27 | # hit. This allows value estimation and RNN state to span across logical 28 | # episodes denoted by horizon. This only has an effect if horizon != inf. 29 | soft_horizon: True 30 | # Number of timesteps collected for each SGD round. This defines the size 31 | # of each SGD epoch. 32 | train_batch_size: 94 # single day of 15min steps 33 | # Total SGD batch size across all devices for SGD. This defines the 34 | # minibatch size within each epoch. 35 | sgd_minibatch_size: 36 | grid_search: 37 | - 24 38 | - 48 39 | - 96 40 | metrics_smoothing_episodes: 1 41 | framework: torch 42 | log_level: "WARNING" 43 | num_workers: 1 # this is required for energym to work (can fail silently otherwise) 44 | num_gpus: 1 45 | # environment config 46 | env: 47 | name: Apartments2Thermal-v0 48 | gym: energym 49 | config: 50 | # Number of simulation days 51 | days: 365 52 | # Name of energym environment 53 | energym_environment: Apartments2Thermal-v0 54 | gym_kwargs: 55 | # Maximum number of timesteps in one episode 56 | max_episode_length: 35040 # corresponds to a year of 15 min steps 57 | # User-provided flag to require state and action spaces to be normalized 58 | normalize: True 59 | # Number of real-world minutes between timesteps in building simulation 60 | step_period: 15 61 | # Weather file to use for the scenario (possible files depend on env chosen) 62 | weather: ESP_CT_Barcelona -------------------------------------------------------------------------------- /beobench/data/configs/archive/rewex01_grid.yaml: -------------------------------------------------------------------------------- 1 | # REWEX Experiment 01 2 | # Run with the command 3 | # beobench run -c beobench/experiment/definitions/rewex01.yaml -d . --use-gpu --docker-shm-size 28gb 4 | 5 | # Some of the descriptions of RLlib config values are taken from 6 | # https://docs.ray.io/en/latest/rllib/rllib-training.html 7 | 8 | # agent config 9 | agent: 10 | origin: rllib # either path to agent script or name of agent library (rllib) 11 | config: # given to ray.tune.run() as arguments (since rllib set before) 12 | run_or_experiment: PPO 13 | stop: 14 | timesteps_total: 35040 15 | config: 16 | lr: 17 | grid_search: 18 | - 0.0005 19 | - 0.00005 20 | - 0.00001 21 | - 0.000001 22 | - 0.0000001 23 | model: 24 | fcnet_activation: relu 25 | fcnet_hiddens: [256,256,256,256] 26 | post_fcnet_activation: tanh 27 | batch_mode: complete_episodes 28 | gamma: 0.999 29 | # Number of steps after which the episode is forced to terminate. Defaults 30 | # to `env.spec.max_episode_steps` (if present) for Gym envs. 31 | horizon: 96 32 | # Calculate rewards but don't reset the environment when the horizon is 33 | # hit. This allows value estimation and RNN state to span across logical 34 | # episodes denoted by horizon. This only has an effect if horizon != inf. 35 | soft_horizon: True 36 | # Number of timesteps collected for each SGD round. This defines the size 37 | # of each SGD epoch. 38 | train_batch_size: 96 # single day of 15min steps 39 | # Total SGD batch size across all devices for SGD. This defines the 40 | # minibatch size within each epoch. 41 | sgd_minibatch_size: 24 42 | metrics_smoothing_episodes: 1 43 | framework: torch 44 | log_level: "WARNING" 45 | num_workers: 1 # this is required for energym to work (can fail silently otherwise) 46 | num_gpus: 1 47 | # environment config 48 | env: 49 | name: Apartments2Thermal-v0 50 | gym: energym 51 | config: 52 | # Number of simulation days 53 | days: 365 54 | # Name of energym environment 55 | energym_environment: Apartments2Thermal-v0 56 | gym_kwargs: 57 | # Maximum number of timesteps in one episode 58 | max_episode_length: 35040 # corresponds to a year of 15 min steps 59 | # User-provided flag to require state and action spaces to be normalized 60 | normalize: True 61 | # Number of real-world minutes between timesteps in building simulation 62 | step_period: 15 63 | # Weather file to use for the scenario (possible files depend on env chosen) 64 | weather: ESP_CT_Barcelona -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean clean-test clean-pyc clean-build docs help 2 | .DEFAULT_GOAL := help 3 | 4 | define BROWSER_PYSCRIPT 5 | import os, webbrowser, sys 6 | 7 | from urllib.request import pathname2url 8 | 9 | webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1]))) 10 | endef 11 | export BROWSER_PYSCRIPT 12 | 13 | define PRINT_HELP_PYSCRIPT 14 | import re, sys 15 | 16 | for line in sys.stdin: 17 | match = re.match(r'^([a-zA-Z_-]+):.*?## (.*)$$', line) 18 | if match: 19 | target, help = match.groups() 20 | print("%-20s %s" % (target, help)) 21 | endef 22 | export PRINT_HELP_PYSCRIPT 23 | 24 | BROWSER := python -c "$$BROWSER_PYSCRIPT" 25 | 26 | help: 27 | @python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST) 28 | 29 | clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts 30 | 31 | clean-build: ## remove build artifacts 32 | rm -fr build/ 33 | rm -fr dist/ 34 | rm -fr .eggs/ 35 | find . -name '*.egg-info' -exec rm -fr {} + 36 | find . -name '*.egg' -exec rm -f {} + 37 | 38 | clean-pyc: ## remove Python file artifacts 39 | find . -name '*.pyc' -exec rm -f {} + 40 | find . -name '*.pyo' -exec rm -f {} + 41 | find . -name '*~' -exec rm -f {} + 42 | find . -name '__pycache__' -exec rm -fr {} + 43 | 44 | clean-test: ## remove test and coverage artifacts 45 | rm -fr .tox/ 46 | rm -f .coverage 47 | rm -fr htmlcov/ 48 | rm -fr .pytest_cache 49 | 50 | lint/flake8: ## check style with flake8 51 | flake8 beobench tests 52 | lint/black: ## check style with black 53 | black --check beobench tests 54 | 55 | lint: lint/flake8 lint/black ## check style 56 | 57 | test: ## run tests quickly with the default Python 58 | python setup.py test 59 | 60 | test-all: ## run tests on every Python version with tox 61 | tox 62 | 63 | coverage: ## check code coverage quickly with the default Python 64 | coverage run --source beobench setup.py test 65 | coverage report -m 66 | coverage html 67 | $(BROWSER) htmlcov/index.html 68 | 69 | docs: ## generate Sphinx HTML documentation, including API docs 70 | rm -f docs/beobench.rst 71 | rm -f docs/modules.rst 72 | sphinx-apidoc -o docs/ beobench 73 | $(MAKE) -C docs clean 74 | $(MAKE) -C docs html 75 | $(BROWSER) docs/_build/html/index.html 76 | 77 | servedocs: docs ## compile the docs watching for changes 78 | watchmedo shell-command -p '*.rst' -c '$(MAKE) -C docs html' -R -D . 79 | 80 | release: dist ## package and upload a release 81 | twine upload dist/* 82 | 83 | dist: clean ## builds source and wheel package 84 | python setup.py sdist 85 | python setup.py bdist_wheel 86 | ls -l dist 87 | 88 | install: clean ## install the package to the active Python's site-packages 89 | python setup.py install 90 | -------------------------------------------------------------------------------- /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | # See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.205.2/containers/python-3/.devcontainer/base.Dockerfile 2 | 3 | # [Choice] Python version (use -bullseye variants on local arm64/Apple Silicon): 3, 3.10, 3.9, 3.8, 3.7, 3.6, 3-bullseye, 3.10-bullseye, 3.9-bullseye, 3.8-bullseye, 3.7-bullseye, 3.6-bullseye, 3-buster, 3.10-buster, 3.9-buster, 3.8-buster, 3.7-buster, 3.6-buster 4 | ARG VARIANT="3.10" 5 | FROM mcr.microsoft.com/vscode/devcontainers/python:0-${VARIANT} 6 | 7 | # [Choice] Node.js version: none, lts/*, 16, 14, 12, 10 8 | ARG NODE_VERSION="none" 9 | RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi 10 | 11 | # [Optional] If your pip requirements rarely change, uncomment this section to add them to the image. 12 | COPY requirements/dev_requirements.txt requirements/doc_requirements.txt /tmp/pip-tmp/ 13 | RUN pip3 install --upgrade pip 14 | RUN pip3 --disable-pip-version-check --no-cache-dir install -r /tmp/pip-tmp/dev_requirements.txt \ 15 | -r /tmp/pip-tmp/doc_requirements.txt \ 16 | && rm -rf /tmp/pip-tmp 17 | 18 | # [Optional] install nvidia container runtime (custom to beobench, this enables gpu-using containers within devcontainer) 19 | ARG NVIDIA_SUPPORT="none" 20 | RUN if [ "${NVIDIA_SUPPORT}" != "none" ]; then wget -O tmp/nvidia-container-toolkit.deb https://nvidia.github.io/libnvidia-container/stable/debian10/amd64/nvidia-container-toolkit_1.7.0-1_amd64.deb \ 21 | && wget -O tmp/nvidia-container-tools.deb https://nvidia.github.io/libnvidia-container/stable/debian10/amd64/libnvidia-container-tools_1.7.0-1_amd64.deb \ 22 | && wget -O tmp/libnvidia-container.deb https://nvidia.github.io/libnvidia-container/stable/debian10/amd64/libnvidia-container1_1.7.0-1_amd64.deb \ 23 | && dpkg --install --recursive tmp \ 24 | && rm tmp/nvidia-container-toolkit.deb; fi 25 | 26 | 27 | 28 | # [Optional] This enables that BOPTEST example files work. Note that this adds 29 | # potentially some not so easily identifiable paths to the Python namespace. 30 | # E.g. `examples` 31 | # ENV PYTHONPATH "${PYTHONPATH}:/opt/beobench/boptest:/opt/beobench/boptest_gym:/opt/beobench" 32 | 33 | 34 | # Install background dependencies for OpenCV (for BOPTEST gym) 35 | # See https://stackoverflow.com/a/63377623 36 | # This should no longer be necessary - as BOPTEST gym has reduced dependencies now 37 | #RUN apt-get update 38 | #RUN apt-get install ffmpeg libsm6 libxext6 -y 39 | 40 | # Install boptest gym 41 | # No long necessary: installer installs this now as well 42 | #RUN pip3 --disable-pip-version-check --no-cache-dir install git+https://github.com/rdnfn/project1-boptest-gym.git@rdnfn/feature-packaging -------------------------------------------------------------------------------- /docs/guides/add_env.rst: -------------------------------------------------------------------------------- 1 | Adding your own environment 2 | ----------------------------- 3 | 4 | This guide explains how to add a new reinforcement learning (RL) environment to beobench. 5 | 6 | .. admonition:: Why should I use this? 7 | 8 | Adding your RL environment to beobench allows you to test all of RLlib's methods on your environment without actually implementing any methods yourself. It also allows you to compare the performance of agents across your and other beobench environments, enabling you to compare your environment's difficulty. 9 | 10 | 11 | Creating build context 12 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 13 | 14 | To add an environment to beobench we need to create a special *docker build context* (for more details `see the official docker build documentation `_). Such a beobench-specific *docker build context* consists at least of the following two files in a folder ``/``: 15 | 16 | 1. A dockerfile ``Dockerfile`` that defines a docker container that has everything necessary for your environment installed. In addition to any of your packages/modules, the dockerfile must also install `beobench` via pip. 17 | 2. An ``env_creator.py`` file that defines a function with the signature ``create_env(env_config: dict = None) -> gym.Env``. This ``create_env()`` function should take an ``env_config`` dictionary (that completely configures your environment) as input and return an instance of your environment with this configuration. If your environment is not yet implementing the commonly used ``gym.Env`` class (`see here `_), you will need to wrap your environment in a class that implements this ``gym.Env`` class within the ``create_env()`` function. 18 | 19 | The path to the folder with these two files, ``path/to/folder//``, can either be on your local file system or on github. It can also contain additional files that help with the installation process. 20 | 21 | **Example**: for an example of such a *docker context folder* see `the official BOPTEST integration folder `_. 22 | 23 | 24 | Defining experiment 25 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 26 | 27 | In order run an experiment on your gym, we need to add the build context to an experiment definition. This can be done by adding/changing the following parameter in your experiment definition file ``config.yaml`` (see :doc:`/guides/intro_experiment` for how to create a complete experiment definition file): 28 | 29 | .. code-block:: yaml 30 | 31 | # ... 32 | env: 33 | gym: "path/to/folder//" 34 | # ... 35 | 36 | 37 | For example, we could set the ``gym`` key to ``"https://github.com/rdnfn/beobench_contrib.git#main:gyms/boptest"``. 38 | 39 | .. warning:: 40 | 41 | Only set ``gym`` to experiment build contexts from authors that you trust. This setting can create an arbitrary docker container on your system. 42 | 43 | Running experiment 44 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 45 | 46 | With a complete experiment definition file ``config.yaml``, we can then use the standard command below to start the experiment: 47 | 48 | .. include:: ../snippets/run_standard_experiment.rst 49 | 50 | Done! You have now successfully integrated your RL environment with beobench. -------------------------------------------------------------------------------- /beobench/data/configs/baselines/boptest_arroyo2022_dqn.yaml: -------------------------------------------------------------------------------- 1 | # A first attempt at reproduction of experiments in the following paper by Arroyo et al. 2 | # https://lirias.kuleuven.be/retrieve/658452 3 | # 4 | # Some of the descriptions of RLlib config values are taken from 5 | # https://docs.ray.io/en/latest/rllib/rllib-training.html 6 | # other from 7 | # https://github.com/ibpsa/project1-boptest-gym/blob/master/boptestGymEnv.py 8 | 9 | env: 10 | gym: boptest 11 | config: 12 | name: bestest_hydronic_heat_pump 13 | # whether to normalise the observations and actions 14 | normalize: True 15 | discretize: True 16 | gym_kwargs: 17 | actions: ["oveHeaPumY_u"] 18 | # Dictionary mapping observation keys to a tuple with the lower 19 | # and upper bound of each observation. Observation keys must 20 | # belong either to the set of measurements or to the set of 21 | # forecasting variables of the BOPTEST test case. Contrary to 22 | # the actions, the expected minimum and maximum values of the 23 | # measurement and forecasting variables are not provided from 24 | # the BOPTEST framework, although they are still relevant here 25 | # e.g. for normalization or discretization. Therefore, these 26 | # bounds need to be provided by the user. 27 | # If `time` is included as an observation, the time in seconds 28 | # will be passed to the agent. This is the remainder time from 29 | # the beginning of the episode and for periods of the length 30 | # specified in the upper bound of the time feature. 31 | observations: 32 | reaTZon_y: [280.0, 310.0] 33 | # Set to True if desired to use a random start time for each episode 34 | random_start_time: True 35 | # Maximum duration of each episode in seconds 36 | max_episode_length: 31536000 # one year in seconds 37 | # Desired simulation period to initialize each episode 38 | warmup_period: 10 39 | # Sampling time in seconds 40 | step_period: 900 # = 15min 41 | agent: 42 | origin: rllib 43 | config: 44 | run_or_experiment: DQN 45 | config: 46 | lr: 0.0001 47 | gamma: 0.99 48 | # Number of steps after which the episode is forced to terminate. Defaults 49 | # to `env.spec.max_episode_steps` (if present) for Gym envs. 50 | horizon: 24 # one week 672 = 96 * 7 # other previous values: 96 # 10000 # 51 | # Calculate rewards but don't reset the environment when the horizon is 52 | # hit. This allows value estimation and RNN state to span across logical 53 | # episodes denoted by horizon. This only has an effect if horizon != inf. 54 | soft_horizon: True 55 | num_workers: 1 # this is required, otherwise effectively assuming simulator. 56 | # Training batch size, if applicable. Should be >= rollout_fragment_length. 57 | # Samples batches will be concatenated together to a batch of this size, 58 | # which is then passed to SGD. 59 | train_batch_size: 24 60 | stop: 61 | timesteps_total: 105120 # = 3 years # 35040 # = 365 * 96 (full year) 62 | wrappers: 63 | - origin: general 64 | class: WandbLogger 65 | config: 66 | log_freq: 1 67 | summary_metric_keys: 68 | - env.returns.reward 69 | general: 70 | wandb_project: boptest_arroyo2022_baseline 71 | wandb_group: random_action 72 | num_samples: 1 -------------------------------------------------------------------------------- /docs/envs/Energym_descriptions.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | .. _env-Apartments2Thermal-v0: 4 | 5 | ``Apartments2Thermal-v0`` 6 | """"""""""""""""""""""""" 7 | 8 | :Type: residential building 9 | :More info: `framework docs `_ 10 | 11 | 12 | .. _env-Apartments2Grid-v0: 13 | 14 | ``Apartments2Grid-v0`` 15 | """""""""""""""""""""" 16 | 17 | :Type: residential building 18 | :More info: `framework docs `_ 19 | 20 | 21 | .. _env-ApartmentsThermal-v0: 22 | 23 | ``ApartmentsThermal-v0`` 24 | """""""""""""""""""""""" 25 | 26 | :Type: residential building 27 | :More info: `framework docs `_ 28 | 29 | 30 | .. _env-ApartmentsGrid-v0: 31 | 32 | ``ApartmentsGrid-v0`` 33 | """"""""""""""""""""" 34 | 35 | :Type: residential building 36 | :More info: `framework docs `_ 37 | 38 | 39 | .. _env-OfficesThermostat-v0: 40 | 41 | ``OfficesThermostat-v0`` 42 | """""""""""""""""""""""" 43 | 44 | :Type: office building 45 | :More info: `framework docs `_ 46 | 47 | 48 | .. _env-MixedUseFanFCU-v0: 49 | 50 | ``MixedUseFanFCU-v0`` 51 | """"""""""""""""""""" 52 | 53 | :Type: office building 54 | :More info: `framework docs `_ 55 | 56 | 57 | .. _env-SeminarcenterThermostat-v0: 58 | 59 | ``SeminarcenterThermostat-v0`` 60 | """""""""""""""""""""""""""""" 61 | 62 | :Type: office building 63 | :More info: `framework docs `_ 64 | 65 | 66 | .. _env-SeminarcenterFull-v0: 67 | 68 | ``SeminarcenterFull-v0`` 69 | """""""""""""""""""""""" 70 | 71 | :Type: office building 72 | :More info: `framework docs `_ 73 | 74 | 75 | .. _env-SimpleHouseRad-v0: 76 | 77 | ``SimpleHouseRad-v0`` 78 | """"""""""""""""""""" 79 | 80 | :Type: residential building 81 | :More info: `framework docs `_ 82 | 83 | 84 | .. _env-SimpleHouseRSla-v0: 85 | 86 | ``SimpleHouseRSla-v0`` 87 | """""""""""""""""""""" 88 | 89 | :Type: residential building 90 | :More info: `framework docs `_ 91 | 92 | 93 | .. _env-SwissHouseRSlaW2W-v0: 94 | 95 | ``SwissHouseRSlaW2W-v0`` 96 | """""""""""""""""""""""" 97 | 98 | :Type: residential building 99 | :More info: `framework docs `_ 100 | 101 | 102 | .. _env-SwissHouseRSlaA2W-v0: 103 | 104 | ``SwissHouseRSlaA2W-v0`` 105 | """""""""""""""""""""""" 106 | 107 | :Type: residential building 108 | :More info: `framework docs `_ 109 | 110 | 111 | .. _env-SwissHouseRSlaTank-v0: 112 | 113 | ``SwissHouseRSlaTank-v0`` 114 | """"""""""""""""""""""""" 115 | 116 | :Type: residential building 117 | :More info: `framework docs `_ 118 | 119 | 120 | .. _env-SwissHouseRSlaTankDhw-v0: 121 | 122 | ``SwissHouseRSlaTankDhw-v0`` 123 | """""""""""""""""""""""""""" 124 | 125 | :Type: residential building 126 | :More info: `framework docs `_ 127 | -------------------------------------------------------------------------------- /.devcontainer/remote/.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: 2 | // https://github.com/microsoft/vscode-dev-containers/tree/v0.205.2/containers/python-3 3 | { 4 | "name": "beobench_devcontainer_remote", 5 | "build": { 6 | "dockerfile": "../../Dockerfile", 7 | "context": "../../..", 8 | "args": { 9 | // Update 'VARIANT' to pick a Python version: 3, 3.10, 3.9, 3.8, 3.7, 3.6 10 | // Append -bullseye or -buster to pin to an OS version. 11 | // Use -bullseye variants on local on arm64/Apple Silicon. 12 | "VARIANT": "3.9", 13 | // Options 14 | "NODE_VERSION": "none", 15 | "NVIDIA_SUPPORT": "True", 16 | } 17 | }, 18 | "workspaceFolder": "/beobench", 19 | // "workspaceMount": "source=remote-workspace,target=/workspace,type=volume", 20 | // ADAPT: the mount must be adapted to cloned repo location 21 | "workspaceMount": "source=/home/rdnfn-docker/main/repos/github/beobench/,target=/beobench/,type=bind,consistency=cached", 22 | // ADAPT: the mount must be adapted to the gitconfig location on the remote machine 23 | "mounts": [ 24 | "source=/home/rdnfn-docker/.gitconfig,target=/root/.gitconfig,type=bind,consistency=cached", 25 | ], 26 | "postCreateCommand": "pip install -e . && git config --system --add safe.directory /beobench", 27 | // Set *default* container specific settings.json values on container create. 28 | "settings": { 29 | "python.defaultInterpreterPath": "/usr/local/bin/python", 30 | "python.linting.enabled": true, 31 | "python.linting.pylintEnabled": true, 32 | "python.formatting.autopep8Path": "/usr/local/py-utils/bin/autopep8", 33 | "python.formatting.blackPath": "/usr/local/bin/black", 34 | "python.formatting.yapfPath": "/usr/local/py-utils/bin/yapf", 35 | "python.linting.banditPath": "/usr/local/py-utils/bin/bandit", 36 | "python.linting.flake8Path": "/usr/local/py-utils/bin/flake8", 37 | "python.linting.mypyPath": "/usr/local/py-utils/bin/mypy", 38 | "python.linting.pycodestylePath": "/usr/local/py-utils/bin/pycodestyle", 39 | "python.linting.pydocstylePath": "/usr/local/py-utils/bin/pydocstyle", 40 | "python.linting.pylintPath": "/usr/local/py-utils/bin/pylint", 41 | "files.trimTrailingWhitespace": true, 42 | "python.formatting.provider": "black", 43 | "editor.formatOnSave": true, 44 | "editor.rulers": [ 45 | 88 46 | ], 47 | "python.terminal.activateEnvInCurrentTerminal": true, 48 | "esbonio.server.enabled": true, 49 | "esbonio.sphinx.confDir": "${workspaceFolder}/docs", 50 | "restructuredtext.linter.doc8.extraArgs": [ 51 | "max-line-length=2000" 52 | ] 53 | }, 54 | // Add the IDs of extensions you want installed when the container is created. 55 | "extensions": [ 56 | "ms-python.python", 57 | "ms-python.vscode-pylance", 58 | "ms-azuretools.vscode-docker", 59 | "trond-snekvik.simple-rst", 60 | "lextudio.restructuredtext", 61 | "njpwerner.autodocstring" 62 | ], 63 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 64 | // "forwardPorts": [], 65 | // Use 'postCreateCommand' to run commands after the container is created. 66 | // "postCreateCommand": "pip3 install --user -r requirements.txt", 67 | // Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. 68 | // "remoteUser": "vscode", 69 | "features": { 70 | "git": "latest", 71 | "docker-in-docker": { 72 | "version": "latest", 73 | "moby": true 74 | } 75 | }, 76 | "runArgs": [ 77 | "--shm-size=32gb", 78 | "--gpus=all", 79 | ] 80 | } -------------------------------------------------------------------------------- /docs/guides/dev_env.rst: -------------------------------------------------------------------------------- 1 | .. highlight:: shell 2 | 3 | Setting up development environment 4 | ----------------------- 5 | 6 | Requirements 7 | ^^^^^^^^^^^^^^^^^^ 8 | 9 | Beobench uses `vscode devcontainers `_ for its development environment. A devcontainer allows all developers to work on (almost) identical systems, ensuring that not only python packages but also operating systems are the same. The installation of this development environment has the following pre-requisites: 10 | 11 | 1. `Docker `_ 12 | 2. `Visual Studio Code (vscode) `_ 13 | 3. `vscode remote extension pack `_ 14 | 15 | 16 | Standard development 17 | ^^^^^^^^^^^^^^^^^^^^ 18 | 19 | 1. Fork the Beobench repo on GitHub (if you are a maintainer you can skip this step). 20 | 2. Clone your fork locally using 21 | 22 | .. code-block:: 23 | 24 | git clone --recursive git@github.com:/beobench.git 25 | 26 | (if you are a maintainer you can clone directly from the main repository) 27 | 28 | Note that this requires having your github authentification setup, `see here `_. 29 | 30 | 3. Open the beobench repo folder in vscode 31 | 4. Inside vscode, open the command palette (e.g. on macOS shortcut is ``shift`` + ``cmd`` + ``P``), and use the ``Remote-containers: reopen in container`` command. 32 | 33 | This should have opened a new vscode window running in the docker dev container --- once the dev container is ready you're done! (Note: this gets faster after the first docker build) 34 | 35 | 36 | Remote development 37 | ^^^^^^^^^^^^^^^^^^ 38 | 39 | .. note:: 40 | Remote development is only useful if you have a separate server available to develop on. The standard development in the previous section will be more useful in most scenarios. 41 | 42 | 43 | It may be desireable to run your devcontainer not directly on your local machine (e.g. laptop) but instead on a remote machine (i.e. server). The local machine then just provides an interface to the remote machine. 44 | 45 | In order to set this up you need to follow these steps: 46 | 47 | 1. Follow all the instructions for local development above (apart from the final step 4). 48 | 2. Ensure that docker is installed on the remote machine. 49 | 3. Clone the repo to your *remote* machine. 50 | 4. In the cloned repo on your local machine, in ``.devcontainer/remote/.devcontainer/devcontainer.json`` replace the line 51 | 52 | .. code-block:: 53 | 54 | "workspaceMount": "source=/home/rdnfn-docker/main/repos/github/beobench/,target=/workspace,type=bind,consistency=cached" 55 | 56 | 57 | with 58 | 59 | .. code-block:: 60 | 61 | "workspaceMount": "source=,target=/workspace,type=bind,consistency=cached" 62 | 63 | where ``PATH_TO_CLONED_REPO`` is the path to your repo on the remote machine. Similarly, adapt the path in the ``"mounts"`` argument to the location of your ``.gitconfig`` file on the remote machine. 64 | 65 | 5. Create a docker context on your local machine that connects to docker on your remote machine (`See the instructions here `_). 66 | 6. Use the ``Remote-containers: open folder in container`` command and select the ``beobench/.devcontainer/remote`` folder in the pop-up window (beobench here is the main repo folder). 67 | 68 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: 2 | // https://github.com/microsoft/vscode-dev-containers/tree/v0.205.2/containers/python-3 3 | { 4 | "name": "beobench_devcontainer", 5 | "build": { 6 | "dockerfile": "Dockerfile", 7 | "context": "..", 8 | "args": { 9 | // Update 'VARIANT' to pick a Python version: 3, 3.10, 3.9, 3.8, 3.7, 3.6 10 | // Append -bullseye or -buster to pin to an OS version. 11 | // Use -bullseye variants on local on arm64/Apple Silicon. 12 | // Note: ray[rllib] is not available on debian 11 (bullseye) 13 | "VARIANT": "3.9-bullseye", 14 | // Options 15 | "NODE_VERSION": "none", 16 | "NVIDIA_SUPPORT": "none" 17 | } 18 | }, 19 | "workspaceFolder": "/beobench", 20 | // "workspaceMount": "source=remote-workspace,target=/workspace,type=volume", 21 | // ADAPT: the mount must be adapted to cloned repo location 22 | "workspaceMount": "source=${localWorkspaceFolder},target=/beobench,type=bind,consistency=cached", 23 | // ADAPT: the mount must be adapted to the gitconfig location on the remote machine 24 | //"mounts": [ 25 | // "source=${localEnv:HOME}/.gitconfig,target=/root/.gitconfig,type=bind,consistency=cached", 26 | //], 27 | //"initializeCommand": "export DOCKER_BUILDKIT=0 && export COMPOSE_DOCKER_CLI_BUILD=0", 28 | "postCreateCommand": "pip install -e . && git config --system --add safe.directory /beobench", 29 | // Set *default* container specific settings.json values on container create. 30 | "settings": { 31 | "python.defaultInterpreterPath": "/usr/local/bin/python", 32 | "python.linting.enabled": true, 33 | "python.linting.pylintEnabled": true, 34 | "python.formatting.autopep8Path": "/usr/local/py-utils/bin/autopep8", 35 | "python.formatting.blackPath": "/usr/local/bin/black", 36 | "python.formatting.yapfPath": "/usr/local/py-utils/bin/yapf", 37 | "python.linting.banditPath": "/usr/local/py-utils/bin/bandit", 38 | "python.linting.flake8Path": "/usr/local/py-utils/bin/flake8", 39 | "python.linting.mypyPath": "/usr/local/py-utils/bin/mypy", 40 | "python.linting.pycodestylePath": "/usr/local/py-utils/bin/pycodestyle", 41 | "python.linting.pydocstylePath": "/usr/local/py-utils/bin/pydocstyle", 42 | "python.linting.pylintPath": "/usr/local/py-utils/bin/pylint", 43 | "files.trimTrailingWhitespace": true, 44 | "python.formatting.provider": "black", 45 | "editor.formatOnSave": true, 46 | "editor.rulers": [ 47 | 88 48 | ], 49 | "python.terminal.activateEnvInCurrentTerminal": true, 50 | "esbonio.server.enabled": true, 51 | "esbonio.sphinx.confDir": "${workspaceFolder}/docs", 52 | "restructuredtext.linter.doc8.extraArgs": [ 53 | "max-line-length=2000" 54 | ] 55 | }, 56 | // Add the IDs of extensions you want installed when the container is created. 57 | "extensions": [ 58 | "ms-python.python", 59 | "ms-python.vscode-pylance", 60 | "ms-azuretools.vscode-docker", 61 | "trond-snekvik.simple-rst", 62 | "lextudio.restructuredtext", 63 | "njpwerner.autodocstring", 64 | "donjayamanne.githistory", 65 | ], 66 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 67 | // "forwardPorts": [], 68 | // Use 'postCreateCommand' to run commands after the container is created. 69 | // "postCreateCommand": "pip3 install --user -r requirements.txt", 70 | // Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. 71 | // "remoteUser": "vscode", 72 | "features": { 73 | "git": "latest", 74 | "docker-in-docker": "20.10" 75 | }, 76 | "runArgs": [ 77 | //"--shm-size=32gb", 78 | //"--gpus=all", 79 | ] 80 | } -------------------------------------------------------------------------------- /beobench/data/agents/energym_controller.py: -------------------------------------------------------------------------------- 1 | """Energym-provided rule-based controller.""" 2 | 3 | from beobench.experiment.provider import config, create_env 4 | import wandb 5 | import numpy as np 6 | 7 | import energym.examples.Controller # import LabController 8 | 9 | # Setting up experiment tracking via wandb 10 | wandb_used = config["general"]["wandb_project"] is not None 11 | if wandb_used: 12 | wandb.init( 13 | config=config, 14 | project=config["general"]["wandb_project"], 15 | entity=config["general"]["wandb_entity"], 16 | group=config["general"]["wandb_group"], 17 | name="energym_rbc_" + wandb.util.generate_id(), 18 | ) 19 | 20 | # Determining length of experiment (if set) 21 | try: 22 | num_timesteps = config["agent"]["config"]["stop"]["timesteps_total"] 23 | except KeyError: 24 | num_timesteps = 10 25 | 26 | # Determine max length of an episode 27 | try: 28 | horizon = config["agent"]["config"]["config"]["horizon"] 29 | except KeyError: 30 | horizon = 1000 31 | 32 | 33 | print("Energym rule-based controller agent: starting test.") 34 | 35 | env = create_env() 36 | 37 | inputs = env.env.get_inputs_names() 38 | controller = energym.examples.Controller.LabController( 39 | control_list=inputs, 40 | lower_tol=0.2, 41 | upper_tol=0.2, 42 | nighttime_setback=False, 43 | nighttime_start=18, 44 | nighttime_end=6, 45 | nighttime_temp=18, 46 | ) 47 | 48 | observation = env.reset() 49 | outputs = env.env.get_output() 50 | 51 | total_num_steps = 0 52 | num_steps_per_ep = 0 53 | episode = 0 54 | ep_rewards = [] 55 | hour = 0 56 | 57 | for _ in range(num_timesteps): 58 | episode += 1 59 | num_steps_per_ep += 1 60 | 61 | action = controller.get_control(outputs, 21, hour) 62 | action["Bd_Ch_EV1Bat_sp"] = [0.0] 63 | action["Bd_Ch_EV2Bat_sp"] = [0.0] 64 | 65 | # The steps below are based on the energym integration step() function 66 | # See https://github.com/rdnfn/beobench_contrib/blob/4c240bb/gyms/energym/energymGymEnv.py#L232 # pylint: disable=line-too-long 67 | 68 | # take step in energym environment 69 | outputs = env.env.step(action) 70 | # determine whether episode is finshed 71 | done = env.compute_done(outputs) 72 | # evaluate reward 73 | reward = env.compute_reward(outputs) 74 | ep_rewards.append(reward) 75 | 76 | if config["general"]["wandb_project"] and "WandbLogger" in str(env): 77 | # create info with original observations 78 | flattened_acts = { 79 | key: ( 80 | value[0] 81 | if not isinstance(value[0], (list, np.ndarray)) 82 | else value[0][0] 83 | ) 84 | for key, value in action.items() 85 | } 86 | info = {"obs": outputs, "acts": flattened_acts} 87 | 88 | total_num_steps += 1 89 | log_dict = { 90 | "env": { 91 | "action": None, 92 | "obs": None, 93 | "reward": reward, 94 | "done": done, 95 | "info": info, 96 | "step": total_num_steps, 97 | } 98 | } 99 | 100 | wandb.log(log_dict) 101 | 102 | if done or num_steps_per_ep >= horizon: 103 | 104 | if wandb_used: 105 | wandb.log({"episode_reward_mean": np.sum(ep_rewards), "step": episode}) 106 | 107 | num_steps_per_ep = 0 108 | ep_rewards = [] 109 | if done: 110 | observation = env.reset() 111 | env.close() 112 | 113 | print("Energym rule-based controller agent: completed test.") 114 | -------------------------------------------------------------------------------- /beobench/data/configs/default.yaml: -------------------------------------------------------------------------------- 1 | # Default Beobench configuration. 2 | # 3 | # Some of the descriptions of RLlib config 4 | # values are taken from 5 | # https://docs.ray.io/en/latest/rllib/rllib-training.html. 6 | # 7 | # Agent config 8 | agent: 9 | # Either path to agent script or name of built-in agent 10 | # (e.g. `rllib`) 11 | origin: rllib 12 | # Config used by agent. In this case given to 13 | # ray.tune.run() as arguments. 14 | # (since rllib set before) 15 | config: 16 | # RLlib algorithm name 17 | run_or_experiment: PPO 18 | # Stopping condition 19 | stop: 20 | timesteps_total: 400000 21 | # Config only used by ray.RLlib and not other ray modules. 22 | config: 23 | # Learning rate 24 | lr: 0.005 25 | # Neural network model for PPO 26 | model: 27 | fcnet_activation: relu 28 | fcnet_hiddens: [256,256,256,256] 29 | post_fcnet_activation: tanh 30 | batch_mode: complete_episodes 31 | # Discount factor 32 | gamma: 0.999 33 | # Number of steps after which the episode is forced to 34 | # terminate. Defaults to `env.spec.max_episode_steps` 35 | # (if present) for Gym envs. 36 | horizon: 1000 37 | # Calculate rewards but don't reset the environment when 38 | # the horizon is hit. This allows value estimation and 39 | # RNN state to span across logical episodes denoted by 40 | # horizon. This only has an effect if horizon != inf. 41 | soft_horizon: True 42 | # Number of episodes over which RLlib internal metrics 43 | # are smoothed 44 | metrics_num_episodes_for_smoothing: 5 45 | # Deep learning framework 46 | framework: torch 47 | # Number of parallel workers interacting with 48 | # environment for most gym frameworks this has to be 49 | # set to 1 as often the background building simulations 50 | # are not setup for parallel usage 51 | # (can fail silently otherwise). 52 | num_workers: 1 53 | # Environment config 54 | env: 55 | # Gym framework of environment 56 | gym: energym 57 | # Configuration passed to integration's `create_env()` 58 | # function. Specific to whatever gym framework you use 59 | # (in this case Energym). 60 | config: null 61 | # Wrappers (None added by default) 62 | wrappers: [] 63 | # General Beobench config 64 | general: 65 | # Directory to write experiment files to. This argument 66 | # is equivalent to the `local_dir` argument in 67 | # `tune.run()`. 68 | local_dir: ./beobench_results 69 | # Name of wandb project. 70 | wandb_project: null 71 | # Name of wandb entity. 72 | wandb_entity: null 73 | # Name of wandb run group. 74 | wandb_group: null 75 | # Wandb API key 76 | wandb_api_key: null 77 | # Name of MLflow experiment 78 | mlflow_name: null 79 | # Whether to use GPU from the host system. Requires that 80 | # GPU is available. 81 | use_gpu: False 82 | # Size of the shared memory available to the experiment 83 | # container. 84 | docker_shm_size: 4gb 85 | # Whether to force a re-build, even if image already 86 | # exists. 87 | force_build: False 88 | # Whether to use cache IF building experiment container. 89 | # This will not do anything if force_build is disabled, 90 | # and image already exists. 91 | use_no_cache: False 92 | # File or github path to beobench package. For 93 | # developement purpose only. This will install a custom 94 | # beobench version inside the experiment container. 95 | # By default the latest PyPI version is installed. 96 | dev_path: null 97 | # List of docker flags to be added to docker run command 98 | # of Beobench experiment container. 99 | docker_flags: null 100 | # Extra dependencies to install with beobench. Used 101 | # during pip installation in experiment image, 102 | # as in using the command: 103 | # `pip install beobench[]` 104 | beobench_extras: "extended" 105 | # Number of experiment samples to run. This defaults 106 | # to a single sample, i.e. just running the 107 | # experiment once. 108 | num_samples: 1 109 | # Beobench version 110 | # version: 0.5.4 111 | -------------------------------------------------------------------------------- /beobench/cli.py: -------------------------------------------------------------------------------- 1 | """Command line interface for beobench.""" 2 | 3 | import click 4 | 5 | import beobench.experiment.scheduler 6 | import beobench.utils 7 | 8 | 9 | @click.group() 10 | def cli(): 11 | """Command line interface for Beobench.""" 12 | 13 | 14 | @cli.command() 15 | @click.option( 16 | "--config", 17 | "-c", 18 | default=None, 19 | help="Json or filepath with yaml that defines beobench experiment configuration.", 20 | type=str, 21 | multiple=True, 22 | ) 23 | @click.option( 24 | "--method", 25 | default=None, 26 | help="Name of RL method to use in experiment.", 27 | ) 28 | @click.option( 29 | "--gym", 30 | default=None, 31 | help="Name of gym framework to use in experiment.", 32 | ) 33 | @click.option( 34 | "--env", 35 | default=None, 36 | help="Name of RL environment to use in experiment.", 37 | ) 38 | @click.option( 39 | "--local-dir", 40 | default=None, 41 | help="Local directory to write results to.", 42 | type=click.Path(exists=False, file_okay=False, dir_okay=True), 43 | ) 44 | @click.option( 45 | "--wandb-project", 46 | default=None, 47 | help="Weights and biases project name to log runs to.", 48 | ) 49 | @click.option( 50 | "--wandb-entity", 51 | default=None, 52 | help="Weights and biases entity name to log runs under.", 53 | ) 54 | @click.option( 55 | "--wandb-api-key", 56 | default=None, 57 | help="Weights and biases API key.", 58 | ) 59 | @click.option( 60 | "--mlflow-name", 61 | default=None, 62 | help="Name of MLflow experiment.", 63 | ) 64 | @click.option( 65 | "--use-gpu", 66 | is_flag=True, 67 | help="Whether to use GPUs from the host system in experiment container.", 68 | ) 69 | @click.option( 70 | "--docker-shm-size", 71 | default=None, 72 | help="Size of shared memory available to experiment container.", 73 | ) 74 | @click.option( 75 | "--no-additional-container", 76 | is_flag=True, 77 | help="Do not run another container to do experiments in.", 78 | ) 79 | @click.option( 80 | "--use-no-cache", 81 | is_flag=True, 82 | help="Whether to use cache to build experiment container.", 83 | ) 84 | @click.option( 85 | "--dev-path", 86 | "-d", 87 | default=None, 88 | help="For developer use only: location of custom beobench package version.", 89 | ) 90 | @click.option( 91 | "--force-build", 92 | is_flag=True, 93 | help="Whether to force a re-build, even if image already exists.", 94 | ) 95 | @click.option( 96 | "--dry-run", 97 | is_flag=True, 98 | help=( 99 | "Whether to dry run the experiment without" 100 | " running docker commands/saving files." 101 | ), 102 | ) 103 | def run( 104 | config: str, 105 | method: str, 106 | gym: str, 107 | env: str, 108 | local_dir: str, 109 | wandb_project: str, 110 | wandb_entity: str, 111 | wandb_api_key: str, 112 | mlflow_name: str, 113 | use_gpu: bool, 114 | docker_shm_size: str, 115 | no_additional_container: bool, 116 | use_no_cache: bool, 117 | dev_path: str, 118 | force_build: bool, 119 | dry_run: bool, 120 | ) -> None: 121 | """Run beobench experiment from command line. 122 | 123 | Command line version of beobench.run() function. 124 | """ 125 | 126 | # This appears to be the best (but not great) way to have a parallel python 127 | # and command line interface. 128 | # 129 | # See https://stackoverflow.com/a/40094408. 130 | 131 | beobench.experiment.scheduler.run( 132 | config=list(config), 133 | method=method, 134 | gym=gym, 135 | env=env, 136 | local_dir=local_dir, 137 | wandb_project=wandb_project, 138 | wandb_entity=wandb_entity, 139 | wandb_api_key=wandb_api_key, 140 | mlflow_name=mlflow_name, 141 | use_gpu=use_gpu, 142 | docker_shm_size=docker_shm_size, 143 | no_additional_container=no_additional_container, 144 | use_no_cache=use_no_cache, 145 | dev_path=dev_path, 146 | force_build=force_build, 147 | dry_run=dry_run, 148 | ) 149 | 150 | 151 | @cli.command() 152 | def restart(): 153 | """Restart beobench. This will stop any remaining running beobench containers.""" 154 | beobench.utils.restart() 155 | -------------------------------------------------------------------------------- /beobench/utils.py: -------------------------------------------------------------------------------- 1 | """Module with a number of utility functions.""" 2 | 3 | import docker 4 | import subprocess 5 | 6 | import beobench.logging 7 | from beobench.logging import logger 8 | 9 | 10 | def check_if_in_notebook() -> bool: 11 | """Check if code is executed from jupyter notebook. 12 | 13 | Taken from https://stackoverflow.com/a/44100805. 14 | 15 | Returns: 16 | bool: whether code is run from notebook. 17 | """ 18 | try: 19 | # This function should only be available in ipython kernel. 20 | get_ipython() # pylint: disable=undefined-variable 21 | return True 22 | except: # pylint: disable=bare-except 23 | return False 24 | 25 | 26 | def merge_dicts( 27 | a: dict, 28 | b: dict, 29 | path: list = None, 30 | mutate_a: bool = False, 31 | let_b_overrule_a=False, 32 | ) -> dict: 33 | """Merge dictionary b into dictionary a. 34 | 35 | Adapted from https://stackoverflow.com/a/7205107. 36 | 37 | Args: 38 | a (dict): a dicitonary 39 | b (dict): another dictionary 40 | path (list, optional): where the dict is in the original dict. 41 | Necessary for recursion, no need to use. Defaults to None. 42 | mutate_a (bool, optional): whether to mutate the dictionary a that 43 | is given. Necessary for recursion, no need to use. 44 | Defaults to False. 45 | let_b_overrule_a: whether to allow dict b to overrule if they disagree on a 46 | key value. Defaults to False. 47 | 48 | 49 | Raises: 50 | Exception: When dictionaries are inconsistent, and not let_b_overrule_a. 51 | 52 | Returns: 53 | dictionary: merged dictionary. 54 | """ 55 | # pylint: disable=consider-using-f-string 56 | 57 | # Ensure that dict a is not mutated 58 | if not mutate_a: 59 | a = dict(a) 60 | 61 | if path is None: 62 | path = [] 63 | for key in b: 64 | if key in a: 65 | if isinstance(a[key], dict) and isinstance(b[key], dict): 66 | merge_dicts( 67 | a[key], 68 | b[key], 69 | path + [str(key)], 70 | mutate_a=True, 71 | let_b_overrule_a=let_b_overrule_a, 72 | ) 73 | elif a[key] == b[key]: 74 | pass # same leaf value 75 | else: 76 | if not let_b_overrule_a: 77 | location = ".".join(path + [str(key)]) 78 | raise Exception( 79 | ( 80 | f"Conflict at {location}." 81 | f"a={a[key]} is not the same as b={b[key]}." 82 | ) 83 | ) 84 | else: 85 | a[key] = b[key] 86 | else: 87 | a[key] = b[key] 88 | return a 89 | 90 | 91 | def shutdown() -> None: 92 | """Shut down all beobench and BOPTEST containers.""" 93 | 94 | beobench.logging.setup() 95 | 96 | logger.info("Stopping any remaining beobench and BOPTEST docker containers...") 97 | 98 | client = docker.from_env() 99 | container_num = 0 100 | for container in client.containers.list(): 101 | if "auto_beobench" in container.name or "auto_boptest" in container.name: 102 | logger.info(f"Stopping container {container.name}") 103 | container.stop(timeout=0) 104 | container_num += 1 105 | 106 | logger.info(f"Stopped {container_num} container(s).") 107 | 108 | 109 | def restart() -> None: 110 | """Clean up remaining beobench processes and containers 111 | before running new experiments. 112 | 113 | This stops all docker containers still running. This 114 | function is not called by other scheduler functions 115 | to enable the parallel running of experiments. 116 | """ 117 | 118 | shutdown() 119 | 120 | 121 | def run_command(cmd_line_args, process_name): 122 | """Run command and log its output.""" 123 | 124 | process = subprocess.Popen( # pylint: disable=consider-using-with 125 | cmd_line_args, 126 | stdout=subprocess.PIPE, 127 | stderr=subprocess.STDOUT, 128 | ) 129 | with process.stdout: 130 | beobench.logging.log_subprocess( 131 | process.stdout, 132 | process_name=process_name, 133 | ) 134 | retcode = process.wait() # 0 means success 135 | if retcode: 136 | raise subprocess.CalledProcessError(retcode, cmd=cmd_line_args) 137 | -------------------------------------------------------------------------------- /tests/performance/test_energym.py: -------------------------------------------------------------------------------- 1 | """Tests to evaluate the performance of Beobench energym environments.""" 2 | 3 | import timeit 4 | from datetime import datetime 5 | 6 | 7 | def test_performance_on_fixed_actions( 8 | use_beobench: bool = False, 9 | config: dict = None, 10 | num_steps: int = 100, 11 | beobench_normalize: bool = False, 12 | beobench_use_native_env: bool = False, 13 | ): 14 | 15 | if config is None: 16 | config = { 17 | "env_name": "MixedUseFanFCU-v0", 18 | "weather": "GRC_A_Athens", 19 | "simulation_days": 300, 20 | } 21 | 22 | action = { 23 | "Bd_Fl_AHU1_sp": [1], 24 | "Bd_Fl_AHU2_sp": [1], 25 | "Bd_T_AHU1_sp": [21], 26 | "Bd_T_AHU2_sp": [21], 27 | "Z02_T_Thermostat_sp": [21], 28 | "Z03_T_Thermostat_sp": [21], 29 | "Z04_T_Thermostat_sp": [21], 30 | "Z05_T_Thermostat_sp": [21], 31 | "Z08_T_Thermostat_sp": [21], 32 | "Z09_T_Thermostat_sp": [21], 33 | "Z10_T_Thermostat_sp": [21], 34 | "Z11_T_Thermostat_sp": [21], 35 | } 36 | 37 | if not use_beobench: 38 | import energym # pylint: disable=import-outside-toplevel 39 | 40 | env = energym.make( 41 | config["env_name"], 42 | weather=config["weather"], 43 | simulation_days=config["simulation_days"], 44 | ) 45 | 46 | def take_steps(): 47 | for _ in range(num_steps): 48 | env.step(action) 49 | 50 | step_time = timeit.timeit(take_steps, number=1) 51 | print("Performance test, step time (native): ", step_time) 52 | with open( 53 | "beobench_results/perf_test_results.txt", "a", encoding="utf-8" 54 | ) as text_file: 55 | text_file.write( 56 | ( 57 | f" Performance, time for taking {num_steps}" 58 | " in env: {step_time} seconds\n" 59 | ) 60 | ) 61 | 62 | else: 63 | import beobench # pylint: disable=import-outside-toplevel 64 | 65 | if not beobench_use_native_env: 66 | # if beobench_normalize: 67 | action = {key: value[0] for key, value in action.items()} 68 | 69 | beobench_config = { 70 | "agent": { 71 | "origin": "../tests/performance/energym_fixed_action.py", 72 | "config": { 73 | "action": action, 74 | "num_steps": num_steps, 75 | "use_native_env": beobench_use_native_env, 76 | }, 77 | }, 78 | "env": { 79 | "gym": "energym", 80 | "config": { 81 | "energym_environment": config["env_name"], 82 | "weather": config["weather"], 83 | "days": config["simulation_days"], 84 | "gym_kwargs": { 85 | "normalize": beobench_normalize, 86 | }, 87 | }, 88 | }, 89 | "general": { 90 | "dev_path": "../", 91 | # "use_no_cache": True, 92 | }, 93 | } 94 | beobench.run(config=beobench_config) 95 | 96 | 97 | def main(): 98 | """Main test function.""" 99 | # pylint: disable=cell-var-from-loop 100 | 101 | NUM_STEPS = 10000 # pylint: disable=invalid-name 102 | for use_beobench, beobench_normalize, beobench_use_native_env in [ 103 | (True, False, False), 104 | (True, True, False), 105 | (True, False, True), 106 | (False, None, None), 107 | ]: 108 | with open( 109 | "beobench_results/perf_test_results.txt", "a", encoding="utf-8" 110 | ) as text_file: 111 | text_file.write(str(datetime.now()) + "\n") 112 | text_file.write("New test - Configuration:\n") 113 | text_file.write(f" use_beobench: {use_beobench}\n") 114 | text_file.write(f" beobench_normalize: {beobench_normalize}\n") 115 | text_file.write(f" beobench_use_native_env: {beobench_use_native_env}\n") 116 | text_file.write(f" num_steps: {NUM_STEPS}\n\n") 117 | 118 | func_time = timeit.timeit( 119 | lambda: test_performance_on_fixed_actions( 120 | use_beobench=use_beobench, 121 | config=None, 122 | num_steps=NUM_STEPS, 123 | beobench_normalize=beobench_normalize, 124 | beobench_use_native_env=beobench_use_native_env, 125 | ), 126 | number=1, 127 | ) 128 | with open( 129 | "beobench_results/perf_test_results.txt", "a", encoding="utf-8" 130 | ) as text_file: 131 | text_file.write(f" Performance, overall time: {func_time} seconds\n\n") 132 | 133 | 134 | if __name__ == "__main__": 135 | main() 136 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | .. highlight:: shell 2 | 3 | ============ 4 | Contributing 5 | ============ 6 | 7 | Contributions are welcome, and they are greatly appreciated! Every little bit 8 | helps, and credit will always be given. 9 | 10 | You can contribute in many ways: 11 | 12 | Types of Contributions 13 | ---------------------- 14 | 15 | Report Bugs 16 | ~~~~~~~~~~~ 17 | 18 | Report bugs at https://github.com/rdnfn/beobench/issues. 19 | 20 | If you are reporting a bug, please include: 21 | 22 | * Your operating system name and version. 23 | * Any details about your local setup that might be helpful in troubleshooting. 24 | * Detailed steps to reproduce the bug. 25 | 26 | Fix Bugs 27 | ~~~~~~~~ 28 | 29 | Look through the GitHub issues for bugs. Anything tagged with "bug" and "help 30 | wanted" is open to whoever wants to implement it. 31 | 32 | Implement Features 33 | ~~~~~~~~~~~~~~~~~~ 34 | 35 | Look through the GitHub issues for features. Anything tagged with "enhancement" 36 | and "help wanted" is open to whoever wants to implement it. 37 | 38 | Write Documentation 39 | ~~~~~~~~~~~~~~~~~~~ 40 | 41 | beobench could always use more documentation, whether as part of the 42 | official beobench docs, in docstrings, or even on the web in blog posts, 43 | articles, and such. 44 | 45 | To update the API docs, use the following command inside the ``/docs`` directory: 46 | 47 | .. code-block:: 48 | 49 | sphinx-apidoc -f -o . .. 50 | 51 | 52 | Submit Feedback 53 | ~~~~~~~~~~~~~~~ 54 | 55 | The best way to send feedback is to file an issue at https://github.com/rdnfn/beobench/issues. 56 | 57 | If you are proposing a feature: 58 | 59 | * Explain in detail how it would work. 60 | * Keep the scope as narrow as possible, to make it easier to implement. 61 | * Remember that this is a volunteer-driven project, and that contributions 62 | are welcome :) 63 | 64 | Get Started! 65 | ------------ 66 | 67 | Ready to contribute? Here's how to set up `beobench` for local development. 68 | 69 | 1. Follow :doc:`this guide ` to fork the repo and setup the development environment. 70 | 71 | 2. Inside the devcontainer just set up, create a branch for local development:: 72 | 73 | $ git checkout -b dev/name-of-your-bugfix-or-feature 74 | 75 | Now you can make your changes locally. 76 | 77 | 3. When you're done making changes, check that your changes pass flake8 and the 78 | tests, including testing other Python versions with tox:: 79 | 80 | $ flake8 beobench tests 81 | $ python setup.py test 82 | $ tox 83 | 84 | 4. Commit your changes and push your branch to GitHub:: 85 | 86 | $ git add . 87 | $ git commit -m "Your detailed description of your changes." 88 | $ git push origin name-of-your-bugfix-or-feature 89 | 90 | 5. Submit a pull request through the GitHub website. 91 | 92 | Guidelines 93 | ---------- 94 | 95 | Commit messages 96 | ~~~~~~~~~~~~~~~ 97 | 98 | When committing to the Beobench repo, please try to follow `this style 99 | guide by Udacity `_ for the 100 | commit messages with the following adaptions: 101 | 102 | 1. Replace the ``chore:`` type with ``aux:``. 103 | 2. Use a ``exp:`` type for commits relating to experiment data (e.g. experiment config files). 104 | 105 | 106 | Pull Requests 107 | ~~~~~~~~~~~~~ 108 | 109 | Before you submit a pull request, check that it meets these guidelines: 110 | 111 | 1. The pull request should include tests. 112 | 2. If the pull request adds functionality, the docs should be updated. Put 113 | your new functionality into a function with a docstring, and add the 114 | feature to the list in README.rst. 115 | 116 | 117 | .. 3. The pull request should work for Python 3.6, 3.7, 3.8 and 3.9. 118 | 119 | .. Check https://travis-ci.com/rdnfn/beobench/pull_requests 120 | and make sure that the tests pass for all supported Python versions. 121 | 122 | Tips 123 | ---- 124 | 125 | To run a subset of tests:: 126 | 127 | 128 | $ python -m unittest tests.test_beobench 129 | 130 | 131 | Resources 132 | --------- 133 | 134 | Documentation and cheatsheets for reStructuredText (``.rst`` files): 135 | 136 | * https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html 137 | * https://bashtage.github.io/sphinx-material/rst-cheatsheet/rst-cheatsheet.html 138 | 139 | Deploying 140 | --------- 141 | 142 | A reminder for the maintainers on how to deploy. Follow this checklist (inspired by `this checklist `_ and `this packaging tutorial `_): 143 | 144 | 1. Update ``HISTORY.rst`` and commit with message like "aux: add changelog for upcoming release 0.1.0" 145 | 2. Run 146 | 147 | .. code-block:: console 148 | 149 | bump2version patch # possible: major / minor / patch 150 | 151 | 3. Push commits *and tags* (`see here how to do this in vscode `_) 152 | 4. Merge pull request into ``main`` branch. 153 | 5. Add release on GitHub (using existing tag) -------------------------------------------------------------------------------- /beobench/integration/rllib.py: -------------------------------------------------------------------------------- 1 | """RLlib integration in beobench.""" 2 | 3 | import json 4 | import ray.tune 5 | import ray.tune.integration.wandb 6 | import ray.tune.integration.mlflow 7 | 8 | import beobench.utils 9 | import beobench.experiment.config_parser 10 | import beobench.integration.wandb 11 | from beobench.constants import RAY_LOCAL_DIR_IN_CONTAINER 12 | 13 | 14 | def run_in_tune( 15 | config: dict, 16 | ) -> ray.tune.ExperimentAnalysis: 17 | """Run beobench experiment. 18 | 19 | Additional info: note that RLlib is a submodule of the ray package, i.e. it is 20 | imported as `ray.rllib`. For experiment definitions it uses the `ray.tune` 21 | submodule. Therefore ray tune experiment definition means the same as ray rllib 22 | experiment defintions. To avoid confusion all variable/argument names use rllib 23 | instead of ray tune but strictly speaking these are ray tune experiment 24 | definitions. 25 | 26 | Args: 27 | config (dict): beobench config 28 | wandb_project (str, optional): name of wandb project. Defaults to None. 29 | wandb_entity (str, optional): name of wandb entirty. Defaults to None. 30 | mlflow_name (str, optional): name of mlflow experiment. Defaults to None. 31 | use_gpu (bool, optional): whether to use GPU. Defaults to False. 32 | 33 | Raises: 34 | ValueError: raised if only one of wandb project or wandb entity given. 35 | 36 | Returns: 37 | ray.tune.ExperimentAnalysis: analysis object from experiment. 38 | """ 39 | 40 | if config["general"]["wandb_project"] and config["general"]["wandb_entity"]: 41 | 42 | callbacks = [ 43 | ray.tune.integration.wandb.WandbLoggerCallback( 44 | project=config["general"]["wandb_project"], 45 | entity=config["general"]["wandb_entity"], 46 | group=config["general"]["wandb_group"], 47 | id=config["autogen"]["run_id"], 48 | config={"beobench": config}, 49 | ) 50 | ] 51 | elif config["general"]["wandb_project"] or config["general"]["wandb_entity"]: 52 | raise ValueError( 53 | "Only one of wandb_project or wandb_entity given, but both required." 54 | ) 55 | elif config["general"]["mlflow_name"]: 56 | callbacks = [_create_mlflow_callback(config["general"]["mlflow_name"])] 57 | else: 58 | callbacks = [] 59 | 60 | # combine the three incomplete ray tune experiment 61 | # definitions into a single complete one. 62 | rllib_config = beobench.experiment.config_parser.create_rllib_config(config) 63 | 64 | # change RLlib setup if GPU used 65 | if config["general"]["use_gpu"]: 66 | rllib_config["config"]["num_gpus"] = 1 67 | 68 | # register the problem environment with ray tune 69 | # provider is a module available in experiment containers 70 | # pylint: disable=import-outside-toplevel,import-error 71 | from beobench.experiment.provider import create_env 72 | 73 | ray.tune.registry.register_env( 74 | rllib_config["config"]["env"], 75 | create_env, 76 | ) 77 | 78 | # if run in notebook, change the output reported throughout experiment. 79 | if beobench.utils.check_if_in_notebook(): 80 | reporter = ray.tune.JupyterNotebookReporter(overwrite=True) 81 | else: 82 | reporter = None 83 | 84 | # running the experiment 85 | analysis = ray.tune.run( 86 | progress_reporter=reporter, 87 | callbacks=callbacks, 88 | local_dir=RAY_LOCAL_DIR_IN_CONTAINER, 89 | **rllib_config, 90 | ) 91 | 92 | return analysis 93 | 94 | 95 | def _create_mlflow_callback( 96 | mlflow_name: str, 97 | ): 98 | """Create an RLlib MLflow callback. 99 | 100 | Args: 101 | mlflow_name (str, optional): name of MLflow experiment. 102 | 103 | Returns: 104 | : a wandb callback 105 | """ 106 | mlflow_callback = ray.tune.integration.mlflow.MLflowLoggerCallback( 107 | experiment_name=mlflow_name, tracking_uri="file:/root/ray_results/mlflow" 108 | ) 109 | return mlflow_callback 110 | 111 | 112 | def get_cross_episodes_data(path: str) -> dict: 113 | """Get concatenated episode data from RLlib output. 114 | 115 | This currently only concatenates data from info variable. 116 | It further assumes that each info returned by step() in the env is 117 | a dict of dicts. 118 | 119 | Args: 120 | path (str): path to RLlib output json file. 121 | 122 | Returns: 123 | dict: dictionary with episode data 124 | """ 125 | # TODO: add non-info data to this logging procedure 126 | 127 | # Open RLlib output 128 | outputs = [] 129 | with open(path, encoding="UTF-8") as json_file: 130 | for line in json_file.readlines(): 131 | outputs.append(json.loads(line)) 132 | 133 | # Get all keys of observations saved in info dict 134 | all_obs_keys = [] 135 | for info_key in outputs[0]["infos"][0].keys(): 136 | all_obs_keys += list(outputs[0]["infos"][0][info_key].keys()) 137 | 138 | # Create empty (flat) dict of obs saved in info dict 139 | infos = [] 140 | 141 | # Add data to dict, one step at a time 142 | for output in outputs[:]: 143 | for info in output["infos"]: 144 | infos.append(info) 145 | 146 | return infos 147 | -------------------------------------------------------------------------------- /notebooks/utils/nb001_create_envs_table.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "%reload_ext autoreload\n", 10 | "%autoreload 2\n", 11 | "%config IPCompleter.greedy=True\n", 12 | "%config IPCompleter.use_jedi=False" 13 | ] 14 | }, 15 | { 16 | "cell_type": "code", 17 | "execution_count": 15, 18 | "metadata": {}, 19 | "outputs": [], 20 | "source": [ 21 | "import pandas as pd\n", 22 | "import numpy as np\n", 23 | "\n", 24 | "# Load data from google sheet\n", 25 | "sheet_id = \"1QaXBrhDY8tyF9cIBjejDTGMiv5vDUQG0KQ7iVapGQoY\"\n", 26 | "gid = 1987847080\n", 27 | "link = f\"https://docs.google.com/feeds/download/spreadsheets/Export?key={sheet_id}&exportFormat=csv&gid={gid}\"\n", 28 | "envs = pd.read_csv(link)\n", 29 | "\n", 30 | "# Preprocessing\n", 31 | "\n", 32 | "# Add house type icons\n", 33 | "def emojify(row): \n", 34 | " house_type = row[\"House type\"]\n", 35 | " if house_type == \"residential\":\n", 36 | " return \"|home|\"\n", 37 | " elif house_type == \"office\":\n", 38 | " return \"|office|\"\n", 39 | " elif house_type == \"industrial\":\n", 40 | " return \"|industry|\"\n", 41 | " return \"other\"\n", 42 | "\n", 43 | "def new_type(row): \n", 44 | " house_type = row[\"House type\"]\n", 45 | " if house_type == \"residential\":\n", 46 | " return \"residential building\"\n", 47 | " elif house_type == \"office\":\n", 48 | " return \"office building\"\n", 49 | " elif house_type == \"industrial\":\n", 50 | " return \"data center\"\n", 51 | " return \"data center\"\n", 52 | "\n", 53 | "envs['Type'] = envs.apply(lambda row: emojify(row), axis=1)\n", 54 | "envs['new_type'] = envs.apply(lambda row: new_type(row), axis=1)\n", 55 | "\n", 56 | "# Make sure each env has gym\n", 57 | "envs['Gym'] = envs['Gym'].replace('', np.NaN)\n", 58 | "envs['Gym'] = envs['Gym'].fillna(method='ffill',axis=0)\n", 59 | "\n", 60 | "\n", 61 | "\n", 62 | "\n" 63 | ] 64 | }, 65 | { 66 | "cell_type": "code", 67 | "execution_count": 16, 68 | "metadata": {}, 69 | "outputs": [], 70 | "source": [ 71 | "# Create main environment list\n", 72 | "\n", 73 | "def create_main_list(filepath=\"../../docs/envs/envs_list.rst\"):\n", 74 | "\n", 75 | " rst_out = \"\"\n", 76 | "\n", 77 | " for i, gym in enumerate(envs['Gym'].unique()):\n", 78 | " gym_envs = envs[envs['Gym']==gym]\n", 79 | "\n", 80 | " rst_out += f\":{gym}:\\n\"\n", 81 | "\n", 82 | " for index, row in gym_envs.iterrows():\n", 83 | " env = row[\"Environment\"]\n", 84 | " symbol = row[\"Type\"]\n", 85 | " #rst_out += f\" - `{env}`_ {symbol}\\n\"\n", 86 | " rst_out += f\" - `{env}`_\\n\"\n", 87 | "\n", 88 | " # Add hline between gyms\n", 89 | " if i < len(envs['Gym'].unique()) - 1:\n", 90 | " rst_out += \"\\n----\\n\\n\"\n", 91 | "\n", 92 | " # Add image links\n", 93 | " rst_out += \"\"\"\n", 94 | " .. |office| image:: https://raw.githubusercontent.com/tabler/tabler-icons/master/icons/building-skyscraper.svg\n", 95 | " .. |home| image:: https://raw.githubusercontent.com/tabler/tabler-icons/master/icons/home.svg\n", 96 | " .. |industry| image:: https://raw.githubusercontent.com/tabler/tabler-icons/master/icons/building-factory.svg\n", 97 | " \"\"\"\n", 98 | "\n", 99 | " with open(filepath, 'w') as file:\n", 100 | " file.write(rst_out)\n", 101 | "\n", 102 | "\n", 103 | "create_main_list(filepath=\"../../docs/envs/envs_list.rst\")" 104 | ] 105 | }, 106 | { 107 | "cell_type": "code", 108 | "execution_count": 17, 109 | "metadata": {}, 110 | "outputs": [], 111 | "source": [ 112 | "def create_env_descriptions():\n", 113 | " for _, gym in enumerate(envs['Gym'].unique()):\n", 114 | " env_descr_file = f\"../../docs/envs/{gym}_descriptions.rst\"\n", 115 | " rst_out = \"\"\n", 116 | " gym_envs = envs[envs['Gym']==gym]\n", 117 | "\n", 118 | " for _, row in gym_envs.iterrows():\n", 119 | " env = row[\"Environment\"]\n", 120 | " rst_out += f\"\\n\\n.. _env-{env}: \\n\\n\"\n", 121 | " rst_out += f\"``{env}``\\n\"\n", 122 | " rst_out += '\"' * (len(env) + 4) + \"\\n\\n\"\n", 123 | " #rst_out += f\":Type: {row['new_type']} ({row['Type']})\\n\"\n", 124 | " rst_out += f\":Type: {row['new_type']}\\n\"\n", 125 | " rst_out += f\":More info: `framework docs <{row['Original docs']}>`_\\n\"\n", 126 | "\n", 127 | " with open(env_descr_file, 'w') as file:\n", 128 | " file.write(rst_out)\n", 129 | "\n", 130 | "create_env_descriptions()\n", 131 | " " 132 | ] 133 | }, 134 | { 135 | "cell_type": "code", 136 | "execution_count": null, 137 | "metadata": {}, 138 | "outputs": [], 139 | "source": [] 140 | } 141 | ], 142 | "metadata": { 143 | "interpreter": { 144 | "hash": "949777d72b0d2535278d3dc13498b2535136f6dfe0678499012e853ee9abcab1" 145 | }, 146 | "kernelspec": { 147 | "display_name": "Python 3.9.7 64-bit", 148 | "language": "python", 149 | "name": "python3" 150 | }, 151 | "language_info": { 152 | "codemirror_mode": { 153 | "name": "ipython", 154 | "version": 3 155 | }, 156 | "file_extension": ".py", 157 | "mimetype": "text/x-python", 158 | "name": "python", 159 | "nbconvert_exporter": "python", 160 | "pygments_lexer": "ipython3", 161 | "version": "3.9.7" 162 | }, 163 | "orig_nbformat": 4 164 | }, 165 | "nbformat": 4, 166 | "nbformat_minor": 2 167 | } 168 | -------------------------------------------------------------------------------- /docs/envs/Sinergym_descriptions.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | .. _env-Eplus-demo-v1: 4 | 5 | ``Eplus-demo-v1`` 6 | """"""""""""""""" 7 | 8 | :Type: residential building 9 | :More info: `framework docs `_ 10 | 11 | 12 | .. _env-Eplus-5Zone-hot-discrete-v1: 13 | 14 | ``Eplus-5Zone-hot-discrete-v1`` 15 | """"""""""""""""""""""""""""""" 16 | 17 | :Type: residential building 18 | :More info: `framework docs `_ 19 | 20 | 21 | .. _env-Eplus-5Zone-mixed-discrete-v1: 22 | 23 | ``Eplus-5Zone-mixed-discrete-v1`` 24 | """"""""""""""""""""""""""""""""" 25 | 26 | :Type: residential building 27 | :More info: `framework docs `_ 28 | 29 | 30 | .. _env-Eplus-5Zone-cool-discrete-v1: 31 | 32 | ``Eplus-5Zone-cool-discrete-v1`` 33 | """""""""""""""""""""""""""""""" 34 | 35 | :Type: residential building 36 | :More info: `framework docs `_ 37 | 38 | 39 | .. _env-Eplus-5Zone-hot-continuous-v1: 40 | 41 | ``Eplus-5Zone-hot-continuous-v1`` 42 | """"""""""""""""""""""""""""""""" 43 | 44 | :Type: residential building 45 | :More info: `framework docs `_ 46 | 47 | 48 | .. _env-Eplus-5Zone-mixed-continuous-v1: 49 | 50 | ``Eplus-5Zone-mixed-continuous-v1`` 51 | """"""""""""""""""""""""""""""""""" 52 | 53 | :Type: residential building 54 | :More info: `framework docs `_ 55 | 56 | 57 | .. _env-Eplus-5Zone-cool-continuous-v1: 58 | 59 | ``Eplus-5Zone-cool-continuous-v1`` 60 | """""""""""""""""""""""""""""""""" 61 | 62 | :Type: residential building 63 | :More info: `framework docs `_ 64 | 65 | 66 | .. _env-Eplus-5Zone-hot-discrete-stochastic-v1: 67 | 68 | ``Eplus-5Zone-hot-discrete-stochastic-v1`` 69 | """""""""""""""""""""""""""""""""""""""""" 70 | 71 | :Type: residential building 72 | :More info: `framework docs `_ 73 | 74 | 75 | .. _env-Eplus-5Zone-mixed-discrete-stochastic-v1: 76 | 77 | ``Eplus-5Zone-mixed-discrete-stochastic-v1`` 78 | """""""""""""""""""""""""""""""""""""""""""" 79 | 80 | :Type: residential building 81 | :More info: `framework docs `_ 82 | 83 | 84 | .. _env-Eplus-5Zone-cool-discrete-stochastic-v1: 85 | 86 | ``Eplus-5Zone-cool-discrete-stochastic-v1`` 87 | """"""""""""""""""""""""""""""""""""""""""" 88 | 89 | :Type: residential building 90 | :More info: `framework docs `_ 91 | 92 | 93 | .. _env-Eplus-5Zone-hot-continuous-stochastic-v1: 94 | 95 | ``Eplus-5Zone-hot-continuous-stochastic-v1`` 96 | """""""""""""""""""""""""""""""""""""""""""" 97 | 98 | :Type: residential building 99 | :More info: `framework docs `_ 100 | 101 | 102 | .. _env-Eplus-5Zone-mixed-continuous-stochastic-v1: 103 | 104 | ``Eplus-5Zone-mixed-continuous-stochastic-v1`` 105 | """""""""""""""""""""""""""""""""""""""""""""" 106 | 107 | :Type: residential building 108 | :More info: `framework docs `_ 109 | 110 | 111 | .. _env-Eplus-5Zone-cool-continuous-stochastic-v1: 112 | 113 | ``Eplus-5Zone-cool-continuous-stochastic-v1`` 114 | """"""""""""""""""""""""""""""""""""""""""""" 115 | 116 | :Type: residential building 117 | :More info: `framework docs `_ 118 | 119 | 120 | .. _env-Eplus-datacenter-discrete-v1: 121 | 122 | ``Eplus-datacenter-discrete-v1`` 123 | """""""""""""""""""""""""""""""" 124 | 125 | :Type: data center 126 | :More info: `framework docs `_ 127 | 128 | 129 | .. _env-Eplus-datacenter-continuous-v1: 130 | 131 | ``Eplus-datacenter-continuous-v1`` 132 | """""""""""""""""""""""""""""""""" 133 | 134 | :Type: data center 135 | :More info: `framework docs `_ 136 | 137 | 138 | .. _env-Eplus-datacenter-discrete-stochastic-v1: 139 | 140 | ``Eplus-datacenter-discrete-stochastic-v1`` 141 | """"""""""""""""""""""""""""""""""""""""""" 142 | 143 | :Type: data center 144 | :More info: `framework docs `_ 145 | 146 | 147 | .. _env-Eplus-datacenter-continuous-stochastic-v1: 148 | 149 | ``Eplus-datacenter-continuous-stochastic-v1`` 150 | """"""""""""""""""""""""""""""""""""""""""""" 151 | 152 | :Type: data center 153 | :More info: `framework docs `_ 154 | 155 | 156 | .. _env-Eplus-IWMullion-discrete-v1: 157 | 158 | ``Eplus-IWMullion-discrete-v1`` 159 | """"""""""""""""""""""""""""""" 160 | 161 | :Type: office building 162 | :More info: `framework docs `_ 163 | 164 | 165 | .. _env-Eplus-IWMullion-continuous-v1: 166 | 167 | ``Eplus-IWMullion-continuous-v1`` 168 | """"""""""""""""""""""""""""""""" 169 | 170 | :Type: office building 171 | :More info: `framework docs `_ 172 | 173 | 174 | .. _env-Eplus-IWMullion-discrete-stochastic-v1: 175 | 176 | ``Eplus-IWMullion-discrete-stochastic-v1`` 177 | """""""""""""""""""""""""""""""""""""""""" 178 | 179 | :Type: office building 180 | :More info: `framework docs `_ 181 | 182 | 183 | .. _env-Eplus-IWMullion-continuous-stochastic-v1: 184 | 185 | ``Eplus-IWMullion-continuous-stochastic-v1`` 186 | """""""""""""""""""""""""""""""""""""""""""" 187 | 188 | :Type: office building 189 | :More info: `framework docs `_ 190 | -------------------------------------------------------------------------------- /beobench/wrappers/general.py: -------------------------------------------------------------------------------- 1 | """Environment wrappers that may be applied across gyms.""" 2 | 3 | import gym 4 | import gym.spaces 5 | import warnings 6 | import wandb 7 | import numpy as np 8 | 9 | from beobench.experiment.provider import config 10 | 11 | 12 | class EnvResetCalledError(Exception): 13 | pass 14 | 15 | 16 | class SubsetDictObs(gym.ObservationWrapper): 17 | """Wrapper that reduces dict observation space to subset.""" 18 | 19 | def __init__(self, env, selected_obs_keys=None): 20 | """Wrapper that reduces dict observation space to subset. 21 | 22 | Args: 23 | env (_type_): _description_ 24 | selected_obs_keys (_type_, optional): _description_. Defaults to None. 25 | """ 26 | super().__init__(env) 27 | self.selected_obs_keys = selected_obs_keys 28 | self._observation_space = gym.spaces.Dict( 29 | {key: self.env.observation_space[key] for key in selected_obs_keys} 30 | ) 31 | 32 | def observation(self, observation): 33 | return {key: observation[key] for key in self.selected_obs_keys} 34 | 35 | 36 | class FixDictActs(gym.ActionWrapper): 37 | """Wrapper to fix some actions in dict action space.""" 38 | 39 | def __init__(self, env: gym.Env, fixed_actions: dict = None): 40 | """Wrapper to fix some actions in dict action space. 41 | 42 | Args: 43 | env (gym.Env): environment to wrap. 44 | fixed_actions (dict, optional): dictionary of the values of fixed actions. 45 | Defaults to None. 46 | """ 47 | super().__init__(env) 48 | self.fixed_actions = fixed_actions 49 | self._action_space = gym.spaces.Dict( 50 | { 51 | key: self.env.action_space[key] 52 | for key in self.env.action_space.keys() 53 | if key not in fixed_actions.keys() 54 | } 55 | ) 56 | 57 | def action(self, action): 58 | return {**action, **self.fixed_actions} 59 | 60 | 61 | class PreventReset(gym.Wrapper): 62 | """Wrapper to prevent more than one (initial) reset of the environment.""" 63 | 64 | def __init__(self, env: gym.Env, raise_error: bool = False): 65 | """Wrapper to prevent more than one (initial) reset of the environment. 66 | 67 | Args: 68 | env (gym.Env): environment to wrap. 69 | raise_error (bool, optional): Whether to raise error. Defaults to False. 70 | """ 71 | super().__init__(env) 72 | self.reset_raises_error = raise_error 73 | self.first_reset_done = False 74 | 75 | def reset(self, **kwargs): 76 | if self.first_reset_done is True: 77 | if self.reset_raises_error: 78 | raise EnvResetCalledError("Environment method .reset() called.") 79 | else: 80 | warnings.warn("Environment method .reset() called.") 81 | return self.env.reset(**kwargs) 82 | else: 83 | self.first_reset_done = True 84 | return self.env.reset(**kwargs) 85 | 86 | 87 | class WandbLogger(gym.Wrapper): 88 | """Wrapper to log all env data for every xth step.""" 89 | 90 | def __init__( 91 | self, 92 | env: gym.Env, 93 | log_freq: int = 1, 94 | summary_metric_keys: list = None, 95 | restart_sum_metrics_at_reset: bool = False, 96 | ): 97 | """Wrapper to log all env data for every xth step. 98 | 99 | Args: 100 | env (gym.Env): environment to wrap. 101 | log_freq (int, optional): how often to log the step() method. E.g. for 2 102 | every second step is logged. Defaults to 1. 103 | summary_metric_keys (list, optional): list of keys of logged metrics for 104 | summary metrics such as cummulative sum and mean are computed. This 105 | defaults to ["env.returns.reward"]. WARNING: summary metrics only 106 | work for log_feq == 1, otherwise the cummulative metrics will not be 107 | correct. 108 | """ 109 | 110 | if summary_metric_keys is None: 111 | summary_metric_keys = ["env.returns.reward"] 112 | 113 | super().__init__(env) 114 | wandb.init( 115 | id=config["autogen"]["run_id"], 116 | project=config["general"]["wandb_project"], 117 | entity=config["general"]["wandb_entity"], 118 | group=config["general"]["wandb_group"], 119 | ) 120 | self.log_freq = log_freq 121 | self.total_env_steps = 0 122 | self.restart_sum_metrics_at_reset = restart_sum_metrics_at_reset 123 | self.cum_metrics = {key: 0 for key in summary_metric_keys} 124 | self.num_env_resets = 0 125 | self.last_log_dict = {} 126 | 127 | def step(self, action): 128 | obs, reward, done, info = self.env.step(action) 129 | 130 | self.total_env_steps += 1 131 | 132 | if self.total_env_steps % self.log_freq == 0: 133 | # TODO: enable flat dict for dict action and observation spaces 134 | # This would allow for summary values of invididual actions/observations 135 | if isinstance(action, (list, np.ndarray)): 136 | for i, act in enumerate(action): 137 | new_action = {f"{i}": act} 138 | action = new_action 139 | 140 | log_dict = { 141 | "env.inputs.action": action, 142 | "env.returns.obs": obs, 143 | "env.returns.reward": reward, 144 | "env.returns.done": done, 145 | "env.total_steps": self.total_env_steps, 146 | **{f"env.returns.info.{key}": value for key, value in info.items()}, 147 | } 148 | log_dict = self._create_summary_metrics(log_dict) 149 | wandb.log(log_dict) 150 | self.last_log_dict = log_dict 151 | 152 | return obs, reward, done, info 153 | 154 | def reset(self): 155 | 156 | if self.restart_sum_metrics_at_reset: 157 | wandb.log({"reset": self.last_log_dict, "reset.num": self.num_env_resets}) 158 | self.cum_metrics = {key: 0 for key in self.cum_metrics.keys()} 159 | self.num_env_resets += 1 160 | 161 | return self.env.reset() 162 | 163 | def _create_summary_metrics(self, log_dict: dict): 164 | """Create summary of variables in log dict. 165 | 166 | In particular, the 167 | 168 | Args: 169 | log_dict (dict): _description_ 170 | 171 | Returns: 172 | _type_: _description_ 173 | """ 174 | 175 | for key in self.cum_metrics.keys(): 176 | self.cum_metrics[key] += log_dict[key] 177 | log_dict[key + "_cum"] = self.cum_metrics[key] 178 | log_dict[key + "_mean"] = self.cum_metrics[key] / self.total_env_steps 179 | 180 | return log_dict 181 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # pylint: disable=missing-module-docstring 4 | # beobench documentation build configuration file, created by 5 | # sphinx-quickstart on Fri Jun 9 13:47:02 2017. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | # If extensions (or modules to document with autodoc) are in another 17 | # directory, add these directories to sys.path here. If the directory is 18 | # relative to the documentation root, use os.path.abspath to make it 19 | # absolute, like shown here. 20 | # 21 | import os 22 | import sys 23 | 24 | sys.path.insert(0, os.path.abspath("..")) 25 | 26 | import beobench # pylint: disable=wrong-import-position 27 | 28 | # -- General configuration --------------------------------------------- 29 | 30 | # If your documentation needs a minimal Sphinx version, state it here. 31 | # 32 | # needs_sphinx = '1.0' 33 | 34 | # Add any Sphinx extension module names here, as strings. They can be 35 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 36 | extensions = [ 37 | "sphinx.ext.autodoc", 38 | "sphinx.ext.viewcode", 39 | "sphinx.ext.autosummary", 40 | "sphinx_tabs.tabs", 41 | "sphinx.ext.napoleon", 42 | "sphinx_autodoc_typehints", 43 | # "myst_parser", 44 | # "m2r2", 45 | ] 46 | 47 | # Add any paths that contain templates here, relative to this directory. 48 | templates_path = ["_templates"] 49 | 50 | # The suffix(es) of source filenames. 51 | # You can specify multiple suffix as a list of string: 52 | # 53 | # source_suffix = ['.rst', '.md'] 54 | source_suffix = ".rst" 55 | 56 | # The master toctree document. 57 | master_doc = "index" 58 | 59 | # General information about the project. 60 | project = "beobench" 61 | copyright = "2021-2022" # pylint: disable=redefined-builtin 62 | author = "Beobench authors" 63 | 64 | # The version info for the project you're documenting, acts as replacement 65 | # for |version| and |release|, also used in various other places throughout 66 | # the built documents. 67 | # 68 | # The short X.Y version. 69 | version = beobench.__version__ 70 | # The full version, including alpha/beta/rc tags. 71 | release = beobench.__version__ 72 | 73 | # The language for content autogenerated by Sphinx. Refer to documentation 74 | # for a list of supported languages. 75 | # 76 | # This is also used if you do content translation via gettext catalogs. 77 | # Usually you set "language" from the command line for these cases. 78 | language = None 79 | 80 | # List of patterns, relative to source directory, that match files and 81 | # directories to ignore when looking for source files. 82 | # This patterns also effect to html_static_path and html_extra_path 83 | exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] 84 | 85 | # The name of the Pygments (syntax highlighting) style to use. 86 | pygments_style = "sphinx" 87 | 88 | # If true, `todo` and `todoList` produce output, else they produce nothing. 89 | todo_include_todos = False 90 | 91 | 92 | # -- Options for HTML output ------------------------------------------- 93 | 94 | # The theme to use for HTML and HTML Help pages. See the documentation for 95 | # a list of builtin themes. 96 | # 97 | # html_theme = "alabaster" 98 | html_theme = "sphinx_book_theme" 99 | 100 | # Theme options are theme-specific and customize the look and feel of a 101 | # theme further. For a list of options available for each theme, see the 102 | # documentation. 103 | # 104 | # html_theme_options = {} 105 | 106 | # Add any paths that contain custom static files (such as style sheets) here, 107 | # relative to this directory. They are copied after the builtin static files, 108 | # so a file named "default.css" will overwrite the builtin "default.css". 109 | html_static_path = ["_static"] 110 | 111 | 112 | # -- Options for HTMLHelp output --------------------------------------- 113 | 114 | # Output file base name for HTML help builder. 115 | htmlhelp_basename = "beobenchdoc" 116 | 117 | 118 | # -- Options for LaTeX output ------------------------------------------ 119 | 120 | latex_elements = { 121 | # The paper size ('letterpaper' or 'a4paper'). 122 | # 123 | # 'papersize': 'letterpaper', 124 | # The font size ('10pt', '11pt' or '12pt'). 125 | # 126 | # 'pointsize': '10pt', 127 | # Additional stuff for the LaTeX preamble. 128 | # 129 | # 'preamble': '', 130 | # Latex figure (float) alignment 131 | # 132 | # 'figure_align': 'htbp', 133 | } 134 | 135 | # Grouping the document tree into LaTeX files. List of tuples 136 | # (source start file, target name, title, author, documentclass 137 | # [howto, manual, or own class]). 138 | latex_documents = [ 139 | ( 140 | master_doc, 141 | "beobench.tex", 142 | "beobench Documentation", 143 | "Beobench authors", 144 | "manual", 145 | ), 146 | ] 147 | 148 | 149 | # -- Options for manual page output ------------------------------------ 150 | 151 | # One entry per manual page. List of tuples 152 | # (source start file, name, description, authors, manual section). 153 | man_pages = [(master_doc, "beobench", "beobench Documentation", [author], 1)] 154 | 155 | 156 | # -- Options for Texinfo output ---------------------------------------- 157 | 158 | # Grouping the document tree into Texinfo files. List of tuples 159 | # (source start file, target name, title, author, 160 | # dir menu entry, description, category) 161 | texinfo_documents = [ 162 | ( 163 | master_doc, 164 | "beobench", 165 | "beobench Documentation", 166 | author, 167 | "beobench", 168 | "One line description of project.", 169 | "Miscellaneous", 170 | ), 171 | ] 172 | 173 | 174 | ## Custom elements 175 | 176 | html_logo = "_static/beobench_logo_v2_large.png" 177 | html_favicon = "_static/beobench_favicon_v3.png" 178 | 179 | if html_theme == "alabaster": 180 | html_theme_options = { 181 | "logo_name": False, 182 | "logo": "/beobench_logo.png", 183 | "github_repo": "beobench", 184 | "github_user": "rdnfn", 185 | "github_button": True, 186 | "github_count": False, 187 | "github_type": "star", 188 | "fixed_sidebar": True, 189 | # "sidebar_width": "200pt", 190 | } 191 | elif html_theme == "sphinx_book_theme": 192 | html_theme_options = { 193 | "extra_navbar": "", 194 | "home_page_in_toc": False, 195 | "use_fullscreen_button": False, 196 | "use_download_button": True, 197 | "repository_url": "https://github.com/rdnfn/beobench", 198 | "use_repository_button": True, 199 | "use_issues_button": True, 200 | "logo_only": True, 201 | #'prev_next_buttons_location': None, 202 | } 203 | 204 | html_css_files = ["custom.css"] 205 | -------------------------------------------------------------------------------- /beobench/experiment/config_parser.py: -------------------------------------------------------------------------------- 1 | """Experiment config parser module""" 2 | 3 | from typing import Union 4 | import pathlib 5 | import uuid 6 | import yaml 7 | import ast 8 | import sys 9 | import random 10 | import os 11 | from beobench.logging import logger 12 | 13 | import beobench 14 | import beobench.utils 15 | 16 | from beobench.constants import USER_CONFIG_PATH 17 | 18 | # To enable compatiblity with Python<=3.8 (e.g. for sinergym dockerfile) 19 | if sys.version_info[1] >= 9: 20 | import importlib.resources 21 | else: 22 | import importlib_resources 23 | import importlib 24 | 25 | importlib.resources = importlib_resources 26 | 27 | 28 | def parse(config: Union[dict, str, pathlib.Path, list]) -> dict: 29 | """Parse experiment config to dict. 30 | 31 | Args: 32 | config (Union[dict, str, pathlib.Path, list]): path of yaml file 33 | 34 | Returns: 35 | dict: config in dictionary 36 | """ 37 | if isinstance(config, list): 38 | # get list of config dicts 39 | parsed_configs = [] 40 | for single_config in config: 41 | parsed_configs.append(parse(single_config)) 42 | 43 | # merge config dicts 44 | parsed_config = {} 45 | for conf in parsed_configs: 46 | parsed_config = beobench.utils.merge_dicts(parsed_config, conf) 47 | 48 | elif isinstance(config, pathlib.Path): 49 | # load config yaml to dict if path given 50 | with open(config, "r", encoding="utf-8") as config_file: 51 | parsed_config = yaml.safe_load(config_file) 52 | 53 | elif isinstance(config, str): 54 | if config[0] in ["{", "["]: 55 | # if json str or list 56 | parsed_config = parse(ast.literal_eval(config)) 57 | else: 58 | # make sure config is a real path 59 | config_path = pathlib.Path(config) 60 | parsed_config = parse(config_path) 61 | 62 | elif isinstance(config, dict): 63 | parsed_config = config 64 | 65 | else: 66 | raise ValueError( 67 | f"Config not one of allowed types (dict, str, pathlib.Path, list): {config}" 68 | ) 69 | 70 | return parsed_config 71 | 72 | 73 | def create_rllib_config(config: dict) -> dict: 74 | """Create a configuration for ray.tune.run() method. 75 | 76 | Args: 77 | config (dict): beobench config 78 | 79 | Raises: 80 | ValueError: this is raised if the config does not specify an rllib agent. 81 | 82 | Returns: 83 | dict: kwargs for ray.tune.run() method 84 | """ 85 | 86 | # Check if config is with rllib agent 87 | if config["agent"]["origin"] != "rllib": 88 | raise ValueError( 89 | ( 90 | "Configuration does not have rllib agent origin set." 91 | f"Config is set to: {config}" 92 | ) 93 | ) 94 | 95 | rllib_config = config["agent"]["config"] 96 | rllib_config["config"]["env_config"] = config["env"]["config"] 97 | if "name" in config["env"].keys(): 98 | rllib_config["config"]["env"] = config["env"]["name"] 99 | elif ( 100 | config["env"]["config"] is not None and "name" in config["env"]["config"].keys() 101 | ): 102 | rllib_config["config"]["env"] = config["env"]["config"]["name"] 103 | else: 104 | rllib_config["config"]["env"] = f"default_{config['env']['gym']}_env_name" 105 | 106 | return rllib_config 107 | 108 | 109 | def get_standard_config(name: str) -> dict: 110 | 111 | """Get standard beobench config from beobench.data.configs 112 | 113 | Returns: 114 | dict: default beobench config dict 115 | """ 116 | 117 | defs_path = importlib.resources.files("beobench.data.configs") 118 | with importlib.resources.as_file(defs_path.joinpath(f"{name}.yaml")) as def_file: 119 | config = parse(def_file) 120 | 121 | return config 122 | 123 | 124 | def get_default() -> dict: 125 | """Get default beobench config. 126 | 127 | Returns: 128 | dict: default beobench config dict 129 | """ 130 | 131 | return get_standard_config("default") 132 | 133 | 134 | def get_user() -> dict: 135 | """Get user beobench config. 136 | 137 | Returns: 138 | dict: default beobench config dict 139 | """ 140 | 141 | if os.path.isfile(USER_CONFIG_PATH): 142 | logger.info(f"Recognised user config at '{USER_CONFIG_PATH}'.") 143 | user_config = parse(USER_CONFIG_PATH) 144 | else: 145 | user_config = {} 146 | 147 | return user_config 148 | 149 | 150 | def add_default_and_user_configs(config: dict) -> dict: 151 | """Add default and user configs to existing beobench config. 152 | 153 | Args: 154 | config (dict): beobench config 155 | 156 | Returns: 157 | dict: merged dict of given config, and user and default configs. 158 | """ 159 | 160 | default_config = get_default() 161 | user_config = get_user() 162 | user_default_config = beobench.utils.merge_dicts( 163 | a=default_config, b=user_config, let_b_overrule_a=True 164 | ) 165 | config = beobench.utils.merge_dicts( 166 | a=user_default_config, b=config, let_b_overrule_a=True 167 | ) 168 | 169 | return config 170 | 171 | 172 | def get_autogen_config() -> dict: 173 | """Get automatically generated parts of a Beobench configuration.""" 174 | 175 | config = { 176 | "autogen": { 177 | "run_id": uuid.uuid4().hex, 178 | "random_seed": random.randint(1, 10000000), 179 | }, 180 | } 181 | 182 | return config 183 | 184 | 185 | def check_config(config: dict) -> None: 186 | """Check if config is valid. 187 | 188 | Args: 189 | config (dict): Beobench config. 190 | """ 191 | if "version" in config["general"].keys(): 192 | requested_version = config["general"]["version"] 193 | if requested_version != beobench.__version__: 194 | raise ValueError( 195 | f"Beobench config requests version {requested_version}" 196 | f" that does not match installed version {beobench.__version__}. " 197 | "Change the installed Beobench version to the requested version " 198 | f"{requested_version} or remove general.version parameter from config " 199 | "to prevent this error. " 200 | "If you have recently changed Beobench version, it may be worth trying" 201 | " adding the flag `--force-build` to the `beobench run` command. " 202 | "If you're using Beobench installed from a local clone, also add the " 203 | "flag `-d `." 204 | ) 205 | 206 | 207 | def get_high_level_config(method: str, gym: str, env: str) -> dict: 208 | """Get config from agent, gym and env params. 209 | 210 | Args: 211 | method (str): name of method 212 | gym (str): name of gym 213 | env (str): name of environment. 214 | 215 | Returns: 216 | dict: Beobench configuration. 217 | """ 218 | 219 | config = {} 220 | if method is not None: 221 | config = beobench.utils.merge_dicts( 222 | config, 223 | get_standard_config(f"method_{method}"), 224 | ) 225 | if gym is not None and env is not None: 226 | config = beobench.utils.merge_dicts( 227 | config, 228 | get_standard_config(f"gym_{gym}"), 229 | ) 230 | config["env"]["name"] = env 231 | 232 | return config 233 | -------------------------------------------------------------------------------- /HISTORY.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | History 3 | ======= 4 | 5 | 0.5.4 (2023-01-16) 6 | ------------------ 7 | 8 | * Fixes: 9 | 10 | * Ensure agent script stdout is shown (e.g. log statements and so on) by replacing ``check_output`` call with ``check_call``. This fixes #99 raised by @XkunW (Thanks!). 11 | 12 | * Other: 13 | 14 | * Add note on maintenance status of project to main readme and docs. Beobench is only receiving minor maintenance updates at this point. Therefore, Beobench may also no longer support the latest versions of the integrated building simulation tools. If you would like to use the latest versions of these tools, it is recommended to use them directly without Beobench. 15 | 16 | 0.5.3 (2022-11-18) 17 | ------------------ 18 | 19 | * Features: 20 | 21 | * Add support for installing agent script's requirements via requirements file (#71). 22 | 23 | * Improvements 24 | 25 | * Add support for dry running ``beobench run`` with ``--dry-run`` flag. This aims to help with testing and debugging. 26 | * Add explicit warning for windows users with recommended fixes (i.e. using WSL instead). 27 | 28 | * Fixes: 29 | 30 | * Fix Sinergym data ranges in Sinergym experiment container definition in beobench_contrib (#96). Fix by @XkunW, thanks! 31 | * Remove default normalisation with Sinergym (as this may fail with newer Sinergym versions as pointed out by @kad99kev (thanks!)). 32 | * Pin the version of sinergym to the currently latest version to avoid future issues (v2.1.2). 33 | * Change the way Beobench is installed inside experiment containers. Previously this was done using conditional logic inside Dockerfiles. Now the logic is done in Python, with two different dockerfiles for local and pypi installations. This enables the use of non-buildx in the construction of Beobench experiment containers. Credit and thanks to @HYDesmondLiu and @david-woelfle for finding and sharing the underlying error. 34 | * Fix #90 by removing access to env config before env_creator script. Thanks to @HYDesmondLiu, who first flagged this bug in #82. 35 | * If one of the Beobench scheduler subprocesses fails (e.g. docker run) the main process now fails as well. 36 | 37 | 38 | 0.5.2 (2022-07-01) 39 | ------------------ 40 | 41 | * Known issues 42 | 43 | * This release breaks the experiment build process for most machines. Thus, this release was yanked on pypi and is not installed unless specifically pinned to. See #82 for more details. 44 | 45 | * Improvements: 46 | 47 | * Add more informative error when there are issues with access to Docker from Beobench. 48 | 49 | * Fixes: 50 | 51 | * Revert default build command to ``docker build`` from ``docker buildx build``. Only arm64 machines use ``buildx`` now. This aims to enable usage of older docker versions such as v19.03 on non-arm64 machines. Arm64 machines require buildx and thus also newer docker versions. 52 | * Fix wrong env name in logging output. Removes unused default env name var and fix logging output to use new env name location. 53 | 54 | 55 | 56 | 0.5.1 (2022-06-28) 57 | ------------------ 58 | 59 | * Features: 60 | 61 | * Add pretty logging based on loguru package. Now all Beobench output is clearly marked as such. 62 | 63 | * Improvements: 64 | 65 | * Enable adding wrapper without setting config. 66 | * Add ``demo.yaml`` simple example config. 67 | 68 | * Fixes: 69 | 70 | * Update Sinergym integration to latest Sinergym version. 71 | 72 | 0.5.0 (2022-05-26) 73 | ------------------ 74 | 75 | * Features: 76 | 77 | * Mean and cummulative metrics can now be logged by WandbLogger wrapper. 78 | * Support for automatically running multiple samples/trials of same experiment via ``num_samples`` config parameter. 79 | * Configs named `.beobench.yml` will be automatically parsed when Beobench is run in directory containing such a config. This allows users to set e.g. wandb API keys without referring to the config in every Beobench command call. 80 | * Configs from experiments now specify the Beobench version used. When trying to rerun an experiment this version will be checked, and an error thrown if there is a mismatch between installed and requested version. 81 | * Add improved high-level API for getting started. This uses the CLI arguments ``--method``, ``--gym`` and ``--env``. Example usage: ``beobench run --method ppo --gym sinergym --env Eplus-5Zone-hot-continuous-v1`` (#55). 82 | 83 | * Improvements: 84 | 85 | * Add ``CITATION.cff`` file to make citing software easier. 86 | * By default, docker builds of experiment images are now skipped if an image with tag corresponding to installed Beobench version already exists. 87 | * Remove outdated guides and add yaml configuration description from docs (#38, #76, #78). 88 | * Add support for logging multidimensional actions to wandb. 89 | * Add support for logging summary metrics on every env reset to wandb. 90 | * Energym config now uses ``name`` argument like other integrations (#34). 91 | 92 | * Fixes: 93 | 94 | * Updated BOPTEST integration to work with current version of Beobench. 95 | 96 | 0.4.4 (2022-05-09) 97 | ------------------ 98 | 99 | * Features: 100 | 101 | * Add general support for wrappers. (#28) 102 | 103 | * Improvements: 104 | 105 | * Make dev beobench build part of image build process for improved 106 | speed. 107 | * Add number of environment steps (``env_step``) to wandb logging. 108 | * Update logo to new version (#48) 109 | * Update docs and main readme to include more useful quickstart guide, which includes a custom agent (#47) 110 | 111 | * Fixes: 112 | 113 | * Enable automatic episode data logging in RLlib integration for long training periods. 114 | * Update broken links in main readme env list (#40) 115 | 116 | 0.4.3 (2022-04-12) 117 | ------------------ 118 | 119 | * Feature: enable easy access to standard configs via util method 120 | * Feature: add non-normalised observations to info in energym integration (#62) 121 | * Feature: enable logging full episode data from RLlib and adding this data 122 | to wandb (#62) 123 | * Feature: ship integrations with package improving image build times (#44) 124 | * Feature: add wandb logging support for random agent script (#59) 125 | * Feature: add rule-based agent script based on energym controller (#60) 126 | * Fix: add importlib-resources backport package to requirements 127 | * Fix: allow users to disable reset() method in energym envs (#43) 128 | * Aux: add automatic deployment of PyPI package via GitHub actions (#50) 129 | * Aux: add tests and automatic checks on PRs (#25) 130 | 131 | 0.4.2 (2022-04-04) 132 | ------------------ 133 | 134 | * Feature: defining all relevant options/kwargs of CLI an API is now supported 135 | yaml files (#54) 136 | * Feature: allow multiple configs to be given to both CLI 137 | (giving multiple ``-c`` options) and Python API (as a list) (#51) 138 | * Fix: adapted Energym env reset() method to avoid triggering 139 | long warm-up times with additional simulation runs (#43) 140 | * Fix: enable container build even if prior build failed midway 141 | and left artifacts 142 | 143 | 0.4.1 (2022-03-30) 144 | ------------------ 145 | 146 | * Feature: enable package extras to be given in development mode 147 | * Feature: add support for arm64/aarch64-based development by forcing 148 | experiment containers to run as amd64 containers on those systems (#32) 149 | * Fix: add gym to extended package requirements 150 | 151 | 152 | 0.4.0 (2022-03-28) 153 | ------------------ 154 | 155 | * Make dependencies that are only used inside experiment/gym 156 | containers optional 157 | (for all dependencies install via ``pip install beobench[extended]``) 158 | * Add two part experiment image build process so that there is shared beobench 159 | installation dockerfile 160 | * Add support for yaml config files (!) 161 | * Overhaul of documentation, including new envs page and new theme 162 | * Enable RLlib free experiment containers when not required 163 | * Add beobench_contrib as submodule 164 | * Simplify Pypi readme file 165 | * Remove GPU requirement for devcontainer 166 | 167 | 0.3.0 (2022-02-14) 168 | ------------------ 169 | 170 | * Add complete redesign of CLI: main command changed from 171 | ``python -m beobench.experiment.scheduler`` to ``beobench run``. 172 | * Add support for energym environments 173 | * Add support for MLflow experiment tracking 174 | * Add support for custom agents 175 | 176 | 177 | 0.2.1 (2022-02-03) 178 | ------------------ 179 | 180 | * Add integration with sinergym 181 | * Move gym integrations to separate beobench_contrib repo 182 | * Make usage of GPUs in containers optional 183 | 184 | 0.2.0 (2022-01-18) 185 | ------------------ 186 | 187 | * Enable adding custom environments to beobench with 188 | *docker build context*-based syntax 189 | * Save experiment results on host machine 190 | * Major improvements to documentation 191 | * Remove unnecessary wandb arguments in main CLI 192 | 193 | 0.1.0 (2022-01-10) 194 | ------------------ 195 | 196 | * First release on PyPI. 197 | -------------------------------------------------------------------------------- /beobench/experiment/containers.py: -------------------------------------------------------------------------------- 1 | """Module for managing experiment containers.""" 2 | 3 | import contextlib 4 | import subprocess 5 | import os 6 | import docker 7 | from loguru import logger 8 | 9 | import beobench 10 | from beobench.constants import AVAILABLE_INTEGRATIONS 11 | 12 | # To enable compatiblity with Python<=3.6 (e.g. for sinergym dockerfile) 13 | try: 14 | import importlib.resources 15 | except ImportError: 16 | import importlib_resources 17 | import importlib 18 | 19 | importlib.resources = importlib_resources 20 | 21 | 22 | def check_image_exists(image: str): 23 | try: 24 | client = docker.from_env() 25 | except docker.errors.DockerException as e: 26 | logger.error( 27 | ( 28 | "Unable to access docker client. " 29 | "Is Docker installed and are all permissions setup? " 30 | "See here for Beobench installation guidance: " 31 | "at https://beobench.readthedocs.io/en/latest/getting_started.html. " 32 | "If on Linux, make sure to follow the post-installation steps to " 33 | "ensure all necessary permissions are set, see here: " 34 | "https://beobench.readthedocs.io/en/latest/guides/" 35 | "installation_linux.html" 36 | ) 37 | ) 38 | raise e 39 | 40 | try: 41 | client.images.get(image) 42 | return True 43 | except docker.errors.ImageNotFound: 44 | return False 45 | 46 | 47 | def build_experiment_container( 48 | build_context: str, 49 | use_no_cache: bool = False, 50 | beobench_package: str = "beobench", 51 | beobench_extras: str = "extended", 52 | force_build: bool = False, 53 | requirements: str = None, 54 | ) -> None: 55 | """Build experiment container from beobench/integrations/boptest/Dockerfile. 56 | 57 | Args: 58 | build_context (str): context to build from. This can either be a path to 59 | directory with Dockerfile in it, or a URL to a github repo, or name 60 | of existing beobench integration (e.g. `boptest`). See the official docs 61 | https://docs.docker.com/engine/reference/commandline/build/ for more info. 62 | use_no_cache (bool, optional): wether to use cache in build. Defaults to False. 63 | beobench_extras (str, optional): which beobench extra dependencies to install 64 | As in `pip install beobench[extras]`. Defaults to "extended". 65 | force_build (bool, optional): whether to force a re-build, even if 66 | image already exists. 67 | """ 68 | 69 | version = beobench.__version__ 70 | 71 | # Flags are shared between gym image build and gym_and_beobench image build 72 | flags = [] 73 | 74 | # On arm64 machines force experiment containers to be amd64 75 | # This is only useful for development purposes. 76 | # (example: M1 macbooks) 77 | if os.uname().machine in ["arm64", "aarch64"]: 78 | # Using buildx to enable platform-specific builds 79 | build_commands = ["docker", "buildx", "build"] 80 | flags += ["--platform", "linux/amd64"] 81 | else: 82 | # Otherwise use standard docker build command. 83 | # This change enables usage of older docker versions w/o buildx, 84 | # e.g. v19.03, on non-arm64 machines 85 | build_commands = ["docker", "build"] 86 | 87 | if use_no_cache: 88 | flags.append("--no-cache") 89 | 90 | if build_context in AVAILABLE_INTEGRATIONS: 91 | image_name = f"beobench_{build_context}" 92 | gym_name = build_context 93 | gym_source = importlib.resources.files("beobench").joinpath( 94 | f"beobench_contrib/gyms/{gym_name}" 95 | ) 96 | package_build_context = True 97 | 98 | logger.info(f"Recognised integration named {gym_name}.") 99 | else: 100 | # get alphanumeric name from context 101 | context_name = "".join(e for e in build_context if e.isalnum()) 102 | image_name = f"beobench_custom_{context_name}" 103 | package_build_context = False 104 | 105 | # Create tags of different image stages 106 | stage0_image_tag = f"{image_name}_base:{version}" 107 | stage1_image_tag = f"{image_name}_intermediate:{version}" 108 | stage2_image_tag = f"{image_name}_complete:{version}" 109 | 110 | if requirements is None: 111 | final_image_tag = stage2_image_tag 112 | else: 113 | stage3_image_tag = f"{image_name}_custom_requirements:{version}" 114 | final_image_tag = stage3_image_tag 115 | 116 | # skip build if image already exists. 117 | if not force_build and check_image_exists(final_image_tag): 118 | logger.info(f"Existing image found ({final_image_tag}). Skipping build.") 119 | return final_image_tag 120 | 121 | logger.warning( 122 | f"Image not found ({stage2_image_tag}) or forced rebuild. Building image.", 123 | ) 124 | 125 | logger.info(f"Building experiment base image `{stage0_image_tag}`...") 126 | 127 | with contextlib.ExitStack() as stack: 128 | # if using build context from beobench package, get (potentially temp.) build 129 | # context file path 130 | if package_build_context: 131 | build_context = stack.enter_context(importlib.resources.as_file(gym_source)) 132 | build_context = str(build_context.absolute()) 133 | 134 | # Part 1: build stage 0 (base) experiment image 135 | stage0_build_args = [ 136 | *build_commands, 137 | "-t", 138 | stage0_image_tag, 139 | *flags, 140 | build_context, 141 | ] 142 | env = os.environ.copy() 143 | logger.info("Running command: " + " ".join(stage0_build_args)) 144 | subprocess.check_call( 145 | stage0_build_args, 146 | env=env, # this enables accessing dockerfile in subdir 147 | ) 148 | 149 | # Part 2: build stage 1 (intermediate) experiment image 150 | # This includes installation of beobench in experiment image 151 | stage1_dockerfile = str( 152 | importlib.resources.files("beobench.data.dockerfiles").joinpath( 153 | "Dockerfile.experiment" 154 | ) 155 | ) 156 | 157 | stage1_build_args = [ 158 | *build_commands, 159 | "-t", 160 | stage1_image_tag, 161 | "-f", 162 | "-", 163 | "--build-arg", 164 | f"GYM_IMAGE={stage0_image_tag}", 165 | "--build-arg", 166 | f"EXTRAS={beobench_extras}", 167 | *flags, 168 | build_context, 169 | ] 170 | 171 | # Load dockerfile into pipe 172 | with subprocess.Popen( 173 | ["cat", stage1_dockerfile], stdout=subprocess.PIPE 174 | ) as proc: 175 | logger.info("Running command: " + " ".join(stage1_build_args)) 176 | subprocess.check_call( 177 | stage1_build_args, 178 | stdin=proc.stdout, 179 | env=env, # this enables accessing dockerfile in subdir 180 | ) 181 | 182 | # Part 3: build stage 2 (complete) experiment image 183 | if beobench_package is None: 184 | beobench_package = "beobench" 185 | if beobench_package == "beobench": 186 | package_type = "pypi" 187 | build_context = "-" 188 | stage2_docker_flags = [] 189 | else: 190 | package_type = "local" 191 | build_context = beobench_package 192 | # need to add std-in-dockerfile via -f flag and not context directly 193 | stage2_docker_flags = ["-f", "-"] 194 | 195 | stage2_dockerfile = str( 196 | importlib.resources.files("beobench.data.dockerfiles").joinpath( 197 | f"Dockerfile.beobench_install_{package_type}" 198 | ) 199 | ) 200 | 201 | stage2_build_args = [ 202 | *build_commands, 203 | "-t", 204 | stage2_image_tag, 205 | *stage2_docker_flags, 206 | "--build-arg", 207 | f"PREV_IMAGE={stage1_image_tag}", 208 | "--build-arg", 209 | f"EXTRAS={beobench_extras}", 210 | *flags, 211 | build_context, 212 | ] 213 | 214 | with subprocess.Popen( 215 | ["cat", stage2_dockerfile], stdout=subprocess.PIPE 216 | ) as proc: 217 | logger.info("Running command: " + " ".join(stage2_build_args)) 218 | subprocess.check_call( 219 | stage2_build_args, 220 | stdin=proc.stdout, 221 | env=env, # this enables accessing dockerfile in subdir 222 | ) 223 | 224 | if requirements is not None: 225 | # Part 4: build stage 3 (optional) additional installation of requirements 226 | build_context = str(requirements.parents[0].absolute()) 227 | # need to add std-in-dockerfile via -f flag and not context directly 228 | stage3_docker_flags = ["-f", "-"] 229 | 230 | stage3_dockerfile = str( 231 | importlib.resources.files("beobench.data.dockerfiles").joinpath( 232 | "Dockerfile.requirements" 233 | ) 234 | ) 235 | 236 | stage3_build_args = [ 237 | *build_commands, 238 | "-t", 239 | stage3_image_tag, 240 | *stage3_docker_flags, 241 | "--build-arg", 242 | f"PREV_IMAGE={stage2_image_tag}", 243 | "--build-arg", 244 | f"REQUIREMENTS={requirements.name}", 245 | *flags, 246 | build_context, 247 | ] 248 | 249 | with subprocess.Popen( 250 | ["cat", stage3_dockerfile], stdout=subprocess.PIPE 251 | ) as proc: 252 | logger.info("Running command: " + " ".join(stage3_build_args)) 253 | subprocess.check_call( 254 | stage3_build_args, 255 | stdin=proc.stdout, 256 | env=env, # this enables accessing dockerfile in subdir 257 | ) 258 | 259 | logger.info("Experiment gym image build finished.") 260 | 261 | return final_image_tag 262 | 263 | 264 | def create_docker_network(network_name: str) -> None: 265 | """Create docker network. 266 | 267 | For more details see 268 | https://docs.docker.com/engine/reference/run/#network-settings 269 | 270 | Args: 271 | network_name (str): name of docker network. 272 | """ 273 | 274 | logger.info("Creating docker network ...") 275 | try: 276 | args = ["docker", "network", "create", network_name] 277 | subprocess.check_call(args) 278 | logger.info("Docker network created.") 279 | except subprocess.CalledProcessError: 280 | logger.info("No new network created. Network may already exist.") 281 | 282 | 283 | class DockerSetupError(Exception): 284 | pass 285 | -------------------------------------------------------------------------------- /pylintrc: -------------------------------------------------------------------------------- 1 | # This Pylint rcfile contains a best-effort configuration to uphold the 2 | # best-practices and style described in the Google Python style guide: 3 | # https://google.github.io/styleguide/pyguide.html 4 | # 5 | # Its canonical open-source location is: 6 | # https://google.github.io/styleguide/pylintrc 7 | 8 | [MASTER] 9 | 10 | # Files or directories to be skipped. They should be base names, not paths. 11 | ignore=third_party 12 | 13 | # Files or directories matching the regex patterns are skipped. The regex 14 | # matches against base names, not paths. 15 | ignore-patterns= 16 | 17 | # Pickle collected data for later comparisons. 18 | persistent=no 19 | 20 | # List of plugins (as comma separated values of python modules names) to load, 21 | # usually to register additional checkers. 22 | load-plugins= 23 | 24 | # Use multiple processes to speed up Pylint. 25 | jobs=4 26 | 27 | # Allow loading of arbitrary C extensions. Extensions are imported into the 28 | # active Python interpreter and may run arbitrary code. 29 | unsafe-load-any-extension=no 30 | 31 | 32 | [MESSAGES CONTROL] 33 | 34 | # Only show warnings with the listed confidence levels. Leave empty to show 35 | # all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED 36 | confidence= 37 | 38 | # Enable the message, report, category or checker with the given id(s). You can 39 | # either give multiple identifier separated by comma (,) or put this option 40 | # multiple time (only on the command line, not in the configuration file where 41 | # it should appear only once). See also the "--disable" option for examples. 42 | #enable= 43 | 44 | # Disable the message, report, category or checker with the given id(s). You 45 | # can either give multiple identifiers separated by comma (,) or put this 46 | # option multiple times (only on the command line, not in the configuration 47 | # file where it should appear only once).You can also use "--disable=all" to 48 | # disable everything first and then reenable specific checks. For example, if 49 | # you want to run only the similarities checker, you can use "--disable=all 50 | # --enable=similarities". If you want to run only the classes checker, but have 51 | # no Warning level messages displayed, use"--disable=all --enable=classes 52 | # --disable=W" 53 | disable=abstract-method, 54 | apply-builtin, 55 | arguments-differ, 56 | attribute-defined-outside-init, 57 | backtick, 58 | bad-option-value, 59 | basestring-builtin, 60 | buffer-builtin, 61 | c-extension-no-member, 62 | consider-using-enumerate, 63 | cmp-builtin, 64 | cmp-method, 65 | coerce-builtin, 66 | coerce-method, 67 | delslice-method, 68 | div-method, 69 | duplicate-code, 70 | eq-without-hash, 71 | execfile-builtin, 72 | file-builtin, 73 | filter-builtin-not-iterating, 74 | fixme, 75 | getslice-method, 76 | global-statement, 77 | hex-method, 78 | idiv-method, 79 | implicit-str-concat-in-sequence, 80 | import-error, 81 | import-self, 82 | import-star-module-level, 83 | inconsistent-return-statements, 84 | input-builtin, 85 | intern-builtin, 86 | invalid-str-codec, 87 | locally-disabled, 88 | long-builtin, 89 | long-suffix, 90 | map-builtin-not-iterating, 91 | misplaced-comparison-constant, 92 | missing-function-docstring, 93 | metaclass-assignment, 94 | next-method-called, 95 | next-method-defined, 96 | no-absolute-import, 97 | no-else-break, 98 | no-else-continue, 99 | no-else-raise, 100 | no-else-return, 101 | no-init, # added 102 | no-member, 103 | no-name-in-module, 104 | no-self-use, 105 | nonzero-method, 106 | oct-method, 107 | old-division, 108 | old-ne-operator, 109 | old-octal-literal, 110 | old-raise-syntax, 111 | parameter-unpacking, 112 | print-statement, 113 | raising-string, 114 | range-builtin-not-iterating, 115 | raw_input-builtin, 116 | rdiv-method, 117 | reduce-builtin, 118 | relative-import, 119 | reload-builtin, 120 | round-builtin, 121 | setslice-method, 122 | signature-differs, 123 | standarderror-builtin, 124 | suppressed-message, 125 | sys-max-int, 126 | too-few-public-methods, 127 | too-many-ancestors, 128 | too-many-arguments, 129 | too-many-boolean-expressions, 130 | too-many-branches, 131 | too-many-instance-attributes, 132 | too-many-locals, 133 | too-many-nested-blocks, 134 | too-many-public-methods, 135 | too-many-return-statements, 136 | too-many-statements, 137 | trailing-newlines, 138 | unichr-builtin, 139 | unicode-builtin, 140 | unnecessary-pass, 141 | unpacking-in-except, 142 | useless-else-on-loop, 143 | useless-object-inheritance, 144 | useless-suppression, 145 | using-cmp-argument, 146 | wrong-import-order, 147 | xrange-builtin, 148 | zip-builtin-not-iterating, 149 | 150 | 151 | [REPORTS] 152 | 153 | # Set the output format. Available formats are text, parseable, colorized, msvs 154 | # (visual studio) and html. You can also give a reporter class, eg 155 | # mypackage.mymodule.MyReporterClass. 156 | output-format=text 157 | 158 | # Tells whether to display a full report or only the messages 159 | reports=no 160 | 161 | # Python expression which should return a note less than 10 (10 is the highest 162 | # note). You have access to the variables errors warning, statement which 163 | # respectively contain the number of errors / warnings messages and the total 164 | # number of statements analyzed. This is used by the global evaluation report 165 | # (RP0004). 166 | evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) 167 | 168 | # Template used to display messages. This is a python new-style format string 169 | # used to format the message information. See doc for all details 170 | #msg-template= 171 | 172 | 173 | [BASIC] 174 | 175 | # Good variable names which should always be accepted, separated by a comma 176 | good-names=main,_ 177 | 178 | # Bad variable names which should always be refused, separated by a comma 179 | bad-names= 180 | 181 | # Colon-delimited sets of names that determine each other's naming style when 182 | # the name regexes allow several styles. 183 | name-group= 184 | 185 | # Include a hint for the correct naming format with invalid-name 186 | include-naming-hint=no 187 | 188 | # List of decorators that produce properties, such as abc.abstractproperty. Add 189 | # to this list to register other decorators that produce valid properties. 190 | property-classes=abc.abstractproperty,cached_property.cached_property,cached_property.threaded_cached_property,cached_property.cached_property_with_ttl,cached_property.threaded_cached_property_with_ttl 191 | 192 | # Regular expression matching correct function names 193 | function-rgx=^(?:(?PsetUp|tearDown|setUpModule|tearDownModule)|(?P_?[A-Z][a-zA-Z0-9]*)|(?P_?[a-z][a-z0-9_]*))$ 194 | 195 | # Regular expression matching correct variable names 196 | variable-rgx=^[a-z][a-z0-9_]*$ 197 | 198 | # Regular expression matching correct constant names 199 | const-rgx=^(_?[A-Z][A-Z0-9_]*|__[a-z0-9_]+__|_?[a-z][a-z0-9_]*)$ 200 | 201 | # Regular expression matching correct attribute names 202 | attr-rgx=^_{0,2}[a-z][a-z0-9_]*$ 203 | 204 | # Regular expression matching correct argument names 205 | argument-rgx=^[a-z][a-z0-9_]*$ 206 | 207 | # Regular expression matching correct class attribute names 208 | class-attribute-rgx=^(_?[A-Z][A-Z0-9_]*|__[a-z0-9_]+__|_?[a-z][a-z0-9_]*)$ 209 | 210 | # Regular expression matching correct inline iteration names 211 | inlinevar-rgx=^[a-z][a-z0-9_]*$ 212 | 213 | # Regular expression matching correct class names 214 | class-rgx=^_?[A-Z][a-zA-Z0-9]*$ 215 | 216 | # Regular expression matching correct module names 217 | module-rgx=^(_?[a-z][a-z0-9_]*|__init__)$ 218 | 219 | # Regular expression matching correct method names 220 | method-rgx=(?x)^(?:(?P_[a-z0-9_]+__|runTest|setUp|tearDown|setUpTestCase|tearDownTestCase|setupSelf|tearDownClass|setUpClass|(test|assert)_*[A-Z0-9][a-zA-Z0-9_]*|next)|(?P_{0,2}[A-Z][a-zA-Z0-9_]*)|(?P_{0,2}[a-z][a-z0-9_]*))$ 221 | 222 | # Regular expression which should only match function or class names that do 223 | # not require a docstring. 224 | no-docstring-rgx=(__.*__|main|test.*|.*test|.*Test)$ 225 | 226 | # Minimum line length for functions/classes that require docstrings, shorter 227 | # ones are exempt. 228 | docstring-min-length=10 229 | 230 | 231 | [TYPECHECK] 232 | 233 | # List of decorators that produce context managers, such as 234 | # contextlib.contextmanager. Add to this list to register other decorators that 235 | # produce valid context managers. 236 | contextmanager-decorators=contextlib.contextmanager,contextlib2.contextmanager 237 | 238 | # Tells whether missing members accessed in mixin class should be ignored. A 239 | # mixin class is detected if its name ends with "mixin" (case insensitive). 240 | ignore-mixin-members=yes 241 | 242 | # List of module names for which member attributes should not be checked 243 | # (useful for modules/projects where namespaces are manipulated during runtime 244 | # and thus existing member attributes cannot be deduced by static analysis. It 245 | # supports qualified module names, as well as Unix pattern matching. 246 | ignored-modules= 247 | 248 | # List of class names for which member attributes should not be checked (useful 249 | # for classes with dynamically set attributes). This supports the use of 250 | # qualified names. 251 | ignored-classes=optparse.Values,thread._local,_thread._local 252 | 253 | # List of members which are set dynamically and missed by pylint inference 254 | # system, and so shouldn't trigger E1101 when accessed. Python regular 255 | # expressions are accepted. 256 | generated-members= 257 | 258 | 259 | [FORMAT] 260 | 261 | # Maximum number of characters on a single line. 262 | max-line-length=88 263 | 264 | # TODO(https://github.com/PyCQA/pylint/issues/3352): Direct pylint to exempt 265 | # lines made too long by directives to pytype. 266 | 267 | # Regexp for a line that is allowed to be longer than the limit. 268 | ignore-long-lines=(?x)( 269 | ^\s*(\#\ )??$| 270 | ^\s*(from\s+\S+\s+)?import\s+.+$) 271 | 272 | # Allow the body of an if to be on the same line as the test if there is no 273 | # else. 274 | single-line-if-stmt=yes 275 | 276 | # List of optional constructs for which whitespace checking is disabled. `dict- 277 | # separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. 278 | # `trailing-comma` allows a space between comma and closing bracket: (a, ). 279 | # `empty-line` allows space-only lines. 280 | # no-space-check= 281 | 282 | # Maximum number of lines in a module 283 | max-module-lines=99999 284 | 285 | # String used as indentation unit. The internal Google style guide mandates 2 286 | # spaces. Google's externaly-published style guide says 4, consistent with 287 | # PEP 8. Here, we use 2 spaces, for conformity with many open-sourced Google 288 | # projects (like TensorFlow). 289 | indent-string=' ' 290 | 291 | # Number of spaces of indent required inside a hanging or continued line. 292 | indent-after-paren=4 293 | 294 | # Expected format of line ending, e.g. empty (any line ending), LF or CRLF. 295 | expected-line-ending-format= 296 | 297 | 298 | [MISCELLANEOUS] 299 | 300 | # List of note tags to take in consideration, separated by a comma. 301 | notes=TODO 302 | 303 | 304 | [STRING] 305 | 306 | # This flag controls whether inconsistent-quotes generates a warning when the 307 | # character used as a quote delimiter is used inconsistently within a module. 308 | check-quote-consistency=yes 309 | 310 | 311 | [VARIABLES] 312 | 313 | # Tells whether we should check for unused import in __init__ files. 314 | init-import=no 315 | 316 | # A regular expression matching the name of dummy variables (i.e. expectedly 317 | # not used). 318 | dummy-variables-rgx=^\*{0,2}(_$|unused_|dummy_) 319 | 320 | # List of additional names supposed to be defined in builtins. Remember that 321 | # you should avoid to define new builtins when possible. 322 | additional-builtins= 323 | 324 | # List of strings which can identify a callback function by name. A callback 325 | # name must start or end with one of those strings. 326 | callbacks=cb_,_cb 327 | 328 | # List of qualified module names which can have objects that can redefine 329 | # builtins. 330 | redefining-builtins-modules=six,six.moves,past.builtins,future.builtins,functools 331 | 332 | 333 | [LOGGING] 334 | 335 | # Logging modules to check that the string format arguments are in logging 336 | # function parameter format 337 | logging-modules=logging,absl.logging,tensorflow.io.logging 338 | 339 | 340 | [SIMILARITIES] 341 | 342 | # Minimum lines number of a similarity. 343 | min-similarity-lines=4 344 | 345 | # Ignore comments when computing similarities. 346 | ignore-comments=yes 347 | 348 | # Ignore docstrings when computing similarities. 349 | ignore-docstrings=yes 350 | 351 | # Ignore imports when computing similarities. 352 | ignore-imports=no 353 | 354 | 355 | [SPELLING] 356 | 357 | # Spelling dictionary name. Available dictionaries: none. To make it working 358 | # install python-enchant package. 359 | spelling-dict= 360 | 361 | # List of comma separated words that should not be checked. 362 | spelling-ignore-words= 363 | 364 | # A path to a file that contains private dictionary; one word per line. 365 | spelling-private-dict-file= 366 | 367 | # Tells whether to store unknown words to indicated private dictionary in 368 | # --spelling-private-dict-file option instead of raising a message. 369 | spelling-store-unknown-words=no 370 | 371 | 372 | [IMPORTS] 373 | 374 | # Deprecated modules which should not be used, separated by a comma 375 | deprecated-modules=regsub, 376 | TERMIOS, 377 | Bastion, 378 | rexec, 379 | sets 380 | 381 | # Create a graph of every (i.e. internal and external) dependencies in the 382 | # given file (report RP0402 must not be disabled) 383 | import-graph= 384 | 385 | # Create a graph of external dependencies in the given file (report RP0402 must 386 | # not be disabled) 387 | ext-import-graph= 388 | 389 | # Create a graph of internal dependencies in the given file (report RP0402 must 390 | # not be disabled) 391 | int-import-graph= 392 | 393 | # Force import order to recognize a module as part of the standard 394 | # compatibility libraries. 395 | known-standard-library= 396 | 397 | # Force import order to recognize a module as part of a third party library. 398 | known-third-party=enchant, absl 399 | 400 | # Analyse import fallback blocks. This can be used to support both Python 2 and 401 | # 3 compatible code, which means that the block might have code that exists 402 | # only in one or another interpreter, leading to false positives when analysed. 403 | analyse-fallback-blocks=no 404 | 405 | 406 | [CLASSES] 407 | 408 | # List of method names used to declare (i.e. assign) instance attributes. 409 | defining-attr-methods=__init__, 410 | __new__, 411 | setUp 412 | 413 | # List of member names, which should be excluded from the protected access 414 | # warning. 415 | exclude-protected=_asdict, 416 | _fields, 417 | _replace, 418 | _source, 419 | _make 420 | 421 | # List of valid names for the first argument in a class method. 422 | valid-classmethod-first-arg=cls, 423 | class_ 424 | 425 | # List of valid names for the first argument in a metaclass class method. 426 | valid-metaclass-classmethod-first-arg=mcs 427 | 428 | 429 | [EXCEPTIONS] 430 | 431 | # Exceptions that will emit a warning when being caught. Defaults to 432 | # "Exception" 433 | overgeneral-exceptions=StandardError, 434 | Exception, 435 | BaseException 436 | --------------------------------------------------------------------------------