Skip to content

Commit 590351d

Browse files
committed
Merge branch 'master' into issue135_forecastUncertainty_2
2 parents 3036dda + 0140bc0 commit 590351d

File tree

115 files changed

+442564
-408690
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

115 files changed

+442564
-408690
lines changed
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
name: Build, test and publish containers
2+
3+
on:
4+
pull_request:
5+
branches:
6+
- '*'
7+
push:
8+
branches:
9+
- '*'
10+
tags:
11+
- '*'
12+
13+
jobs:
14+
15+
simulation-tests:
16+
name: Build containers and run boptest simulation tests
17+
runs-on: ubuntu-22.04
18+
timeout-minutes: 240
19+
env:
20+
COMPOSE_PROJECT_NAME: boptest_service
21+
steps:
22+
- name: Checkout code
23+
uses: actions/checkout@v2
24+
25+
- name: Install Python
26+
uses: actions/setup-python@v2
27+
with:
28+
python-version: "3.9"
29+
30+
- name: Build and run stack
31+
run: |
32+
cd service
33+
docker compose build
34+
docker compose up -d web worker
35+
36+
- name: dump docker logs
37+
uses: jwalton/gh-docker-logs@v1
38+
39+
- name: Upload test cases to minio
40+
run: |
41+
# The provision script will wait for the web service to be available,
42+
# so there is no need for an external wait-for-it script
43+
cd service
44+
docker compose run --no-deps provision
45+
curl http://localhost/testcases
46+
47+
- name: Run example
48+
run: |
49+
python --version
50+
pip install requests matplotlib numpy pandas
51+
PYTHONPATH=$GITHUB_WORKSPACE
52+
echo "PYTHONPATH=$PYTHONPATH" >> $GITHUB_ENV
53+
# Storing PYTHONPATH above doesn't work for python so setting it below at run
54+
PYTHONPATH=$PWD python examples/python/testcase1.py
55+
56+
- name: Run tests
57+
run: |
58+
cd service
59+
docker compose run --no-deps test
60+
61+
- name: Dump docker logs on failure
62+
if: failure()
63+
uses: jwalton/gh-docker-logs@v1
64+
65+
- name: Log in to the GitHub container registry
66+
uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9
67+
with:
68+
registry: ghcr.io
69+
username: ${{ secrets.GH_USERNAME }}
70+
password: ${{ secrets.GH_REGISTRY }}
71+
72+
- name: Publish docker images to GitHub Registry
73+
if: |
74+
github.ref == 'refs/heads/develop' ||
75+
contains(github.ref, 'refs/tags')
76+
shell: bash
77+
run: service/ci/publish_to_github.sh
78+
79+
#------------------ Push to docker hub (disabled) -------------------------------------
80+
#
81+
# - name: Log in to Docker Hub
82+
# uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9
83+
# with:
84+
# username: ${{ secrets.DOCKER_USER }}
85+
# password: ${{ secrets.DOCKER_PASS }}
86+
#
87+
# - name: Publish docker images to Docker Hub
88+
# if: |
89+
# github.ref == 'refs/heads/develop' ||
90+
# github.ref == 'refs/heads/experimental' ||
91+
# contains(github.ref, 'refs/tags')
92+
# shell: bash
93+
# run: service/ci/publish_to_docker.sh

bacnet/create_ttl.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
baseurl = 'http://127.0.0.1:80'
1919

2020
# Instatiate test case
21-
test_case_name = 'bestest_air'
21+
test_case_name = 'multizone_office_simple_hydronic'
2222
testid = requests.post("{0}/testcases/{1}/select".format(baseurl, test_case_name)).json()["testid"]
2323

2424
# Write the file prefix

data/data_manager.py

