24. Rule Controller example

First, we import all the necessary libraries. Remember to always import sinergym, even if it appears unused, as it’s needed to define the environments.

[1]:
from typing import List, Any, Sequence
from sinergym.utils.constants import YEAR
from datetime import datetime
import gymnasium as gym
import numpy as np
import sinergym

Next, we can define the environment we want to use. In this case, we’re using the Eplus demo.

[2]:
env = gym.make('Eplus-5zone-hot-continuous-v1')
#==============================================================================================#
[ENVIRONMENT] (INFO) : Creating Gymnasium environment... [5zone-hot-discrete-v1]
#==============================================================================================#
[MODELING] (INFO) : Experiment working directory created [/workspaces/sinergym/examples/Eplus-env-5zone-hot-discrete-v1-res35041]
[MODELING] (INFO) : runperiod established: {'start_day': 1, 'start_month': 1, 'start_year': 1991, 'end_day': 31, 'end_month': 12, 'end_year': 1991, 'start_weekday': 1, 'n_steps_per_hour': 4}
[MODELING] (INFO) : Episode length (seconds): 31536000.0
[MODELING] (INFO) : timestep size (seconds): 900.0
[MODELING] (INFO) : timesteps per episode: 35040
[MODELING] (INFO) : Model Config is correct.
[REWARD] (INFO) : Reward function initialized.
[ENVIRONMENT] (INFO) : Environment 5zone-hot-discrete-v1 created successfully.

For the Rule-based controller, check out the already defined controllers. There’s one for each building. Since the demo is based on the 5Zone building, we’re extending that controller and defining the action function we want. Feel free to modify the function to define your own action.

[3]:
from sinergym.utils.controllers import RBC5Zone

class MyRuleBasedController(RBC5Zone):

    def act(self, observation: List[Any]) -> Sequence[Any]:
        """Select action based on outdoor air drybulb temperature and daytime.

        Args:
            observation (List[Any]): Perceived observation.

        Returns:
            Sequence[Any]: Action chosen.
        """
        obs_dict = dict(zip(self.env.get_wrapper_attr('observation_variables'), observation))

        out_temp = obs_dict['outdoor_temperature']

        day = int(obs_dict['day_of_month'])
        month = int(obs_dict['month'])
        hour = int(obs_dict['hour'])
        year = int(obs_dict['year'] if obs_dict.get('year',False) else YEAR)

        summer_start_date = datetime(year, 6, 1)
        summer_final_date = datetime(year, 9, 30)

        current_dt = datetime(year, month, day)

        # Get season comfort range
        if current_dt >= summer_start_date and current_dt <= summer_final_date:
            season_comfort_range = self.setpoints_summer
        else:
            season_comfort_range = self.setpoints_summer
        season_comfort_range = self.setpoints_winter
        # Update setpoints
        in_temp = obs_dict['air_temperature']

        current_heat_setpoint = obs_dict[
            'htg_setpoint']
        current_cool_setpoint = obs_dict[
            'clg_setpoint']

        new_heat_setpoint = current_heat_setpoint
        new_cool_setpoint = current_cool_setpoint

        if in_temp < season_comfort_range[0]:
            new_heat_setpoint = current_heat_setpoint + 1
            new_cool_setpoint = current_cool_setpoint + 1
        elif in_temp > season_comfort_range[1]:
            new_cool_setpoint = current_cool_setpoint - 1
            new_heat_setpoint = current_heat_setpoint - 1

        #Clip setpoints to the action space
        if new_heat_setpoint>self.env.get_wrapper_attr('action_space').high[0]:
            new_heat_setpoint=self.env.get_wrapper_attr('action_space').high[0]
        if new_heat_setpoint<self.env.get_wrapper_attr('action_space').low[0]:
            new_heat_setpoint=self.env.get_wrapper_attr('action_space').low[0]
        if new_cool_setpoint>self.env.get_wrapper_attr('action_space').high[1]:
            new_cool_setpoint=self.env.get_wrapper_attr('action_space').high[1]
        if new_cool_setpoint<self.env.get_wrapper_attr('action_space').low[1]:
            new_cool_setpoint=self.env.get_wrapper_attr('action_space').low[1]

        action = (new_heat_setpoint, new_cool_setpoint)
        if current_dt.weekday() > 5 or hour in range(22, 6):
            #weekend or night
            action = (18.33, 23.33)

        return action

Now that our controller is ready, we can use it:

[4]:

