-
Notifications
You must be signed in to change notification settings - Fork 77
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Issue135 forecast uncertainty #482
Changes from 20 commits
eab2f75
79cbeb2
0dc3f89
f4093d1
407bc79
cac66b6
e66ea46
c74022e
9d33819
816358c
edeaf1c
0267d23
b5db17d
2597023
b6fb4a7
bc6f233
5eebe2b
32f727f
a433f7c
c29d489
a7560fb
d81a697
5c200c8
7fb0965
3ffc633
b1f38f1
8c29dbf
f503671
a4bc294
7e001b4
e229f4d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,3 +15,7 @@ testcase2/doc/build/ | |
parser/*.fmu | ||
parser/*.mo | ||
parser/*.json | ||
|
||
xx.ipynb | ||
connect_boptest.py | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't see a connect_boptest.py and don't think this should be written in the .gitignore. |
||
\! | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This shouldn't be in the .gitignore. |
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
''' | ||
Created on Sept 5, 2022 | ||
|
||
@author: Laura Zabala | ||
|
||
''' | ||
|
||
import numpy as np | ||
|
||
|
||
|
||
def mean_filter(data, window_size=3): | ||
""" | ||
Apply mean filter to a 1D list of data. | ||
|
||
Parameters: | ||
- data: List of numbers to be filtered. | ||
- window_size: Size of the filtering window. Must be an odd number. | ||
|
||
Returns: | ||
- Filtered data. | ||
""" | ||
if window_size % 2 == 0: | ||
raise ValueError("Window size must be an odd number.") | ||
|
||
half_window = window_size // 2 | ||
filtered_data = data.copy() | ||
|
||
for i in range(half_window, len(data) - half_window): | ||
if data[i] == 0: | ||
continue | ||
|
||
window_data = data[i - half_window : i + half_window + 1] | ||
filtered_data[i] = sum(window_data) / len(window_data) | ||
|
||
return filtered_data | ||
def predict_temperature_error_AR1(hp, F0, K0, F, K, mu): | ||
''' | ||
Generates an error for the temperature forecast with an AR model with normal distribution in the hp points of the predictions horizon. | ||
|
||
Parameters | ||
---------- | ||
hp : int | ||
Number of points in the prediction horizon. | ||
F0 : float | ||
Mean of the initial error model. | ||
K0 : float | ||
Standard deviation of the initial error model. | ||
F : float | ||
Autocorrelation factor of the AR error model, value should be between 0 and 1. | ||
K : float | ||
Standard deviation of the AR error model. | ||
mu : float | ||
Mean value of the distribution function integrated in the AR error model. | ||
|
||
|
||
Returns | ||
------- | ||
error : 1D array | ||
Array containing the error values in the hp points. | ||
''' | ||
|
||
|
||
error = np.zeros(hp) | ||
error[0] = np.random.normal(F0, K0) | ||
for i_c in range(hp - 1): | ||
error[i_c + 1] = np.random.normal( | ||
error[i_c] * F + mu, K | ||
) | ||
return error | ||
|
||
|
||
|
||
def predict_solar_error_AR1(hp, ag0, bg0, phi, ag, bg): | ||
'''Generates an error for the solar forecast based on the specified parameters using an AR model with Laplace distribution in the hp points of the predictions horizon. | ||
|
||
Parameters | ||
---------- | ||
hp : int | ||
Number of points in the prediction horizon. | ||
ag0, bg0, phi, ag, bg : float | ||
Parameters for the AR1 model. | ||
|
||
Returns | ||
------- | ||
error : 1D numpy array | ||
Contains the error values in the hp points. | ||
''' | ||
|
||
error = np.zeros(hp) | ||
error[0] = np.random.laplace(ag0, bg0) | ||
for i_c in range(1, hp): | ||
error[i_c] = np.random.laplace(error[i_c - 1] * phi + ag, bg) | ||
|
||
return error |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
{ | ||
"temperature": { | ||
"low": { | ||
"F0": 0, | ||
"K0": 0.6, | ||
"F": 0.92, | ||
"K": 0.4, | ||
"mu": 0 | ||
}, | ||
"medium": { | ||
"F0": 0.15, | ||
"K0": 1.2, | ||
"F": 0.93, | ||
"K": 0.6, | ||
"mu": 0 | ||
}, | ||
"high": { | ||
"F0": -0.58, | ||
"K0": 1.5, | ||
"F": 0.95, | ||
"K": 0.7, | ||
"mu": -0.015 | ||
} | ||
}, | ||
"solar": { | ||
"low": { | ||
"ag0": 4.44, | ||
"bg0": 57.42, | ||
"phi": 0.62, | ||
"ag": 1.86, | ||
"bg": 45.64 | ||
}, | ||
"medium": { | ||
"ag0": 15.02, | ||
"bg0": 122.6, | ||
"phi": 0.63, | ||
"ag": 4.44, | ||
"bg": 91.97 | ||
}, | ||
"high": { | ||
"ag0": 32.09, | ||
"bg0": 119.94, | ||
"phi": 0.67, | ||
"ag": 10.63, | ||
"bg": 87.44 | ||
} | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,9 @@ | |
of the test case to provide deterministic forecast. | ||
|
||
''' | ||
from .error_emulator import predict_temperature_error_AR1, predict_solar_error_AR1, mean_filter | ||
import numpy as np | ||
|
||
|
||
class Forecaster(object): | ||
'''This class retrieves test case data forecast for its use in | ||
|
@@ -30,45 +33,72 @@ def __init__(self, testcase): | |
# Point to the test case object | ||
self.case = testcase | ||
|
||
def get_forecast(self,point_names, horizon=24*3600, interval=3600, | ||
def get_forecast(self, point_names, horizon=24 * 3600, interval=3600, | ||
weather_temperature_dry_bulb=None, weather_solar_global_horizontal=None, seed=None, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I suggest shorter variable names, using three letters, such as: Also missing documentation of these parameters, which should be included as docstrings as in other functions, and was done previously for this one. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thank you for your suggestions regarding the variable names, the documentation and the issue of '.gitignore' file. I have updated the variable names to shorter versions as you suggested, updated the docstrings and corrected the '.gitignore'. I've created a pull request to merge these modifications into the issue135_forecastUncertainty branch of the forked repository at laura-zabala/project1-boptest. |
||
category=None, plot=False): | ||
'''Returns forecast of the test case data | ||
|
||
Parameters | ||
---------- | ||
point_names : list of str | ||
List of forecast point names for which to get data. | ||
horizon : int, default is 86400 (one day) | ||
Length of the requested forecast in seconds. If None, | ||
the test case horizon will be used instead. | ||
interval : int, default is 3600 (one hour) | ||
resampling time interval in seconds. If None, | ||
the test case interval will be used instead. | ||
category : string, default is None | ||
Type of data to retrieve from the test case. | ||
If None it will return all available test case | ||
data without filtering it by any category. | ||
Possible options are 'weather', 'prices', | ||
'emissions', 'occupancy', internalGains, 'setpoints' | ||
plot : boolean, default is False | ||
True if desired to plot the forecast | ||
|
||
Returns | ||
------- | ||
forecast : dict | ||
Dictionary with the requested forecast data | ||
{<variable_name>:<variable_forecast_trajectory>} | ||
where <variable_name> is a string with the variable | ||
key and <variable_forecast_trajectory> is a list with | ||
the forecasted values. 'time' is included as a variable | ||
|
||
''' | ||
if weather_temperature_dry_bulb is None: | ||
weather_temperature_dry_bulb = { | ||
"F0": 0, "K0": 0, "F": 0, "K": 0, "mu": 0 | ||
} | ||
|
||
if weather_solar_global_horizontal is None: | ||
weather_solar_global_horizontal = { | ||
"ag0": 0, "bg0": 0, "phi": 0, "ag": 0, "bg": 0 | ||
} | ||
# Get the forecast | ||
forecast = self.case.data_manager.get_data(variables=point_names, | ||
horizon=horizon, | ||
interval=interval, | ||
category=category, | ||
plot=plot) | ||
|
||
if 'TDryBul' in point_names and any(weather_temperature_dry_bulb.values()): | ||
if seed is not None: | ||
np.random.seed(seed) | ||
# error in the forecast | ||
error_forecast_temp = predict_temperature_error_AR1( | ||
hp=int(horizon / interval + 1), | ||
F0=weather_temperature_dry_bulb["F0"], | ||
K0=weather_temperature_dry_bulb["K0"], | ||
F=weather_temperature_dry_bulb["F"], | ||
K=weather_temperature_dry_bulb["K"], | ||
mu=weather_temperature_dry_bulb["mu"] | ||
) | ||
|
||
# forecast error just added to dry bulb temperature | ||
forecast['TDryBul'] = forecast['TDryBul'] - error_forecast_temp | ||
forecast['TDryBul'] = forecast['TDryBul'].tolist() | ||
if 'HGloHor' in point_names and any(weather_solar_global_horizontal.values()): | ||
|
||
original_HGloHor = np.array(forecast['HGloHor']).copy() | ||
lower_bound = 0.2 * original_HGloHor | ||
upper_bound = 2 * original_HGloHor | ||
indices = np.where(original_HGloHor > 50)[0] | ||
|
||
|
||
for i in range(200): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it ever the case 200 is not enough? What happens if condition not met after 200? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
if seed is not None: | ||
np.random.seed(seed+i*i) | ||
error_forecast_solar = predict_solar_error_AR1( | ||
int(horizon / interval + 1), | ||
weather_solar_global_horizontal["ag0"], | ||
weather_solar_global_horizontal["bg0"], | ||
weather_solar_global_horizontal["phi"], | ||
weather_solar_global_horizontal["ag"], | ||
weather_solar_global_horizontal["bg"] | ||
) | ||
|
||
forecast['HGloHor'] = original_HGloHor - error_forecast_solar | ||
|
||
# Check if any point in forecast['HGloHor'] is out of the specified range | ||
condition = np.any((forecast['HGloHor'][indices] > 2 * original_HGloHor[indices]) | | ||
(forecast['HGloHor'][indices] < 0.2 * original_HGloHor[indices])) | ||
# forecast['HGloHor']=gaussian_filter_ignoring_nans(forecast['HGloHor']) | ||
forecast['HGloHor'] = mean_filter(forecast['HGloHor']) | ||
forecast['HGloHor'] = np.clip(forecast['HGloHor'], lower_bound, upper_bound) | ||
forecast['HGloHor'] = forecast['HGloHor'].tolist() | ||
if not condition: | ||
break | ||
|
||
return forecast |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see why this should be in the .gitignore.