Commit 05bae9e6 authored by hazrmard's avatar hazrmard
Browse files

controller script functional

parent 06645404
Loading
Loading
Loading
Loading
+36 −29
Changes for src/Baseline-Condenser.ipynb: 36 added lines, 29 removed lines.
Original line number Diff line number Diff line
%% Cell type:code id: tags:

``` python
%matplotlib inline
%reload_ext autoreload
%autoreload 2

import datetime
import sys
from os import path, environ
import pickle
import warnings

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.neural_network import MLPRegressor
from tqdm.auto import tqdm, trange

from utils import contiguous_sequences
from plotting import model_surface, plot_surface
from systems import Condenser
from baseline_control import SimpleFeedbackController, FeedbackController

chiller_file_1 = path.join(environ['DATADIR'],
                         'EngineeringScienceBuilding',
                         '2422_ESB_HVAC_1.csv')
chiller_file_2 = path.join(environ['DATADIR'],
                         'EngineeringScienceBuilding',
                         '2841_ESB_HVAC_2.csv')

plot_path = path.join('..', 'docs', 'img')
bin_path = './bin/'
```

%% Cell type:code id: tags:

``` python
# Choosing which chiller to use
chiller_file = chiller_file_2
# Data selection 'all' or 'chiller_on' or 'fan_on'
MODE = 'chiller_on'
# Read pre-processed data:
# Pytorch uses float32 as default type for weights etc,
# so input data points are also read in the same type.
df = pd.read_csv(chiller_file, index_col='time',
                 parse_dates=['time'], dtype=np.float32)
print('Original length: {} Records'.format(len(df)))
# These fields were not populated until 2020-07-01, so leaving then out of analysis
df.drop(['PowFanA', 'PowFanB', 'FlowCond', 'PowChiP', 'PerFreqConP', 'PowConP'], axis='columns', inplace=True)
# # These fields were not populated until 2020-07-01, so leaving then out of analysis
# df.drop(['PowFanA', 'PowFanB', 'FlowCond', 'PowChiP', 'PerFreqConP', 'PowConP'], axis='columns', inplace=True)
df.drop(['FlowCond', 'PowChiP', 'PerFreqConP'], axis='columns', inplace=True)

df.dropna(inplace=True)
if MODE == 'chiller_on':
    df = df[df['RunChi'] != 0]
if MODE == 'fan_on':
    df = df[(df['RunFanA'] != 0.) | df['RunFanB'] != 0.]
print('Processed length: {} Records'.format(len(df)))
```

%% Cell type:markdown id: tags:

## Environment model

State variables (9):
State variables (12):

`'TempCondIn', 'TempCondOut', 'TempEvapOut', 'PowChi', 'TempEvapIn', 'TempAmbient', 'TempWetBulb', 'PressDiffEvap', 'PressDiffCond'`
`'TempCondIn', 'TempCondOut', 'TempEvapOut', 'PowChi', 'PowFanA', 'PowFanB', 'PowConP', 'TempEvapIn', 'TempAmbient', 'TempWetBulb', 'PressDiffEvap', 'PressDiffCond'`

Action variables (1):

`'TempCondInSetpoint'`

Output variables (3):

`'PowChi', 'TempCondOut', 'TempCondIn'`
`'TempCondIn', 'TempCondOut', 'TempEvapOut', 'PowChi', 'PowFanA', 'PowFanB', 'PowConP'`

Model:

`[Action, State] --> [Output]`

%% Cell type:code id: tags:

``` python
# load model
with open(path.join(bin_path, 'v2_condenser'), 'rb') as f:
    save = pickle.load(f, fix_imports=False)
    est_cond = save['estimator']
    std_out_cond = save['output_norm']
    statevars = save['statevars']
    actionvars = save['actionvars']
    inputs = save['inputs']
    outputs = save['outputs']
    lag = save['lag']
```

%% Cell type:markdown id: tags:

### Condenser Data

%% Cell type:code id: tags:

