citylearn 2.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- citylearn-2.5.0/LICENSE +21 -0
- citylearn-2.5.0/MANIFEST.in +2 -0
- citylearn-2.5.0/PKG-INFO +48 -0
- citylearn-2.5.0/README.md +34 -0
- citylearn-2.5.0/citylearn/__init__.py +1 -0
- citylearn-2.5.0/citylearn/__main__.py +506 -0
- citylearn-2.5.0/citylearn/agents/__init__.py +0 -0
- citylearn-2.5.0/citylearn/agents/base.py +285 -0
- citylearn-2.5.0/citylearn/agents/marlisa.py +495 -0
- citylearn-2.5.0/citylearn/agents/q_learning.py +169 -0
- citylearn-2.5.0/citylearn/agents/rbc.py +515 -0
- citylearn-2.5.0/citylearn/agents/rlc.py +240 -0
- citylearn-2.5.0/citylearn/agents/sac.py +318 -0
- citylearn-2.5.0/citylearn/assets/building-0-charge-0.png +0 -0
- citylearn-2.5.0/citylearn/assets/building-0-charge-1.png +0 -0
- citylearn-2.5.0/citylearn/assets/building-0-charge-2.png +0 -0
- citylearn-2.5.0/citylearn/assets/building-0-charge-3.png +0 -0
- citylearn-2.5.0/citylearn/assets/building-1-charge-0.png +0 -0
- citylearn-2.5.0/citylearn/assets/building-1-charge-1.png +0 -0
- citylearn-2.5.0/citylearn/assets/building-1-charge-2.png +0 -0
- citylearn-2.5.0/citylearn/assets/building-1-charge-3.png +0 -0
- citylearn-2.5.0/citylearn/assets/glow.png +0 -0
- citylearn-2.5.0/citylearn/assets/grid.png +0 -0
- citylearn-2.5.0/citylearn/base.py +272 -0
- citylearn-2.5.0/citylearn/building.py +3353 -0
- citylearn-2.5.0/citylearn/citylearn.py +2720 -0
- citylearn-2.5.0/citylearn/cost_function.py +388 -0
- citylearn-2.5.0/citylearn/data.py +804 -0
- citylearn-2.5.0/citylearn/dynamics.py +130 -0
- citylearn-2.5.0/citylearn/electric_vehicle.py +162 -0
- citylearn-2.5.0/citylearn/electric_vehicle_charger.py +465 -0
- citylearn-2.5.0/citylearn/end_use_load_profiles/__init__.py +0 -0
- citylearn-2.5.0/citylearn/end_use_load_profiles/clustering.py +191 -0
- citylearn-2.5.0/citylearn/end_use_load_profiles/lstm_model/__init__.py +0 -0
- citylearn-2.5.0/citylearn/end_use_load_profiles/lstm_model/model.py +44 -0
- citylearn-2.5.0/citylearn/end_use_load_profiles/lstm_model/model_generation.py +214 -0
- citylearn-2.5.0/citylearn/end_use_load_profiles/lstm_model/model_generation_wrapper.py +40 -0
- citylearn-2.5.0/citylearn/end_use_load_profiles/lstm_model/preprocessing.py +142 -0
- citylearn-2.5.0/citylearn/end_use_load_profiles/neighborhood.py +830 -0
- citylearn-2.5.0/citylearn/end_use_load_profiles/simulate.py +231 -0
- citylearn-2.5.0/citylearn/energy_model.py +1398 -0
- citylearn-2.5.0/citylearn/misc/queries/create_zone_metadata.sql +26 -0
- citylearn-2.5.0/citylearn/misc/queries/select_citylearn_energy_simulation.sql +107 -0
- citylearn-2.5.0/citylearn/misc/queries/select_citylearn_weather.sql +12 -0
- citylearn-2.5.0/citylearn/misc/queries/select_ideal_loads.sql +24 -0
- citylearn-2.5.0/citylearn/misc/queries/select_lstm_training_data.sql +96 -0
- citylearn-2.5.0/citylearn/misc/queries/select_zone_conditioning_metadata.sql +47 -0
- citylearn-2.5.0/citylearn/misc/settings.yaml +354 -0
- citylearn-2.5.0/citylearn/occupant.py +99 -0
- citylearn-2.5.0/citylearn/power_outage.py +170 -0
- citylearn-2.5.0/citylearn/preprocessing.py +173 -0
- citylearn-2.5.0/citylearn/reward_function.py +523 -0
- citylearn-2.5.0/citylearn/rl.py +133 -0
- citylearn-2.5.0/citylearn/utilities.py +175 -0
- citylearn-2.5.0/citylearn/wrappers.py +857 -0
- citylearn-2.5.0/citylearn.egg-info/PKG-INFO +48 -0
- citylearn-2.5.0/citylearn.egg-info/SOURCES.txt +61 -0
- citylearn-2.5.0/citylearn.egg-info/dependency_links.txt +1 -0
- citylearn-2.5.0/citylearn.egg-info/entry_points.txt +2 -0
- citylearn-2.5.0/citylearn.egg-info/requires.txt +11 -0
- citylearn-2.5.0/citylearn.egg-info/top_level.txt +1 -0
- citylearn-2.5.0/setup.cfg +4 -0
- citylearn-2.5.0/setup.py +41 -0
citylearn-2.5.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2020 Jose Ramon Vazquez-Canteli, Intelligent Environments Laboratory
|
|
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.
|
citylearn-2.5.0/PKG-INFO
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: citylearn
|
|
3
|
+
Version: 2.5.0
|
|
4
|
+
Summary: An open source Farama Foundation Gymnasium environment for benchmarking distributed energy resource control algorithms to provide energy flexibility in a district of buildings.
|
|
5
|
+
Home-page: https://github.com/intelligent-environments-lab/CityLearn
|
|
6
|
+
Author: Jose Ramon Vazquez-Canteli, Kingsley Nweye, Zoltan Nagy
|
|
7
|
+
Author-email: nweye@utexas.edu
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.7.7
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
|
|
15
|
+
# CityLearn
|
|
16
|
+
CityLearn is an open source Farama Foundation Gymnasium environment for the implementation of Multi-Agent Reinforcement Learning (RL) for building energy coordination and demand response in cities. A major challenge for RL in demand response is the ability to compare algorithm performance. Thus, CityLearn facilitates and standardizes the evaluation of RL agents such that different algorithms can be easily compared with each other.
|
|
17
|
+
|
|
18
|
+