Lines changed: 52 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,10 @@
99
1010
'''
1111

12-
import matplotlib.pyplot as plt
12+
1313
import pandas as pd
1414
import numpy as np
1515
import zipfile
16-
from scipy import interpolate
1716
import warnings
1817
import os
1918
import json
@@ -264,7 +263,7 @@ def save_data_and_jsons(self, fmu_path):
264263
self.z_fmu.close()
265264

266265
def get_data(self, horizon=24*3600, interval=None, index=None,
267-
variables=None, category=None, plot=False):
266+
variables=None, category=None):
268267
'''Retrieve test case data from the fmu. The data
269268
is stored within the csv files that are
270269
located in the resources folder of the test case fmu.
@@ -293,9 +292,7 @@ def get_data(self, horizon=24*3600, interval=None, index=None,
293292
The possible options are specified at categories.json.
294293
This argument cannot be used together with the `variables`
295294
argument.
296-
plot : Boolean, default is False
297-
True if desired to plot the retrieved data
298-
295+
299296
Returns
300297
-------
301298
data: dict
@@ -372,16 +369,6 @@ def get_data(self, horizon=24*3600, interval=None, index=None,
372369
# Add starting year back to index desired by user
373370
data_slice_reindexed.index = data_slice_reindexed.index + year_start
374371

375-
if plot:
376-
if category is None:
377-
to_plot = data_slice_reindexed.keys()
378-
else:
379-
to_plot = self.categories[category]
380-
for var in to_plot:
381-
data_slice_reindexed[var].plot()
382-
plt.legend()
383-
plt.show()
384-
385372
# Reset the index to keep the 'time' column in the data
386373
# Transform data frame to dictionary
387374
return data_slice_reindexed.reset_index().to_dict('list')
@@ -465,17 +452,16 @@ def load_data_and_jsons(self):
465452
for category in self.categories:
466453
# Use linear interpolation for continuous variables
467454
if any(col.startswith(key) for key in self.categories['weather']):
468-
g = interpolate.interp1d(df['time'],df[col],
469-
kind='linear')
470-
self.case.data.loc[:,col] = \
471-
g(self.case.data.index)
455+
456+
self.case.data.loc[:,col] = np.interp(self.case.data.index,\
457+
df['time'],df[col])
472458
# Use forward fill for discrete variables
473459
elif any(col.startswith(key) for key in self.categories[category]):
474-
g = interpolate.interp1d(df['time'],df[col],
475-
kind='zero')
476-
self.case.data.loc[:,col] = \
477-
g(self.case.data.index)
478-
else:
460+
461+
self.case.data.loc[:,col] = self.interp0(self.case.data.index,\
462+
df['time'].values,df[col].values)
463+
464+
else:
479465
warnings.warn('The following file does not have '\
480466
'time column and therefore no data is going to '\
481467
'be used from this file as test case data.', Warning)
@@ -549,14 +535,50 @@ def interpolate_data(self,df,index):
549535
for key in df.keys():
550536
# Use linear interpolation for continuous variables
551537
if key in self.categories['weather']:
552-
f = interpolate.interp1d(self.case.data.index,
553-
self.case.data[key], kind='linear')
538+
df.loc[:,key] = np.interp(index,self.case.data.index,
539+
self.case.data[key])
554540
# Use forward fill for discrete variables
555541
else:
556-
f = interpolate.interp1d(self.case.data.index,
557-
self.case.data[key], kind='zero')
558-
df.loc[:,key] = f(index)
542+
df.loc[:,key] = self.interp0(index,self.case.data.index.values,
543+
self.case.data[key].values)
559544
return df
545+
546+
def interp0(self,x, xp, yp):
547+
""" Zeroth order hold interpolation w/ same
548+
(base) signature as numpy.interp.
549+
Parameters
550+
----------
551+
x : np.array
552+
The x-coordinates at which to evaluate the interpolated values.
553+
554+
xp : np.array
555+
The x-coordinates of the data points, must be increasing.
556+
557+
yp : np.array
558+
The y-coordinates of the data points, same length as xp.
559+
560+
Returns
561+
-------
562+
y : np.array
563+
The interpolated values, same length as x.
564+
"""
565+
566+
def func(x0,k):
567+
if x0 <= xp[0]:
568+
return yp[0], k
569+
if x0 >= xp[-1]:
570+
return yp[-1], k
571+
572+
while x0 >= xp[k]:
573+
k += 1
574+
return yp[k-1], k
575+
k = 0
576+
y = list()
577+
for x0 in x:
578+
y0,k = func(x0,k)
579+
y.append(y0)
580+
return np.array(y)
581+
560582

561583
if __name__ == "__main__":
562584
import sys

forecast/forecaster.py

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,9 @@ def __init__(self, testcase, forecast_uncertainty_params_path='forecast/forecast
4040
# Load forecast uncertainty parameters
4141
self.uncertainty_params = self.load_uncertainty_params(forecast_uncertainty_params_path)
4242

43-
def get_forecast(self, point_names, horizon=24 * 3600, interval=3600,
44-
wea_tem_dry_bul=None, wea_sol_glo_hor=None, seed=None):
43+
def get_forecast(self, point_names, horizon=24*3600, interval=3600,
44+
wea_tem_dry_bul=None, wea_sol_glo_hor=None, seed=None,
45+
category=None):
4546
'''
4647
Retrieves forecast data for specified points over a given horizon and interval.
4748
@@ -50,9 +51,11 @@ def get_forecast(self, point_names, horizon=24 * 3600, interval=3600,
5051
point_names : list of str
5152
List of data point names for which the forecast is to be retrieved.
5253
horizon : int, optional
53-
Forecast horizon in seconds (default is 86400 seconds, i.e., one day).
54+
Forecast horizon in seconds.
55+
Default is 86400 seconds (24 hours).
5456
interval : int, optional
55-
Time interval between forecast points in seconds (default is 3600 seconds, i.e., one hour).
57+
Time interval between forecast points in seconds.
58+
Default is 3600 seconds (one hour).
5659
wea_tem_dry_bul : str, optional
5760
Uncertainty level for outside air dry bulb temperature. 'low', 'medium', or 'high'
5861
If None, defaults to no forecast error.
@@ -65,6 +68,12 @@ def get_forecast(self, point_names, horizon=24 * 3600, interval=3600,
6568
Seed for the random number generator to ensure reproducibility of the stochastic forecast error.
6669
If None, no seed is used.
6770
Default is None.
71+
category : string, optional
72+
Type of data to retrieve from the test case.
73+
If None it will return all available test case
74+
data without filtering it by any category.
75+
Possible options are 'weather', 'prices',
76+
'emissions', 'occupancy', internalGains, 'setpoints'.
6877
6978
Returns
7079
-------
@@ -74,13 +83,9 @@ def get_forecast(self, point_names, horizon=24 * 3600, interval=3600,
7483
'''
7584

7685
# Set uncertainty parameters to 0 if no forecast uncertainty
77-
temperature_params = {
78-
"F0": 0, "K0": 0, "F": 0, "K": 0, "mu": 0
79-
}
86+
temperature_params = {"F0": 0, "K0": 0, "F": 0, "K": 0, "mu": 0}
8087

81-
solar_params = {
82-
"ag0": 0, "bg0": 0, "phi": 0, "ag": 0, "bg": 0
83-
}
88+
solar_params = {"ag0": 0, "bg0": 0, "phi": 0, "ag": 0, "bg": 0}
8489

8590
if wea_tem_dry_bul is not None:
8691
temperature_params.update(self.uncertainty_params['temperature'][wea_tem_dry_bul])
@@ -91,7 +96,8 @@ def get_forecast(self, point_names, horizon=24 * 3600, interval=3600,
9196
# Get the forecast
9297
forecast = self.case.data_manager.get_data(variables=point_names,
9398
horizon=horizon,
94-
interval=interval)
99+
interval=interval,
100+
category=category)
95101

96102
# Add any outside dry bulb temperature error
97103
if 'TDryBul' in point_names and any(temperature_params.values()):

kpis/kpi_calculator.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111

1212
import numpy as np
1313
import pandas as pd
14-
from scipy.integrate import trapz
1514
from collections import OrderedDict
1615

1716
class KPI_Calculator(object):
@@ -252,9 +251,9 @@ def get_thermal_discomfort(self):
252251
dT_upper = data - UpperSetp
253252
dT_upper[dT_upper<0]=0
254253
self.tdis_dict[signal[:-1]+'dTlower_y'] += \
255-
trapz(dT_lower,self._get_data_from_last_index('time',self.i_last_tdis))/3600.
254+
np.trapezoid(dT_lower,self._get_data_from_last_index('time',self.i_last_tdis))/3600.
256255
self.tdis_dict[signal[:-1]+'dTupper_y'] += \
257-
trapz(dT_upper,self._get_data_from_last_index('time',self.i_last_tdis))/3600.
256+
np.trapezoid(dT_upper,self._get_data_from_last_index('time',self.i_last_tdis))/3600.
258257
self.tdis_tot = self.tdis_tot + \
259258
self.tdis_dict[signal[:-1]+'dTlower_y']/len(self.sources_tdis) + \
260259
self.tdis_dict[signal[:-1]+'dTupper_y']/len(self.sources_tdis) # Normalize total by number of sources
@@ -299,7 +298,7 @@ def get_iaq_discomfort(self):
299298
dI_upper = data - UpperSetp
300299
dI_upper[dI_upper<0]=0
301300
self.idis_dict[signal[:-1]+'dIupper_y'] += \
302-
trapz(dI_upper, self._get_data_from_last_index('time',self.i_last_idis))/3600.
301+
np.trapezoid(dI_upper, self._get_data_from_last_index('time',self.i_last_idis))/3600.
303302
self.idis_tot = self.idis_tot + \
304303
self.idis_dict[signal[:-1]+'dIupper_y']/len(self.sources_idis) # Normalize total by number of sources
305304

@@ -334,7 +333,7 @@ def get_energy(self):
334333
for signal in self.case.kpi_json[source]:
335334
pow_data = np.array(self._get_data_from_last_index(signal,self.i_last_ener))
336335
self.ener_dict[signal] += \
337-
trapz(pow_data,
336+
np.trapezoid(pow_data,
338337
self._get_data_from_last_index('time',self.i_last_ener))*2.77778e-7 # Convert to kWh
339338
self.ener_dict_by_source[source+'_'+signal] += \
340339
self.ener_dict[signal]
@@ -542,7 +541,7 @@ def get_cost(self, scenario='Constant'):
542541
for signal in self.case.kpi_json[source]:
543542
pow_data = np.array(self._get_data_from_last_index(signal,self.i_last_cost))
544543
self.cost_dict[signal] += \
545-
trapz(np.multiply(source_price_data,pow_data),
544+
np.trapezoid(np.multiply(source_price_data,pow_data),
546545
self._get_data_from_last_index('time',self.i_last_cost))*factor
547546
self.cost_dict_by_source[source+'_'+signal] += \
548547
self.cost_dict[signal]
@@ -586,7 +585,7 @@ def get_emissions(self):
586585
for signal in self.case.kpi_json[source]:
587586
pow_data = np.array(self._get_data_from_last_index(signal,self.i_last_emis))
588587
self.emis_dict[signal] += \
589-
trapz(np.multiply(source_emissions_data,pow_data),
588+
np.trapezoid(np.multiply(source_emissions_data,pow_data),
590589
self._get_data_from_last_index('time',self.i_last_emis))*2.77778e-7 # Convert to kWh
591590
self.emis_dict_by_source[source+'_'+signal] += \
592591
self.emis_dict[signal]

0 commit comments

Comments
 (0)