``` python
statevars = ['TempCondIn', 'TempCondOut', 'TempEvapOut', 'PowChi', 'TempEvapIn', 'TempAmbient', 'TempWetBulb', 'PressDiffEvap', 'PressDiffCond']
actionvars = ['TempCondInSetpoint']
inputs =  actionvars + statevars
outputs = ['PowChi', 'TempCondOut', 'TempCondIn', 'TempEvapOut']
lag = (1, 1, 1, 1)    # 0, 1, 2, 3, 4, ...

df_in = pd.DataFrame(columns=inputs, index=df.index)
df_in['TempCondInSetpoint'] = np.clip(df['TempWetBulb'] - 4, a_min=65, a_max=None)  # approach controller
df_in[inputs[1:]] = df[inputs[1:]]

df_out = pd.DataFrame(columns=outputs, index=df.index)
df_out[outputs] = df[outputs]

idx_list = contiguous_sequences(df.index, pd.Timedelta(5, unit='min'), filter_min=10)

# Create dataframes of contiguous sequences with a delay
# of 1 time unit to indicate causality input -> outputs
dfs_in, dfs_out = [], []
for idx in idx_list:
    dfs_in.append(df_in.loc[idx[:-max(lag) if max(lag) > 0 else None]])
    cols = []
    for l, c in zip(lag, outputs):
        window = slice(l, None if l==max(lag) else -(max(lag)-l))
        series = df_out[c].loc[idx[window]]
        cols.append(series.values)
        if l == min(lag): index = series.index
    dfs_out.append(pd.DataFrame(np.asarray(cols).T, index=index, columns=outputs))

df_in = pd.concat(dfs_in, sort=False)
df_out = pd.concat(dfs_out, sort=False)

print('{:6d} time series'.format(len(dfs_in)))
print('{:6d} total rows'.format(len(df_in)))
```

%% Cell type:code id: tags:

``` python
# load model
with open(path.join(bin_path, 'v2_condenser'), 'rb') as f:
    save = pickle.load(f, fix_imports=False)
    est_cond = save['estimator']
    std_out_cond = save['output_norm']
```

%% Cell type:markdown id: tags:

## RL Environment

%% Cell type:code id: tags:

``` python
# Make wrapper for cooling tower such that outputs are normalized
# i.e. in physical units instead of being 0 mean and 1 variance.

externalvars = ('TempEvapIn', 'TempAmbient', 'TempWetBulb', 'PressDiffEvap', 'PressDiffCond')
externalvals = [df.loc[:, externalvars] for df in dfs_in]

class InvTransformer:

    def __init__(self, estimator, transformer):
        self.estimator = estimator
        self.transformer = transformer

    def predict(self, x):
        return self.transformer.inverse_transform(self.estimator.predict(x))


esb = Condenser(InvTransformer(est_cond, std_out_cond), externalvals)
```

%% Cell type:code id: tags:

``` python
# Visualize environment episode
done = False
states = []
power = []
esb.reset()
while not done:
    state, _, done, info = esb.step(esb.action_space.sample())
    states.append(state)
    power.append(info.get('powchi'))
esb.reset()

states = np.asarray(states)
power = np.asarray(power)
plt.subplot(2,1,1)
plt.plot(power, label='Total Power')
plt.legend()
plt.subplot(2,1,2)
plt.plot(states[:, 0], label='TempCondIn')
plt.plot(states[:, 1], label='TempCondOut')
plt.plot(states[:, 2], label='TempEvapOut')
plt.plot(states[:, 4], label='TempEvapIn')
plt.plot(states[:, 5], label='TempAmbient')
plt.plot(states[:, 6], label='TempWetBulb')
plt.legend()
plt.show()
```

%% Cell type:markdown id: tags:

## Simple Feedback Control

%% Cell type:code id: tags:

``` python
longest_seq_idx = max(range(20, 40), key= lambda i: len(dfs_in[i]))
longest_seq_idx = max(range(len(dfs_in)), key= lambda i: len(dfs_in[i]))
```

%% Cell type:code id: tags:

``` python
# dfs_in[longest_seq_idx]
dfs_in[34]
dfs_in[longest_seq_idx]
```

%% Cell type:code id: tags:

``` python
# seqidx = np.random.randint(len(dfs_in))
seqidx = 34
seqidx = longest_seq_idx
simulate_hist = True  # Whether to use raw output data, or simulate it through historical actions

# indexing histories after 1st element because simulated trajectories
# are recorded after initial state (> 0), so lengths are equal
act_hist = dfs_in[seqidx].loc[:, ('TempCondInSetpoint')].values[1:, None]
ext = dfs_in[seqidx].loc[:, externalvars]

# Get baseline by running historic actions through environment:
if simulate_hist:
    esb.reset(external=ext, state0=dfs_in[seqidx].iloc[0, 1:].values)
    done = False
    pow_hist_chi, pow_hist_fan, temp_hist = [], [], []
    t = 0
    while not done:
        action = act_hist[t, :1]
        _, _, done, info = esb.step(action)
        # pow_hist_fan.append(info.get('powfans'))
        pow_hist_chi.append(info.get('powchi'))
        temp_hist.append(info.get('tempcondin'))
        t += 1
else:
    pow_hist_chi = dfs_out[seqidx]['PowChi'].values
    pow_hist_fans = dfs_out[seqidx]['PowFans'].values
    temp_hist = dfs_out[seqidx]['TempCondIn'].values
```

%% Cell type:code id: tags:

``` python
# define agent
class Controller1(SimpleFeedbackController):

    def feedback(self, X):
        return -X[3]  # PowChi
        return -sum(X[3:7])  # PowChi, PowFanA, PowFanB, PowConP
        # return -X[0]

    def starting_action(self, X):
        return np.asarray([X[6] + 4]) # TempWetBulb
        return np.asarray([X[9] + 4]) # TempWetBulb

    def clip_action(self, u, X):
        u = super().clip_action(u, X)
        return np.clip(u, a_min=X[9], a_max=None)

class Controller2(FeedbackController):

    def feedback(self, X):
        return -X[3]  # PowChi

    def starting_action(self, X):
        return None
        # return X[6] + 4 # TempWetBulb
        # return X[9] + 4 # TempWetBulb



agent_fn = lambda: Controller1(bounds=((55., 90.),), stepsize=1)
agent_fn = lambda: Controller1(bounds=((60., 80.),), stepsize=1)
# agent_fn = lambda: Controller2(bounds=((55., 90.),), kp=1., ki=0.2, kd=0.)
```

%% Cell type:code id: tags:

``` python
pfan, pchi, act, rewards, temp = [], [], [], [], []

# run multiple trials over same period for stochastic policy
for trial in trange(1, leave=False):
    state = esb.reset(external=ext, state0=dfs_in[seqidx].iloc[0, 1:].values)
    agent = agent_fn()
    done = False
    pfan.append([])
    pchi.append([])
    act.append([])
    rewards.append([])
    temp.append([])
    while not done:
        action = agent.predict(state)[0]
        state, reward, done, info = esb.step(action)
        act[-1].append(action)
        # pfan[-1].append(info.get('powfans'))
        pchi[-1].append(info.get('powchi'))
        rewards[-1].append(reward)
        temp[-1].append(info.get('tempcondin'))

# get std_dev and mean of metrics
# std_pfan = np.std(pfan, axis=0, keepdims=False)
std_pchi = np.std(pchi, axis=0, keepdims=False)
std_act = np.std(act, axis=0, keepdims=False)
std_rewards = np.std(rewards, axis=0, keepdims=False)
std_temp = np.std(temp, axis=0, keepdims=False)

# pfan = np.mean(pfan, axis=0, keepdims=False)
pchi = np.mean(pchi, axis=0, keepdims=False)
act = np.mean(act, axis=0, keepdims=False)
rewards = np.mean(rewards, axis=0, keepdims=False)
temp = np.mean(temp, axis=0, keepdims=False)
```

%% Cell type:code id: tags:

``` python
plt.figure(figsize=(8,12))
# plt.subplot(4,1,1)
# plt.title('Fan Power (Average RL {:.0f}W vs Historical {:.0f}W)'\
#           .format(np.mean(pfan), np.mean(pow_hist_fan)))
# plt.plot(pfan, 'b:', label='RL.Fan')
# plt.fill_between(np.arange(len(pfan)), pfan+std_pfan, pfan-std_pfan, color='b', alpha=0.3)
# plt.plot(pow_hist_fan, 'r:', label='Historical.Fan')
# plt.ylim(bottom=0)
# plt.legend()

plt.subplot(3,1,1)
plt.title('Chiller Power (Average {:.0f}kW vs Historical {:.0f}kW)'\
          .format(np.mean(pchi), np.mean(pow_hist_chi)))
plt.plot(pchi, 'b:', label='Chiller')
plt.fill_between(np.arange(len(pchi)), pchi+std_pchi, pchi-std_pchi, color='b', alpha=0.3)
plt.plot(pow_hist_chi, 'r:', label='Historical.Chiller')
plt.ylim(bottom=0)
plt.legend()

plt.subplot(3,1,2)
plt.title('Setpoint Control (Average {:.2f} vs Historical {:.2f})'\
          .format(np.mean(act[:, 0]), np.mean(act_hist[:, 0])))
plt.plot(ext['TempAmbient'].values, 'g.', label='TempAmbient')
plt.plot(ext['TempWetBulb'].values, 'c.', label='TempWetBulb')
plt.plot(act[:, 0], 'b:', label='Setpoint')
plt.fill_between(np.arange(len(act[:, 0])), act[:, 0]+std_act[:, 0], act[:, 0]-std_act[:, 0], color='b', alpha=0.3)
plt.plot(act_hist[:, 0], 'r:', label='Historical.Setpoint')
# plt.ylim(top=1.05)
plt.legend()


plt.subplot(3,1,3)
plt.title('Output Temperature (Average {:.1f}F vs Historical {:.1f}F)'\
          .format(np.mean(temp), np.mean(temp_hist)))
plt.plot(temp, 'b:', label='Temp')
plt.fill_between(np.arange(len(temp)), temp+std_temp, temp-std_temp, color='b', alpha=0.3)
plt.plot(temp_hist, 'r:', label='Historical.Temp')
plt.legend()

plt.tight_layout()
```

%% Cell type:code id: tags:

``` python
plt.figure(figsize=(8,3))
plt.plot(dfs_in[seqidx]['TempAmbient'].values, label='Ambient Temp')
plt.plot(dfs_in[seqidx]['TempWetBulb'].values, label='WetBulb Temp')
plt.legend()
plt.ylabel('Temperature /F')
plt.title('Environmental Conditions')
plt.tight_layout()
plt.show()
```
+43 −24
Changes for src/Models-v2.ipynb: 43 added lines, 24 removed lines.
Original line number Diff line number Diff line
%% Cell type:markdown id: tags:

Uses `v2` dataset.

%% Cell type:code id: tags:

``` python
%matplotlib notebook
%reload_ext autoreload
%autoreload 2

import datetime
import sys
from os import path, environ
import pickle
import warnings

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.neural_network import MLPRegressor
from tqdm.auto import tqdm, trange
from bdx import get_trend

from utils import contiguous_sequences
from plotting import model_surface, plot_surface
from systems import Condenser
from baseline_control import SimpleFeedbackController

chiller_file_1 = path.join(environ['DATADIR'],
                         'EngineeringScienceBuilding',
                         '2422_ESB_HVAC_1.csv')
chiller_file_2 = path.join(environ['DATADIR'],
                         'EngineeringScienceBuilding',
                         '2841_ESB_HVAC_2.csv')

plot_path = path.join('..', 'docs', 'img')
bin_path = './bin/'
```

%% Cell type:code id: tags:

``` python
# Choosing which chiller to use
chiller_file = chiller_file_2
# Data selection 'all' or 'chiller_on' or 'fan_on'
MODE = 'chiller_on'
# Read pre-processed data:
# Pytorch uses float32 as default type for weights etc,
# so input data points are also read in the same type.
df = pd.read_csv(chiller_file, index_col='time',
                 parse_dates=['time'], dtype=np.float32)
print('Original length: {} Records'.format(len(df)))
# These fields were not populated until 2020-07-01, so leaving then out of analysis
df.drop(['PowFanA', 'PowFanB', 'FlowCond', 'PowChiP', 'PerFreqConP', 'PowConP'], axis='columns', inplace=True)
# # These fields were not populated until 2020-07-01, so leaving then out of analysis
# df.drop(['PowFanA', 'PowFanB', 'FlowCond', 'PowChiP', 'PerFreqConP', 'PowConP'], axis='columns', inplace=True)
df.drop(['FlowCond', 'PowChiP', 'PerFreqConP'], axis='columns', inplace=True)

df.dropna(inplace=True)
if MODE == 'chiller_on':
    df = df[df['RunChi'] != 0]
if MODE == 'fan_on':
    df = df[(df['RunFanA'] != 0.) | df['RunFanB'] != 0.]
print('Processed length: {} Records'.format(len(df)))
```