|
|
19
|
+
|
|
20
|
+
## Environment Overview
|
|
21
|
+
|
|
22
|
+
CityLearn includes energy models of buildings and distributed energy resources (DER) including air-to-water heat pumps, electric heaters and batteries. A collection of building energy models makes up a virtual district (a.k.a neighborhood or community). In each building, space cooling, space heating and domestic hot water end-use loads may be independently satisfied through air-to-water heat pumps. Alternatively, space heating and domestic hot water loads can be satisfied through electric heaters.
|
|
23
|
+
|
|
24
|
+

|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
Install latest release in PyPi with `pip`:
|
|
28
|
+
```console
|
|
29
|
+
pip install CityLearn
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Documentation
|
|
33
|
+
Refer to the [docs](https://intelligent-environments-lab.github.io/CityLearn/).
|
|
34
|
+
|
|
35
|
+
## CityLearn UI
|
|
36
|
+
|
|
37
|
+
CityLearn UI is a visual dashboard for exploring simulation data generated by the CityLearn framework. It was developed to simplify the analysis of results from smart energy communities, district energy coordination, demand response (among other applications), allowing users to visually inspect building-level components, compare simulation KPIs, and create simulation schemas with ease.
|
|
38
|
+
|
|
39
|
+
The interface is available in two options:
|
|
40
|
+
|
|
41
|
+
* Web app: https://citylearnui.netlify.app/ (free hosted version — not recommended for sensitive/personal data)
|
|
42
|
+
* Open-source code: https://github.com/Soft-CPS-Research-Group/citylearn-ui
|
|
43
|
+
|
|
44
|
+
You can check a tutorial at the official CityLearn [website](https://intelligent-environments-lab.github.io/CityLearn/ui.html), in the CityLearn UI repository [README](https://github.com/Soft-CPS-Research-Group/citylearn-ui), or at the help [tooltip of the oficial webapp](https://citylearn-ui.netlify.app/admin/help).
|
|
45
|
+
|
|
46
|
+
**Compatibility:** This version of the UI currently supports CityLearn v2.5.0 simulation data.
|
|
47
|
+
|
|
48
|
+
**Developed by:** José, a member of the [SoftCPS](https://www2.isep.ipp.pt/softcps/), Software for Cyber-Physical Systems research group (ISEP, Portugal) in collaboration with the Intelligent Environments Lab, University of Texas at Austin.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# CityLearn
|
|
2
|
+
CityLearn is an open source Farama Foundation Gymnasium environment for the implementation of Multi-Agent Reinforcement Learning (RL) for building energy coordination and demand response in cities. A major challenge for RL in demand response is the ability to compare algorithm performance. Thus, CityLearn facilitates and standardizes the evaluation of RL agents such that different algorithms can be easily compared with each other.
|
|
3
|
+
|
|
4
|
+

|
|
5
|
+
|
|
6
|
+
## Environment Overview
|
|
7
|
+
|
|
8
|
+
CityLearn includes energy models of buildings and distributed energy resources (DER) including air-to-water heat pumps, electric heaters and batteries. A collection of building energy models makes up a virtual district (a.k.a neighborhood or community). In each building, space cooling, space heating and domestic hot water end-use loads may be independently satisfied through air-to-water heat pumps. Alternatively, space heating and domestic hot water loads can be satisfied through electric heaters.
|
|
9
|
+
|
|
10
|
+

|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
Install latest release in PyPi with `pip`:
|
|
14
|
+
```console
|
|
15
|
+
pip install CityLearn
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Documentation
|
|
19
|
+
Refer to the [docs](https://intelligent-environments-lab.github.io/CityLearn/).
|
|
20
|
+
|
|
21
|
+
## CityLearn UI
|
|
22
|
+
|
|
23
|
+
CityLearn UI is a visual dashboard for exploring simulation data generated by the CityLearn framework. It was developed to simplify the analysis of results from smart energy communities, district energy coordination, demand response (among other applications), allowing users to visually inspect building-level components, compare simulation KPIs, and create simulation schemas with ease.
|
|
24
|
+
|
|
25
|
+
The interface is available in two options:
|
|
26
|
+
|
|
27
|
+
* Web app: https://citylearnui.netlify.app/ (free hosted version — not recommended for sensitive/personal data)
|
|
28
|
+
* Open-source code: https://github.com/Soft-CPS-Research-Group/citylearn-ui
|
|
29
|
+
|
|
30
|
+
You can check a tutorial at the official CityLearn [website](https://intelligent-environments-lab.github.io/CityLearn/ui.html), in the CityLearn UI repository [README](https://github.com/Soft-CPS-Research-Group/citylearn-ui), or at the help [tooltip of the oficial webapp](https://citylearn-ui.netlify.app/admin/help).
|
|
31
|
+
|
|
32
|
+
**Compatibility:** This version of the UI currently supports CityLearn v2.5.0 simulation data.
|
|
33
|
+
|
|
34
|
+
**Developed by:** José, a member of the [SoftCPS](https://www2.isep.ipp.pt/softcps/), Software for Cyber-Physical Systems research group (ISEP, Portugal) in collaboration with the Intelligent Environments Lab, University of Texas at Austin.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '2.5.0'
|
|
@@ -0,0 +1,506 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import concurrent.futures
|
|
3
|
+
import datetime
|
|
4
|
+
import getpass
|
|
5
|
+
import importlib
|
|
6
|
+
import inspect
|
|
7
|
+
import logging
|
|
8
|
+
from multiprocessing import cpu_count
|
|
9
|
+
import os
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
import shutil
|
|
12
|
+
import socket
|
|
13
|
+
import subprocess
|
|
14
|
+
import sys
|
|
15
|
+
from typing import Any, List, Mapping, Tuple, Union
|
|
16
|
+
import uuid
|
|
17
|
+
from citylearn.agents.base import Agent as CityLearnAgent
|
|
18
|
+
from citylearn.citylearn import CityLearnEnv
|
|
19
|
+
from citylearn.data import DataSet, get_settings
|
|
20
|
+
from citylearn.__init__ import __version__
|
|
21
|
+
from citylearn.utilities import FileHandler
|
|
22
|
+
import pandas as pd
|
|
23
|
+
import simplejson as json
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
from stable_baselines3.common.base_class import BaseAlgorithm as StableBaselines3Agent
|
|
27
|
+
|
|
28
|
+
except (ImportError, ModuleNotFoundError):
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
def run_work_order(work_order_filepath, max_workers=None, start_index=None, end_index=None, virtual_environment_path=None, windows_system=None):
|
|
32
|
+
work_order_filepath = Path(work_order_filepath)
|
|
33
|
+
|
|
34
|
+
if virtual_environment_path is not None:
|
|
35
|
+
if windows_system:
|
|
36
|
+
virtual_environment_command = f'"{os.path.join(virtual_environment_path, "Scripts", "Activate.ps1")}"'
|
|
37
|
+
|
|
38
|
+
else:
|
|
39
|
+
virtual_environment_command = f'source "{os.path.join(virtual_environment_path, "bin", "activate")}"'
|
|
40
|
+
|
|
41
|
+
else:
|
|
42
|
+
virtual_environment_command = 'echo "No virtual environment"'
|
|
43
|
+
|
|
44
|
+
with open(work_order_filepath,mode='r') as f:
|
|
45
|
+
args = f.read()
|
|
46
|
+
|
|
47
|
+
args = args.strip('\n').split('\n')
|
|
48
|
+
start_index = 0 if start_index is None else start_index
|
|
49
|
+
end_index = len(args) - 1 if end_index is None else end_index
|
|
50
|
+
assert start_index <= end_index, 'start_index must be <= end_index'
|
|
51
|
+
assert start_index < len(args), 'start_index must be < number of jobs'
|
|
52
|
+
args = args[start_index:end_index + 1]
|
|
53
|
+
args = [a for a in args if not a.startswith('#')]
|
|
54
|
+
args = [f'{virtual_environment_command} && {a}' for a in args]
|
|
55
|
+
max_workers = cpu_count() if max_workers is None else max_workers
|
|
56
|
+
|
|
57
|
+
with concurrent.futures.ProcessPoolExecutor(max_workers=max_workers) as executor:
|
|
58
|
+
logging.debug(f'Will use {max_workers} workers for job.')
|
|
59
|
+
logging.debug(f'Pooling {len(args)} jobs to run in parallel...')
|
|
60
|
+
results = [executor.submit(subprocess.run,**{'args':a, 'shell':True}) for a in args]
|
|
61
|
+
|
|
62
|
+
for future in concurrent.futures.as_completed(results):
|
|
63
|
+
try:
|
|
64
|
+
logging.debug(future.result())
|
|
65
|
+
|
|
66
|
+
except Exception as e:
|
|
67
|
+
logging.debug(e)
|
|
68
|
+
|
|
69
|
+
class Simulator:
|
|
70
|
+
def __init__(self, schema: str, agent_name: str = None, env_kwargs: Mapping[str, Any] = None, agent_kwargs: Mapping[str, Any] = None, wrappers: List[str] = None,
|
|
71
|
+
time_series_variables: List[str] = None, simulation_id: str = None, output_directory: Union[Path, str] = None, agent_filepath: Union[Path, str] = None,
|
|
72
|
+
random_seed: int = None, overwrite: bool = None
|
|
73
|
+
) -> None:
|
|
74
|
+
self.schema = schema
|
|
75
|
+
self.agent_name = agent_name
|
|
76
|
+
self.env_kwargs = env_kwargs
|
|
77
|
+
self.agent_kwargs = agent_kwargs
|
|
78
|
+
self.random_seed = random_seed
|
|
79
|
+
self.wrappers = wrappers
|
|
80
|
+
self.time_series_variables = time_series_variables
|
|
81
|
+
self.simulation_id = simulation_id
|
|
82
|
+
self.overwrite = overwrite
|
|
83
|
+
self.output_directory = output_directory
|
|
84
|
+
self.agent_filepath = agent_filepath
|
|
85
|
+
self.__reset()
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def schema(self) -> str:
|
|
89
|
+
return self.__schema
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def agent_name(self) -> str:
|
|
93
|
+
return self.__agent_name
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def env_kwargs(self) -> Mapping[str, Any]:
|
|
97
|
+
return self.__env_kwargs
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def agent_kwargs(self) -> Mapping[str, Any]:
|
|
101
|
+
return self.__agent_kwargs
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
def wrappers(self) -> List[str]:
|
|
105
|
+
return self.__wrappers
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def time_series_variables(self) -> List[str]:
|
|
109
|
+
return self.__time_series_variables
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def simulation_id(self) -> str:
|
|
113
|
+
return self.__simulation_id
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
def output_directory(self) -> Path:
|
|
117
|
+
return self.__output_directory
|
|
118
|
+
|
|
119
|
+
@property
|
|
120
|
+
def agent_filepath(self) -> Path:
|
|
121
|
+
return self.__agent_filepath
|
|
122
|
+
|
|
123
|
+
@property
|
|
124
|
+
def random_seed(self) -> int:
|
|
125
|
+
return self.__random_seed
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
def overwrite(self) -> bool:
|
|
129
|
+
return self.__overwrite
|
|
130
|
+
|
|
131
|
+
@schema.setter
|
|
132
|
+
def schema(self, value: str):
|
|
133
|
+
self.__schema = value
|
|
134
|
+
|
|
135
|
+
@agent_name.setter
|
|
136
|
+
def agent_name(self, value: str):
|
|
137
|
+
self.__agent_name = value
|
|
138
|
+
|
|
139
|
+
@env_kwargs.setter
|
|
140
|
+
def env_kwargs(self, value: Mapping[str, Any]):
|
|
141
|
+
self.__env_kwargs = {} if value is None else value
|
|
142
|
+
|
|
143
|
+
@agent_kwargs.setter
|
|
144
|
+
def agent_kwargs(self, value: Mapping[str, Any]):
|
|
145
|
+
self.__agent_kwargs = {} if value is None else value
|
|
146
|
+
|
|
147
|
+
@wrappers.setter
|
|
148
|
+
def wrappers(self, value:List[str]):
|
|
149
|
+
self.__wrappers = [] if value is None else value
|
|
150
|
+
|
|
151
|
+
@time_series_variables.setter
|
|
152
|
+
def time_series_variables(self, value: List[str]):
|
|
153
|
+
self.__time_series_variables = self.get_default_time_series_variables() if value is None else value
|
|
154
|
+
|
|
155
|
+
@simulation_id.setter
|
|
156
|
+
def simulation_id(self, value: str):
|
|
157
|
+
self.__simulation_id = f'citylearn-simulation-{uuid.uuid4().hex}' if value is None else value
|
|
158
|
+
|
|
159
|
+
@output_directory.setter
|
|
160
|
+
def output_directory(self, value: Path):
|
|
161
|
+
self.__output_directory = Path(os.path.join('citylearn_simulations', self.simulation_id))\
|
|
162
|
+
if value is None else value
|
|
163
|
+
|
|
164
|
+
if os.path.isdir(self.__output_directory) and self.overwrite:
|
|
165
|
+
shutil.rmtree(self.__output_directory)
|
|
166
|
+
|
|
167
|
+
else:
|
|
168
|
+
pass
|
|
169
|
+
|
|
170
|
+
os.makedirs(self.__output_directory, exist_ok=True)
|
|
171
|
+
|
|
172
|
+
@agent_filepath.setter
|
|
173
|
+
def agent_filepath(self, value: Path):
|
|
174
|
+
self.__agent_filepath = value if value is None else Path(value)
|
|
175
|
+
|
|
176
|
+
@random_seed.setter
|
|
177
|
+
def random_seed(self, value: int):
|
|
178
|
+
self.__random_seed = value
|
|
179
|
+
|
|
180
|
+
if self.random_seed is not None:
|
|
181
|
+
self.env_kwargs['random_seed'] = self.random_seed
|
|
182
|
+
|
|
183
|
+
if self.agent_name is not None:
|
|
184
|
+
random_seed_name = 'seed' if 'stable_baselines3' in self.agent_name else 'random_seed'
|
|
185
|
+
self.agent_kwargs[random_seed_name] = self.random_seed
|
|
186
|
+
|
|
187
|
+
else:
|
|
188
|
+
pass
|
|
189
|
+
|
|
190
|
+
else:
|
|
191
|
+
pass
|
|
192
|
+
|
|
193
|
+
@overwrite.setter
|
|
194
|
+
def overwrite(self, value: bool):
|
|
195
|
+
self.__overwrite = True if value is None else value
|
|
196
|
+
|
|
197
|
+
def __get_evaluation_summary(self):
|
|
198
|
+
return {
|
|
199
|
+
'evaluation_episode_time_steps': [
|
|
200
|
+
self.env.unwrapped.episode_tracker.episode_start_time_step,
|
|
201
|
+
self.env.unwrapped.episode_tracker.episode_end_time_step
|
|
202
|
+
],
|
|
203
|
+
'evaluation_start_timestamp': self.__evaluation_start_timestamp,
|
|
204
|
+
'evaluation_end_timestamp': self.__evaluation_end_timestamp,
|
|
205
|
+
'evaluation': self.env.unwrapped.evaluate().pivot(index='name', columns='cost_function', values='value').to_dict('index'),
|
|
206
|
+
'episode_reward_summary': self.env.unwrapped.episode_rewards[-1],
|
|
207
|
+
'episode_rewards': self.env.unwrapped.rewards,
|
|
208
|
+
'time_series': self.__get_time_series().to_dict('list'),
|
|
209
|
+
'actions': self.__actions_list,
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
def __get_time_series(self) -> pd.DataFrame:
|
|
213
|
+
data_list = []
|
|
214
|
+
|
|
215
|
+
for b in self.env.unwrapped.buildings:
|
|
216
|
+
data = {}
|
|
217
|
+
|
|
218
|
+
for variable in self.time_series_variables:
|
|
219
|
+
key = b
|
|
220
|
+
values = variable.split('.')
|
|
221
|
+
|
|
222
|
+
for i in range(len(values)):
|
|
223
|
+
if hasattr(key, values[i]):
|
|
224
|
+
value = getattr(key, values[i])
|
|
225
|
+
key = value
|
|
226
|
+
|
|
227
|
+
else:
|
|
228
|
+
pass
|
|
229
|
+
|
|
230
|
+
data[variable.replace('.', '_')] = value
|
|
231
|
+
|
|
232
|
+
data = pd.DataFrame(data)
|
|
233
|
+
data.insert(0, 'time_step', data.index)
|
|
234
|
+
data.insert(1, 'building_name', b.name)
|
|
235
|
+
data_list.append(data)
|
|
236
|
+
|
|
237
|
+
return pd.concat(data_list, ignore_index=True)
|
|
238
|
+
|
|
239
|
+
def __get_training_summary(self):
|
|
240
|
+
return {
|
|
241
|
+
'hostname': socket.gethostname(),
|
|
242
|
+
'username': getpass.getuser(),
|
|
243
|
+
'simulation_id': self.simulation_id,
|
|
244
|
+
'agent': self.agent.__class__.__name__,
|
|
245
|
+
'agent_kwargs': self.agent_kwargs,
|
|
246
|
+
'wrappers': self.wrappers,
|
|
247
|
+
'train_episodes': self.env.unwrapped.episode_tracker.episode,
|
|
248
|
+
'train_episode_time_steps': self.env.unwrapped.episode_time_steps,
|
|
249
|
+
'train_start_timestamp': self.__train_start_timestamp,
|
|
250
|
+
'train_end_timestamp': self.__train_end_timestamp,
|
|
251
|
+
'train_episode_reward_summary': self.env.unwrapped.episode_rewards,
|
|
252
|
+
'env_metadata': self.env.unwrapped.get_metadata(),
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
def __evaluate(self):
|
|
256
|
+
observations, _ = self.env.reset()
|
|
257
|
+
actions_list = []
|
|
258
|
+
self.__evaluation_start_timestamp = datetime.datetime.now(datetime.UTC)
|
|
259
|
+
|
|
260
|
+
while not self.env.terminated:
|
|
261
|
+
if isinstance(self.agent, CityLearnAgent):
|
|
262
|
+
actions = self.agent.predict(observations, deterministic=True)
|
|
263
|
+
actions_list.append(self.env.unwrapped._parse_actions(actions))
|
|
264
|
+
|
|
265
|
+
elif isinstance(self.agent, StableBaselines3Agent):
|
|
266
|
+
actions, _ = self.agent.predict(observations, deterministic=True)
|
|
267
|
+
actions_list.append(self.env.unwrapped._parse_actions([actions]))
|
|
268
|
+
|
|
269
|
+
else:
|
|
270
|
+
raise Exception(f'Unknown agent type: {type(self.agent)}')
|
|
271
|
+
|
|
272
|
+
observations, _, _, _, _ = self.env.step(actions)
|
|
273
|
+
|
|
274
|
+
self.__evaluation_end_timestamp = datetime.datetime.now(datetime.UTC)
|
|
275
|
+
self.__actions_list = actions_list
|
|
276
|
+
|
|
277
|
+
def __train(self, episodes: int):
|
|
278
|
+
kwargs = {}
|
|
279
|
+
self.__train_start_timestamp = datetime.datetime.now(datetime.UTC)
|
|
280
|
+
|
|
281
|
+
if isinstance(self.agent, CityLearnAgent):
|
|
282
|
+
kwargs = {**kwargs, 'episodes': episodes}
|
|
283
|
+
self.agent.learn(**kwargs)
|
|
284
|
+
|
|
285
|
+
else:
|
|
286
|
+
kwargs = {**kwargs, 'total_timesteps': episodes*self.env.unwrapped.time_steps}
|
|
287
|
+
self.agent = self.agent.learn(**kwargs)
|
|
288
|
+
|
|
289
|
+
self.__train_end_timestamp = datetime.datetime.now(datetime.UTC)
|
|
290
|
+
|
|
291
|
+
def __save_agent(self):
|
|
292
|
+
if isinstance(self.agent, CityLearnAgent):
|
|
293
|
+
filepath = os.path.join(self.output_directory, f'{self.simulation_id}-agent.pkl')
|
|
294
|
+
FileHandler.write_pickle(filepath, self.agent)
|
|
295
|
+
|
|
296
|
+
else:
|
|
297
|
+
filepath = os.path.join(self.output_directory, f'{self.simulation_id}-agent')
|
|
298
|
+
self.agent.save(filepath)
|
|
299
|
+
|
|
300
|
+
def __set_agent(self) -> Union[CityLearnAgent, StableBaselines3Agent]:
|
|
301
|
+
if self.agent_filepath is None:
|
|
302
|
+
agent = self.env.unwrapped.load_agent(
|
|
303
|
+
agent=self.agent_name,
|
|
304
|
+
env=self.env,
|
|
305
|
+
**self.agent_kwargs,
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
else:
|
|
309
|
+
if str(self.agent_filepath).endswith('.pkl'):
|
|
310
|
+
agent = FileHandler.read_pickle(self.agent_filepath)
|
|
311
|
+
agent.env = self.env
|
|
312
|
+
|
|
313
|
+
else:
|
|
314
|
+
agent = StableBaselines3Agent.load(self.agent_filepath, self.env)
|
|
315
|
+
|
|
316
|
+
return agent
|
|
317
|
+
|
|
318
|
+
def __set_env(self) -> CityLearnEnv:
|
|
319
|
+
env = CityLearnEnv(self.schema, **self.env_kwargs)
|
|
320
|
+
|
|
321
|
+
for wrapper in self.wrappers:
|
|
322
|
+
wrapper_module = '.'.join(wrapper.split('.')[0:-1])
|
|
323
|
+
wrapper_name = wrapper.split('.')[-1]
|
|
324
|
+
wrapper_constructor = getattr(importlib.import_module(wrapper_module), wrapper_name)
|
|
325
|
+
env = wrapper_constructor(env)
|
|
326
|
+
|
|
327
|
+
return env
|
|
328
|
+
|
|
329
|
+
def __reset(self):
|
|
330
|
+
self.env = self.__set_env()
|
|
331
|
+
self.agent = self.__set_agent()
|
|
332
|
+
self.__train_start_timestamp = None
|
|
333
|
+
self.__train_end_timestamp = None
|
|
334
|
+
self.__evaluation_start_timestamp = None
|
|
335
|
+
self.__evaluation_end_timestamp = None
|
|
336
|
+
self.__actions_list = None
|
|
337
|
+
|
|
338
|
+
@classmethod
|
|
339
|
+
def evaluate(cls, evaluation_episode_time_steps: Tuple[int, int] = None, **kwargs):
|
|
340
|
+
kwargs['env_kwargs'] = {} if kwargs.get('env_kwargs') is None else kwargs['env_kwargs']
|
|
341
|
+
|
|
342
|
+
if evaluation_episode_time_steps is not None:
|
|
343
|
+
kwargs['env_kwargs']['episode_time_steps'] = [evaluation_episode_time_steps]
|
|
344
|
+
|
|
345
|
+
else:
|
|
346
|
+
pass
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
simulator = cls(**kwargs)
|
|
350
|
+
simulator.__evaluate()
|
|
351
|
+
filepath = os.path.join(simulator.output_directory, f'{simulator.simulation_id}-evaluation.json')
|
|
352
|
+
FileHandler.write_json(filepath, simulator.__get_evaluation_summary())
|
|
353
|
+
|
|
354
|
+
@classmethod
|
|
355
|
+
def train(cls, episodes: int = None, evaluate: bool = None, evaluation_episode_time_steps: Tuple[int, int] = None, save_agent: bool = None, **kwargs):
|
|
356
|
+
simulator = cls(**kwargs)
|
|
357
|
+
episodes = 1 if episodes is None else episodes
|
|
358
|
+
simulator.__train(episodes)
|
|
359
|
+
filepath = os.path.join(simulator.output_directory, f'{simulator.simulation_id}-train.json')
|
|
360
|
+
FileHandler.write_json(filepath, simulator.__get_training_summary())
|
|
361
|
+
|
|
362
|
+
if save_agent:
|
|
363
|
+
simulator.__save_agent()
|
|
364
|
+
|
|
365
|
+
else:
|
|
366
|
+
pass
|
|
367
|
+
|
|
368
|
+
if evaluate:
|
|
369
|
+
evaluation_episode_time_steps = [[
|
|
370
|
+
simulator.env.unwrapped.episode_tracker.simulation_start_time_step,
|
|
371
|
+
simulator.env.unwrapped.episode_tracker.simulation_end_time_step
|
|
372
|
+
]] if evaluation_episode_time_steps is None else [evaluation_episode_time_steps]
|
|
373
|
+
kwargs['env_kwargs'] = {} if kwargs.get('env_kwargs') is None else kwargs['env_kwargs']
|
|
374
|
+
kwargs['overwrite'] = False
|
|
375
|
+
kwargs['output_directory'] = simulator.output_directory
|
|
376
|
+
kwargs['env_kwargs']['episode_time_steps'] = evaluation_episode_time_steps
|
|
377
|
+
kwargs['simulation_id'] = f'{simulator.simulation_id}'
|
|
378
|
+
simulator.evaluate(**kwargs)
|
|
379
|
+
|
|
380
|
+
else:
|
|
381
|
+
pass
|
|
382
|
+
|
|
383
|
+
@staticmethod
|
|
384
|
+
def get_default_time_series_variables():
|
|
385
|
+
return get_settings()['default_time_series_variables']
|
|
386
|
+
|
|
387
|
+
def main():
|
|
388
|
+
parser = argparse.ArgumentParser(
|
|
389
|
+
prog='citylearn',
|
|
390
|
+
formatter_class=argparse.ArgumentDefaultsHelpFormatter, description=(
|
|
391
|
+
'An open source Farama Foundation Gymnasium environment for benchmarking distributed energy resource '
|
|
392
|
+
'control algorithms to provide energy flexibility in a district of buildings. '
|
|
393
|
+
'Compatible with training and evaluating internally defined CityLearn agents in `citylearn.agents`, '
|
|
394
|
+
'user-defined agents that inherit from `citylearn.agents.base.Agent` and use the same interface as it, and agents '
|
|
395
|
+
'provided by stable-baselines3.'))
|
|
396
|
+
parser.add_argument('--version', action='version', version='%(prog)s' + f' {__version__}')
|
|
397
|
+
subparsers = parser.add_subparsers(title='subcommands', required=True, dest='subcommands')
|
|
398
|
+
|
|
399
|
+
# run many simulations in parallel
|
|
400
|
+
subparser_run_work_order = subparsers.add_parser(
|
|
401
|
+
'run_work_order',
|
|
402
|
+
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
403
|
+
help='Run commands in parallel. Useful for running many `citylearn simulate` commands in parallel.'
|
|
404
|
+
)
|
|
405
|
+
subparser_run_work_order.add_argument('work_order_filepath', type=Path, help=(
|
|
406
|
+
'Filepath to script containing list of commands to be run in parallel with each command defined on a new line.'))
|
|
407
|
+
subparser_run_work_order.add_argument('-w', '--max_workers', dest='max_workers', type=int, help=(
|
|
408
|
+
'Maximum number of commands to run at a time. Default is the number of CPUs.'))
|
|
409
|
+
subparser_run_work_order.add_argument('-is', '--start_index', default=0, dest='start_index', type=int, help=(
|
|
410
|
+
'Line index of first command to execute. Commands above this index are not executed. '
|
|
411
|
+
'The default is to execute from the first line.'))
|
|
412
|
+
subparser_run_work_order.add_argument('-ie', '--end_index', dest='end_index', type=int, help=(
|
|
413
|
+
'Line index of last command to execute. Commands below this index are not exectued. '
|
|
414
|
+
'The default is to execute till the last line.'))
|
|
415
|
+
subparser_run_work_order.set_defaults(func=run_work_order)
|
|
416
|
+
|
|
417
|
+
# get names of datasets
|
|
418
|
+
subparser_datasets = subparsers.add_parser(
|
|
419
|
+
'list_datasets',
|
|
420
|
+
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
421
|
+
help='Lists available dataset names that can be parsed as `schema` in `citylearn simulate schema`.'
|
|
422
|
+
)
|
|
423
|
+
subparser_datasets.set_defaults(func=DataSet().get_dataset_names)
|
|
424
|
+
|
|
425
|
+
# get default time series variables
|
|
426
|
+
subparser_time_series_variables = subparsers.add_parser(
|
|
427
|
+
'list_default_time_series_variables',
|
|
428
|
+
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
429
|
+
help='Lists the default time series variables that will be reported and saved in a `JSON` file post-evaluation.'
|
|
430
|
+
)
|
|
431
|
+
subparser_time_series_variables.set_defaults(func=Simulator.get_default_time_series_variables)
|
|
432
|
+
|
|
433
|
+
# run one simulation
|
|
434
|
+
subparser_simulate = subparsers.add_parser(
|
|
435
|
+
'simulate',
|
|
436
|
+
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
437
|
+
help='Train or evaluate a trained agent against an environment.'
|
|
438
|
+
)
|
|
439
|
+
subparser_simulate.add_argument('schema', type=str, help=(
|
|
440
|
+
'Name of CityLearn dataset or filepath to a schema. Call `citylearn list_datasets` to get list of valid dataset names.'))
|
|
441
|
+
subparser_simulate.add_argument('-a', '--agent_name', dest='agent_name', default='citylearn.agents.base.BaselineAgent', type=str, help=(
|
|
442
|
+
'Name path to agent. Currently only compatible with internally defined CityLearn agents in `citylearn.agents`, '
|
|
443
|
+
'user-defined agents that inherit from `citylearn.agents.base.Agent` and use the same interface as it, and agents '
|
|
444
|
+
'provided by stable-baselines3. To use stable-baselines3 agents, make sure to run `pip install stable-baselines3` '
|
|
445
|
+
'before using the `simulate command.`'))
|
|
446
|
+
subparser_simulate.add_argument('-ke', '--env_kwargs', dest='env_kwargs', type=json.loads, help=(
|
|
447
|
+
'Initialization parameters for`citylearn.citylearn.CityLearnEnv`.'))
|
|
448
|
+
subparser_simulate.add_argument('-ka', '--agent_kwargs', dest='agent_kwargs', type=json.loads, help=(
|
|
449
|
+
'Initialization parameters for agent class.'))
|
|
450
|
+
subparser_simulate.add_argument('-w', '--wrappers', dest='wrappers', type=str, nargs='+', help=(
|
|
451
|
+
'Name path to environment wrappers e.g., \'citylearn.wrappers.ClippedObservationWrapper\'.'))
|
|
452
|
+
subparser_simulate.add_argument('-tv', '--time_series_variables', dest='time_series_variables', type=str, nargs='+', help=(
|
|
453
|
+
'Names of building-level time series properties to be stored in the evaluation `JSON` post-evaluation. '
|
|
454
|
+
'Call `citylearn list_default_time_series_variables` to see the default variable in use.'))
|
|
455
|
+
subparser_simulate.add_argument('-sid', '--simulation_id', dest='simulation_id', type=str, help=(
|
|
456
|
+
'SImulation reference ID used in directory and file names.' ))
|
|
457
|
+
subparser_simulate.add_argument('-fa', '--agent_filepath', dest='agent_filepath', type=str, help=(
|
|
458
|
+
'Filepath to previously saved agent to use for training or evaluation.'))
|
|
459
|
+
subparser_simulate.add_argument('-d', '--output_directory', dest='output_directory', type=str, help=(
|
|
460
|
+
'Directory to save all simulation output to.'))
|
|
461
|
+
subparser_simulate.add_argument('-te', '--evaluation_episode_time_steps', dest='evaluation_episode_time_steps', type=int, nargs=2,
|
|
462
|
+
action='append', help=('Start and end time steps in data set to evaluate on otherwise, the agent is evaluated on entire dataset.'))
|
|
463
|
+
subparser_simulate.add_argument('-p', '--append', dest='overwrite', action='store_false', help=(
|
|
464
|
+
'Add to output for existing simulation with `simulation_id` i.e. do not overwrite.'))
|
|
465
|
+
subparser_simulate.add_argument('-rs', '--random_seed', dest='random_seed', type=int, help=(
|
|
466
|
+
'Random seed used during environment and agent initialization.'))
|
|
467
|
+
simulation_subparsers = subparser_simulate.add_subparsers(title='simulate subcommands', required=True, dest='subcommands')
|
|
468
|
+
|
|
469
|
+
# -> train an agent
|
|
470
|
+
subparser_train = simulation_subparsers.add_parser(
|
|
471
|
+
'train',
|
|
472
|
+
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
473
|
+
help='Train an agent.'
|
|
474
|
+
)
|
|
475
|
+
subparser_train.add_argument('-e', '--episodes', dest='episodes', type=int, help='Number of training episodes/epochs.')
|
|
476
|
+
subparser_train.add_argument('--save_agent', dest='save_agent', action='store_true', help='Whether to save agent to disk at the end of training.')
|
|
477
|
+
subparser_train.add_argument('--evaluate', dest='evaluate', action='store_true', help=(
|
|
478
|
+
'Whether to run deterministic evaluation for one episode at the end of training.'))
|
|
479
|
+
subparser_train.set_defaults(func=Simulator.train)
|
|
480
|
+
|
|
481
|
+
# -> evaluate a trained agent
|
|
482
|
+
subparser_evaluate = simulation_subparsers.add_parser(
|
|
483
|
+
'evaluate',
|
|
484
|
+
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
485
|
+
help='Deterministically evaluate an agent.'
|
|
486
|
+
)
|
|
487
|
+
subparser_evaluate.set_defaults(func=Simulator.evaluate)
|
|
488
|
+
|
|
489
|
+
argv = sys.argv
|
|
490
|
+
argv = ['sphinx-build' in a for a in argv]
|
|
491
|
+
sphinx = any(argv)
|
|
492
|
+
|
|
493
|
+
if not sphinx:
|
|
494
|
+
args = parser.parse_args()
|
|
495
|
+
arg_spec = inspect.getfullargspec(args.func)
|
|
496
|
+
kwargs = {key:value for (key, value) in args._get_kwargs()
|
|
497
|
+
if (key in arg_spec.args or (arg_spec.varkw is not None and key not in ['func','subcommands']))
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
return args.func(**kwargs)
|
|
501
|
+
|
|
502
|
+
else:
|
|
503
|
+
return parser
|
|
504
|
+
|
|
505
|
+
if __name__ == '__main__':
|
|
506
|
+
sys.exit(main())
|
|
File without changes
|