# create rule-based controller agent = MyRuleBasedController(env) for i in range(1): obs, info = env.reset() rewards = [] truncated = terminated = False current_month = 0 while not (terminated or truncated): action = agent.act(obs) obs, reward, terminated, truncated, info = env.step(action) rewards.append(reward) if info['month'] != current_month: # display results every month current_month = info['month'] print('Reward: ', sum(rewards), info) print( 'Episode ', i, 'Mean reward: ', np.mean(rewards), 'Cumulative reward: ', sum(rewards))
#----------------------------------------------------------------------------------------------#
[ENVIRONMENT] (INFO) : Starting a new episode... [5zone-hot-discrete-v1] [Episode 1]
#----------------------------------------------------------------------------------------------#
[MODELING] (INFO) : Episode directory created [/workspaces/sinergym/examples/Eplus-env-5zone-hot-discrete-v1-res35041/Eplus-env-sub_run1]
[MODELING] (INFO) : Weather file USA_AZ_Davis-Monthan.AFB.722745_TMY3.epw used.
[MODELING] (INFO) : Updated building model with whole Output:Variable available names
[MODELING] (INFO) : Updated building model with whole Output:Meter available names
[MODELING] (INFO) : Adapting weather to building model. [USA_AZ_Davis-Monthan.AFB.722745_TMY3.epw]
[ENVIRONMENT] (INFO) : Saving episode output path... [/workspaces/sinergym/examples/Eplus-env-5zone-hot-discrete-v1-res35041/Eplus-env-sub_run1/output]
/usr/local/lib/python3.10/dist-packages/opyplus/weather_data/weather_data.py:493: FutureWarning: the 'line_terminator'' keyword is deprecated, use 'lineterminator' instead.
  epw_content = self._headers_to_epw(use_datetimes=use_datetimes) + df.to_csv(
[SIMULATOR] (INFO) : Running EnergyPlus with args: ['-w', '/workspaces/sinergym/examples/Eplus-env-5zone-hot-discrete-v1-res35041/Eplus-env-sub_run1/USA_AZ_Davis-Monthan.AFB.722745_TMY3.epw', '-d', '/workspaces/sinergym/examples/Eplus-env-5zone-hot-discrete-v1-res35041/Eplus-env-sub_run1/output', '/workspaces/sinergym/examples/Eplus-env-5zone-hot-discrete-v1-res35041/Eplus-env-sub_run1/5ZoneAutoDXVAV.epJSON']
[ENVIRONMENT] (INFO) : Episode 1 started.
[SIMULATOR] (INFO) : handlers initialized.
[SIMULATOR] (INFO) : handlers are ready.
[SIMULATOR] (INFO) : System is ready.
Reward:  -0.913313585398759 {'time_elapsed(hours)': 0.5, 'month': 1, 'day': 1, 'hour': 0, 'is_raining': False, 'action': (15.0, 30.0), 'timestep': 2, 'reward': -0.913313585398759, 'energy_term': -0.05542625239599594, 'comfort_term': -0.857887333002763, 'reward_weight': 0.5, 'abs_energy': 1108.5250479199187, 'abs_comfort': 1.715774666005526, 'energy_values': [1108.5250479199187], 'temp_values': [18.284225333994474]}
Progress: |**-------------------------------------------------------------------------------------------------| 2%
/usr/local/lib/python3.10/dist-packages/gymnasium/spaces/box.py:240: UserWarning: WARN: Casting input x to numpy array.
  gym.logger.warn("Casting input x to numpy array.")
Reward:  -2795.338629010751 {'time_elapsed(hours)': 744.375, 'month': 2, 'day': 1, 'hour': 0, 'is_raining': False, 'action': (15.0, 22.5), 'timestep': 2977, 'reward': -0.005629148688825955, 'energy_term': -0.005629148688825955, 'comfort_term': -0.0, 'reward_weight': 0.5, 'abs_energy': 112.5829737765191, 'abs_comfort': 0.0, 'energy_values': [112.5829737765191], 'temp_values': [20.780085346555335]}
Reward:  -3442.9942477932536 {'time_elapsed(hours)': 1416.25, 'month': 3, 'day': 1, 'hour': 0, 'is_raining': False, 'action': (17.0, 24.5), 'timestep': 5665, 'reward': -0.22725948485913058, 'energy_term': -0.00882370430179678, 'comfort_term': -0.21843578055733381, 'reward_weight': 0.5, 'abs_energy': 176.47408603593558, 'abs_comfort': 0.43687156111466763, 'energy_values': [176.47408603593558], 'temp_values': [19.563128438885332]}
Reward:  -6086.758193423204 {'time_elapsed(hours)': 2160.25, 'month': 4, 'day': 1, 'hour': 0, 'is_raining': False, 'action': (18.33, 23.33), 'timestep': 8641, 'reward': -0.0, 'energy_term': -0.0, 'comfort_term': -0.0, 'reward_weight': 0.5, 'abs_energy': 0.0, 'abs_comfort': 0.0, 'energy_values': [0.0], 'temp_values': [21.98606808783697]}
Reward:  -8588.569469115886 {'time_elapsed(hours)': 2880.25, 'month': 5, 'day': 1, 'hour': 0, 'is_raining': False, 'action': (15.0, 22.5), 'timestep': 11521, 'reward': -0.4531417474460593, 'energy_term': -0.0, 'comfort_term': -0.4531417474460593, 'reward_weight': 0.5, 'abs_energy': 0.0, 'abs_comfort': 0.9062834948921186, 'energy_values': [0.0], 'temp_values': [24.40628349489212]}
Reward:  -11765.765367945189 {'time_elapsed(hours)': 3624.25, 'month': 6, 'day': 1, 'hour': 0, 'is_raining': False, 'action': (15.0, 22.5), 'timestep': 14497, 'reward': -0.0, 'energy_term': -0.0, 'comfort_term': -0.0, 'reward_weight': 0.5, 'abs_energy': 0.0, 'abs_comfort': 0.0, 'energy_values': [0.0], 'temp_values': [25.900405758648773]}
Reward:  -14245.140165597242 {'time_elapsed(hours)': 4344.375, 'month': 7, 'day': 1, 'hour': 0, 'is_raining': False, 'action': (18.33, 23.33), 'timestep': 17377, 'reward': -0.9700071561753898, 'energy_term': -0.0, 'comfort_term': -0.9700071561753898, 'reward_weight': 0.5, 'abs_energy': 0.0, 'abs_comfort': 1.9400143123507796, 'energy_values': [0.0], 'temp_values': [27.94001431235078]}
Reward:  -16167.362661542358 {'time_elapsed(hours)': 5088.25, 'month': 8, 'day': 1, 'hour': 0, 'is_raining': False, 'action': (15.0, 22.5), 'timestep': 20353, 'reward': -0.0, 'energy_term': -0.0, 'comfort_term': -0.0, 'reward_weight': 0.5, 'abs_energy': 0.0, 'abs_comfort': 0.0, 'energy_values': [0.0], 'temp_values': [25.996330089060972]}
Reward:  -18044.632044925856 {'time_elapsed(hours)': 5832.25, 'month': 9, 'day': 1, 'hour': 0, 'is_raining': False, 'action': (15.0, 22.5), 'timestep': 23329, 'reward': -0.0, 'energy_term': -0.0, 'comfort_term': -0.0, 'reward_weight': 0.5, 'abs_energy': 0.0, 'abs_comfort': 0.0, 'energy_values': [0.0], 'temp_values': [25.799172085986662]}
Reward:  -19572.469153276368 {'time_elapsed(hours)': 6552.3125, 'month': 10, 'day': 1, 'hour': 0, 'is_raining': False, 'action': (15.0, 22.5), 'timestep': 26209, 'reward': -1.0005842181877573, 'energy_term': -0.06446283743025183, 'comfort_term': -0.9361213807575055, 'reward_weight': 0.5, 'abs_energy': 1289.2567486050366, 'abs_comfort': 1.872242761515011, 'energy_values': [1289.2567486050366], 'temp_values': [25.37224276151501]}
Reward:  -25697.968230973762 {'time_elapsed(hours)': 7296.25, 'month': 11, 'day': 1, 'hour': 0, 'is_raining': False, 'action': (15.0, 22.5), 'timestep': 29185, 'reward': -0.004536449977537947, 'energy_term': -0.004536449977537947, 'comfort_term': -0.0, 'reward_weight': 0.5, 'abs_energy': 90.72899955075894, 'abs_comfort': 0.0, 'energy_values': [90.72899955075894], 'temp_values': [21.766705234161947]}
Reward:  -28175.99400805497 {'time_elapsed(hours)': 8016.25, 'month': 12, 'day': 1, 'hour': 0, 'is_raining': False, 'action': (15.0, 22.5), 'timestep': 32065, 'reward': -0.004536449977537947, 'energy_term': -0.004536449977537947, 'comfort_term': -0.0, 'reward_weight': 0.5, 'abs_energy': 90.72899955075894, 'abs_comfort': 0.0, 'energy_values': [90.72899955075894], 'temp_values': [20.933681388733177]}
Progress: |***************************************************************************************************| 99%
Episode  0 Mean reward:  -0.8548035936270668 Cumulative reward:  -29952.317920693455

Always remember to close the environment when you’re done:

[5]:
env.close()
[ENVIRONMENT] (INFO) : Environment closed. [5zone-hot-discrete-v1]

For more information about our defined controllers and how to create a new one, please visit our Controller Documentation.