%% Cell type:code id: tags:

``` python
df.describe()
```

%% Cell type:markdown id: tags:

## Condenser model

State variables (9):
State variables (12):

`'TempCondIn', 'TempCondOut', 'TempEvapOut', 'PowChi', 'TempEvapIn', 'TempAmbient', 'TempWetBulb', 'PressDiffEvap', 'PressDiffCond'`
`'TempCondIn', 'TempCondOut', 'TempEvapOut', 'PowChi', 'PowFanA', 'PowFanB', 'PowConP', 'TempEvapIn', 'TempAmbient', 'TempWetBulb', 'PressDiffEvap', 'PressDiffCond'`

Action variables (1):

`'TempCondInSetpoint'`

Output variables (3):

`'PowChi', 'TempCondOut', 'TempCondIn', 'TempEvapOut'`
`'TempCondIn', 'TempCondOut', 'TempEvapOut', 'PowChi', 'PowFanA', 'PowFanB', 'PowConP'`

Model:

`[Action, State] --> [Output]`

%% Cell type:code id: tags:

``` python
statevars = ['TempCondIn', 'TempCondOut', 'TempEvapOut', 'PowChi', 'TempEvapIn', 'TempAmbient', 'TempWetBulb', 'PressDiffEvap', 'PressDiffCond']
actionvars = ['TempCondInSetpoint']
statevars = ['TempCondIn', 'TempCondOut', 'TempEvapOut',
             'PowChi', 'PowFanA', 'PowFanB', 'PowConP',
             'TempEvapIn', 'TempAmbient', 'TempWetBulb', 'PressDiffEvap', 'PressDiffCond']
inputs =  actionvars + statevars
outputs = ['PowChi', 'TempCondOut', 'TempCondIn', 'TempEvapOut']
lag = (1, 1, 1, 1)    # 0, 1, 2, 3, 4, ...
outputs = ['TempCondIn', 'TempCondOut', 'TempEvapOut',
           'PowChi', 'PowFanA', 'PowFanB', 'PowConP']
lag = (1, 1, 1, 1, 1, 1, 1)    # 0, 1, 2, 3, 4, ...

df_in = pd.DataFrame(columns=inputs, index=df.index)
df_in['TempCondInSetpoint'] = np.clip(df['TempWetBulb'] - 4, a_min=65, a_max=None)  # approach controller
df_in[inputs[1:]] = df[inputs[1:]]

df_out = pd.DataFrame(columns=outputs, index=df.index)
df_out[outputs] = df[outputs]

idx_list = contiguous_sequences(df.index, pd.Timedelta(5, unit='min'), filter_min=10)

# Create dataframes of contiguous sequences with a delay
# of 1 time unit to indicate causality input -> outputs
dfs_in, dfs_out = [], []
for idx in idx_list:
    dfs_in.append(df_in.loc[idx[:-max(lag) if max(lag) > 0 else None]])
    cols = []
    for l, c in zip(lag, outputs):
        window = slice(l, None if l==max(lag) else -(max(lag)-l))
        series = df_out[c].loc[idx[window]]
        cols.append(series.values)
        if l == min(lag): index = series.index
    dfs_out.append(pd.DataFrame(np.asarray(cols).T, index=index, columns=outputs))

df_in = pd.concat(dfs_in, sort=False)
df_out = pd.concat(dfs_out, sort=False)

print('{:6d} time series'.format(len(dfs_in)))
print('{:6d} total rows'.format(len(df_in)))
```

%% Cell type:code id: tags:

``` python
std_in_cond, std_out_cond = StandardScaler(), StandardScaler()
net = MLPRegressor(hidden_layer_sizes=(64, 32, 16),
                   activation='tanh',
net = MLPRegressor(hidden_layer_sizes=(64, 64, 32),
                   activation='relu',
                   solver='adam',
                   verbose=True,
                   early_stopping=True,
                   learning_rate_init=1e-3)
                   learning_rate_init=1e-4)
est_cond = Pipeline([('std', std_in_cond), ('net', net)])

with warnings.catch_warnings():
    warnings.simplefilter('ignore', category=FutureWarning)
    est_cond.fit(df_in, std_out_cond.fit_transform(df_out))
```

%% Cell type:code id: tags:

``` python
# Save model
save = {
    'loss': est_cond['net'].loss_,
    'estimator': est_cond,
    'output_norm': std_out_cond,
    'actionvars': actionvars,
    'statevars': statevars,
    'inputs': inputs,
    'outputs': outputs
    'outputs': outputs,
    'lag': lag,
}
with open(path.join(bin_path, 'v2_condenser'), 'wb') as f:
    pickle.dump(save, f)
```

%% Cell type:code id: tags:

``` python
# load model
with open(path.join(bin_path, 'v2_condenser'), 'rb') as f:
    save = pickle.load(f, fix_imports=False)
    est_cond = save['estimator']
    std_out_cond = save['output_norm']
```

%% Cell type:code id: tags:

``` python
# Visualize model predictions
test_in, test_out = dfs_in[2], dfs_out[2]
pred = pd.DataFrame(std_out_cond.inverse_transform(est_cond.predict(test_in)),
                    index=test_out.index, columns=test_out.columns)

plt.subplot(2, 1, 1)
test_in.loc[:, ('TempCondIn')].plot(grid=True, style=':', label='TempCondIn')
test_out.loc[:, ('TempCondIn')].plot(grid=True, style=':', label='TempCondIn-Next')
pred.loc[:, ('TempCondIn')].plot(grid=True, style=':', label='TempCondIn-Next-Pred')
test_in.loc[:, ('TempCondOut')].plot(grid=True, style=':', label='TempCondOut')
test_out.loc[:, ('TempCondOut')].plot(grid=True, style=':', label='TempCondOut-Next')
pred.loc[:, ('TempCondOut')].plot(grid=True, style=':', label='TempCondOut-Next-Pred')
plt.subplot(4, 1, 1)
test_in.loc[:, ('TempCondIn')].plot(grid=True, style='g:', label='TempCondIn')
test_out.loc[:, ('TempCondIn')].plot(grid=True, style='-', label='TempCondIn-Next')
pred.loc[:, ('TempCondIn')].plot(grid=True, style='-', label='TempCondIn-Next-Pred')
plt.legend()
plt.subplot(4, 1, 2)
test_in.loc[:, ('TempCondOut')].plot(grid=True, style='g:', label='TempCondOut')
test_out.loc[:, ('TempCondOut')].plot(grid=True, style='-', label='TempCondOut-Next')
pred.loc[:, ('TempCondOut')].plot(grid=True, style='-', label='TempCondOut-Next-Pred')
plt.legend()
plt.subplot(4, 1, 3)
test_out.loc[:, ('PowChi')].plot(grid=True, label='PowChi')
pred.loc[:, ('PowChi')].plot(grid=True, label='PowChi-Pred')
plt.legend()
plt.subplot(2, 1, 2)
ax4 = test_out.loc[:, ('PowChi')].plot(grid=True, label='PowChi')
ax5 = pred.loc[:, ('PowChi')].plot(grid=True, label='PowChi-Pred')
plt.subplot(4, 1, 4)
test_out.loc[:, ('PowFanA')].plot(grid=True, label='PowFanA')
pred.loc[:, ('PowFanA')].plot(grid=True, label='PowFanA-Pred')
plt.legend()
```

%% Cell type:code id: tags:

``` python
point = df_in.loc['2019-08-01T1200-6'].values.reshape(1, -1)
point = df_in.loc['2020-07-10T1200-6'].values.reshape(1, -1)
x, y, z = model_surface(lambda x: std_out_cond.inverse_transform(est_cond.predict(x))[:,2],
                        X=point, vary_idx=(0, 3),vary_range=((65, 85), (75, 95)), vary_num=(20, 20))
ax = plot_surface(x,y,z, cmap=plt.cm.coolwarm)
ax.set_xlabel('TempCondInSetpoint')
ax.set_ylabel('TempAmbient')
ax.set_zlabel('TempCondIn-Next Cycle')
```

%% Cell type:code id: tags:

``` python
print(df_in.loc['2019-08-01T1200-6'])
print(df_in.loc['2020-07-10T1200-6'])
```
+15 −32
Changes for src/baseline_control.py: 15 added lines, 32 removed lines.
Original line number Diff line number Diff line
@@ -2,7 +2,8 @@
Defines controller classes implementing various approaches.
"""


from typing import Union
from collections import deque

from sklearn.base import BaseEstimator
import numpy as np
@@ -174,50 +175,28 @@ class SimpleFeedbackController(BaseEstimator):
        self.bounds = np.asarray(bounds)
        self.stepsize = stepsize
        self.window = window
        self._feedbacks = []
        self._states = []
        self._actions = []
        self._errors = []
        self._cum_errors = []
        self._feedbacks = deque(maxlen=100)
        self._states = deque(maxlen=100)
        self._actions = deque(maxlen=100)
        self._errors = deque(maxlen=100)


    def predict(self, X: pd.DataFrame):
        feedback = self.feedback(X)
        self._feedbacks.append(feedback)
        # proportional
        error = self._feedbacks[-1] - \
            (np.mean(self._feedbacks[-self.window-1:-1]) if len(self._feedbacks) >= 2 \
             else self._feedbacks[-1])
        self._errors.append(error)
        delta_error = self._errors[-1] - \
            (self._errors[-2] if len(self._errors) >= 2 else self._errors[-1])

        if len(self._actions) <= 2:
        if len(self._actions) < 2:
            action = self.starting_action(X)
            self._actions.append(action)
            step_action = self._actions[-1] - self._actions[-min(2, len(self._actions))]
        else:
            # change_action = self._actions[-1] - self._actions[-2]
            # prev_change_action = self._actions[-2] - self._actions[-3]

            # delta_error = self._errors[-1] - self._errors[-2]
            # prev_delta_error = self._errors[-2] - self._errors[-3]

            # # da - da1 = (da2 - da1) / (de2 - de1) * (de - de1)
            # # da = (da2 - da1) / (de2 - de1) * (0 - de1) + da1
            # # a = a1 + da
            # gradient = (prev_change_action - change_action) / (prev_delta_error - delta_error + 1e-3)
            # step_action = change_action + gradient * (-delta_error)
            # action = self._actions[-1] + step_action
            # action = np.clip(action, a_min=self.bounds[:, 0], a_max=self.bounds[:, 1])

            a_2, a_1 = self._actions[-1], self._actions[-2]
            e_2, e_1 = self._feedbacks[-1], self._feedbacks[-2]
            f_2, f_1 = self._feedbacks[-1], self._feedbacks[-2]
            dir_a = np.random.choice([-1, 1]) if np.sign(a_1-a_2) == 0 else np.sign(a_1-a_2)
            dir_e = np.sign(e_1 - e_2)
            step_action = self.stepsize * dir_a * dir_e 
            dir_f = np.sign(f_1 - f_2)
            step_action = self.stepsize * dir_a * dir_f 
            a_0 = a_1 + step_action
            action = np.clip(a_0, a_min=self.bounds[:, 0], a_max=self.bounds[:, 1])
            action = self.clip_action(a_0, X)
            
            self._actions.append(action)
        # print('err: {:8.2f}, d_err: {:8.2f}, T: {:5.2f}, deltaT: {:5.2f}'\
@@ -231,3 +210,7 @@ class SimpleFeedbackController(BaseEstimator):

    def starting_action(self, X: np.ndarray):
        raise NotImplementedError('Subclass and overide this method.')


    def clip_action(self, u: Union[np.ndarray, float, int], X: np.ndarray):
        return np.clip(u, a_min=self.bounds[:, 0], a_max=self.bounds[:, 1])
 No newline at end of file
+26 −14
Changes for src/controller.py: 26 added lines, 14 removed lines.
Original line number Diff line number Diff line
@@ -21,7 +21,6 @@ import pandas as pd
import pytz
from sklearn.base import BaseEstimator

sys.path.insert(0, '../../BDX/')
import bdx


@@ -87,26 +86,39 @@ def make_logger(**settings) -> logging.Logger:


def get_controller(**settings) -> BaseEstimator:
    from baseline_control import FeedbackController
    class Controller(FeedbackController):
    from baseline_control import SimpleFeedbackController
    class Controller(SimpleFeedbackController):
    
        def feedback(self, X: pd.DataFrame):
            return -X['PowChi']
        def feedback(self, X):
            if settings['target'].lower() == 'power':
                return - X['PowChi'] - X['PowFanA'] - X['PowFanB'] - X['PowConP']
            else:
                return -X['TempCondIn']
        
        def starting_action(self, X: pd.DataFrame):
            return X['TempWetbulb'] + 4.
        def starting_action(self, X):
            return np.asarray([X['TempWetBulb'] + 4])

    kp, ki, kd = float(settings['kp']), float(settings['ki']), float(settings['kd'])
    ctrl = Controller(bounds=((60., 80.),), kp=kp, kd=kd, ki=ki)
        def clip_action(self, u, X):
            u = super().clip_action(u, X)
            return np.clip(u, a_min=X['TempWetBulb'], a_max=None)

    stepsize, window = float(settings['stepsize']), float(settings['window'])
    setpoint_bounds = map(float, settings['bounds'].split(','))
    ctrl = Controller(bounds=(setpoint_bounds,), stepsize=stepsize, window=window)
    return ctrl



def update_controller(ctrl, **settings):
    kp, ki, kd = float(settings['kp']), float(settings['ki']), float(settings['kd'])
    ctrl.kp = kp
    ctrl.ki = ki
    ctrl.kd = kd
    # kp, ki, kd = float(settings['kp']), float(settings['ki']), float(settings['kd'])
    # ctrl.kp = kp
    # ctrl.ki = ki
    # ctrl.kd = kd
    stepsize, window = float(settings['stepsize']), float(settings['window'])
    setpoint_bounds = map(float, settings['bounds'].split(','))
    ctrl.stepsize = stepsize
    ctrl.window = window
    ctrl.bounds = np.asarray([setpoint_bounds])
    return ctrl


@@ -161,7 +173,7 @@ if __name__ == '__main__':
            prev_end = start - 2*timedelta(seconds=int(settings['interval']))
            state = get_current_state(prev_end, start, **settings)
            if state is not None:
                logger.debug('State {}'.format(state))
                logger.debug('State\n{}'.format(state))
                action, = ctrl.predict(state)
                logger.info('Setpoint: {}'.format(action))
                put_control_action(action, **settings)
+14 −8
Changes for src/settings.ini: 14 added lines, 8 removed lines.
Original line number Diff line number Diff line
@@ -3,18 +3,24 @@ controller = FEEDBACK CONTROL
application = ESB
# How much diagnostic output to write. One of:
# 'CRITICAL', 'ERROR', 'WARNING', 'INFO', 'DEBUG'
verbosity = DEBUG
# Interval between querying state and putting control action.
interval = 3
verbosity = INFO
# Interval (seconds) between querying state and putting control action.
interval = 300
username = 
password = 
# Trend IDs for chiller 1 and chiller 2 condenser units.
chiller_1_trend = 2422
chiller_2_trend = 2841
# Location of output file where to put control.
# Location of output file where to put control action.
output = ./output.txt

# feedback control parameters
kp = 1.
ki = 1.
kd = 1.
 No newline at end of file
# Simple feedback control parameters
# One of 'power', 'temperature'. 'power' tries to mininize chiller, fan, condenser
# pump power. 'temperature' minimizes temperature of water coming into the condenser.
target = power
# Size of setpoint change between timesteps
stepsize = 1.
# Past measurements to average over to compare performance
window = 1.
# min,max setpoint values
bounds = 60.,80.
 No newline at end of file
Loading