[docs]classdc_gymenv(gym.Env):def__init__(self,observation_variables:list,observation_space:spaces.Box,action_variables:list,action_space:spaces.Discrete,action_mapping:dict,ranges:dict,# this data frame should be time indexed for the code to workadd_cpu_usage:bool,min_temp:float,max_temp:float,action_definition:dict,DC_Config:dict,seed:int=123,episode_length_in_time:pd.Timedelta=None,# can be 1 week in minutes eg pd.Timedelta('7days')):"""Creates the data center environment Args: observation_variables (list[str]): The partial list of variables that will be evaluated inside this evironment.The actual gym space may include other variables like sine cosine of hours, day of year, cpu usage, carbon intensity and battery state of charge. observation_space (spaces.Box): The gym observations space following gymnasium standard action_variables (list[str]): The list of action variables for the environment. It is used to create the info dict returned by the environment action_space (spaces.Discrete): The gym action space following gymnasium standard action_mapping (dict): A mapping from agent discrete action choice to actual delta change in setpoint. The mapping is defined in utils.make_pyeplus_env.py ranges (dict[str,list]): The upper and lower bounds on the observation_variables max_temp (float): The maximum temperature allowed for the CRAC setpoint min_temp (float): The minimum temperature allowed for the CRAC setpoint action_definition (dict): A mapping of the action name to the default or initialized value. Specified in utils.make_pyeplus_env.py episode_length_in_time (pd.Timedelta, optional): The maximum length after which the done flag should be True. Defaults to None. Setting none causes done to be True after data set is exausted. """self.observation_variables=observation_variablesself.observation_space=observation_spaceself.action_variables=action_variablesself.action_space=action_spaceself.action_mapping=action_mappingself.ranges=rangesself.seed=seedself.add_cpu_usage=add_cpu_usageself.ambient_temp=20self.scale_obs=Falseself.obs_max=[]self.obs_min=[]self.DC_Config=DC_Config# similar to resetself.dc=DataCenter.DataCenter_ITModel(num_racks=self.DC_Config.NUM_RACKS,rack_supply_approach_temp_list=self.DC_Config.RACK_SUPPLY_APPROACH_TEMP_LIST,rack_CPU_config=self.DC_Config.RACK_CPU_CONFIG,max_W_per_rack=self.DC_Config.MAX_W_PER_RACK,DC_ITModel_config=self.DC_Config)self.CRAC_Fan_load,self.CRAC_cooling_load,self.Compressor_load,self.CW_pump_load,self.CT_pump_load=None,None,None,None,Noneself.HVAC_load=self.ranges['Facility Total HVAC Electricity Demand Rate(Whole Building)'][0]self.rackwise_cpu_pwr,self.rackwise_itfan_pwr,self.rackwise_outlet_temp=[],[],[]self.cpu_load_frac=0.5self.bat_SoC=300*1e3# all units are SIself.raw_curr_state=Noneself.raw_next_state=Noneself.raw_curr_stpt=action_definition['cooling setpoints']['initial_value']self.max_temp=max_tempself.min_temp=min_tempself.consecutive_actions=0self.last_action=Noneself.action_scaling_factor=1# Starts with a scale factor of 1# IT + HVACself.power_lb_kW=(self.ranges['Facility Total Building Electricity Demand Rate(Whole Building)'][0]+self.ranges['Facility Total HVAC Electricity Demand Rate(Whole Building)'][0])/1e3self.power_ub_kW=(self.ranges['Facility Total Building Electricity Demand Rate(Whole Building)'][1]+self.ranges['Facility Total HVAC Electricity Demand Rate(Whole Building)'][1])/1e3super().__init__()
[docs]defreset(self,*,seed=None,options=None):""" Reset `dc_gymenv` to initial state. Args: seed (int, optional): Random seed. options (dict, optional): Environment options. Returns: raw_curr_state (List[float]): Current state of the environmment {} (dict): A dictionary that containing additional information about the environment state """super().reset(seed=self.seed)self.CRAC_Fan_load,self.CRAC_cooling_load,self.Compressor_load,self.CW_pump_load,self.CT_pump_load=None,None,None,None,Noneself.HVAC_load=self.ranges['Facility Total HVAC Electricity Demand Rate(Whole Building)'][0]self.rackwise_cpu_pwr,self.rackwise_itfan_pwr,self.rackwise_outlet_temp=[],[],[]self.water_usage=Noneself.raw_curr_state=self.get_obs()self.consecutive_actions=0self.last_action=Noneself.action_scaling_factor=1# Starts with a scale factor of 1ifself.scale_obs:returnself.normalize(self.raw_curr_state),{}
[docs]defstep(self,action):""" Makes an environment step in`dc_gymenv. Args: action_id (int): Action to take. Returns: observations (List[float]): Current state of the environmment reward (float): reward value. done (bool): A boolean value signaling the if the episode has ended. info (dict): A dictionary that containing additional information about the environment state """crac_setpoint_delta=self.action_mapping[action]# Check if the current action is in the same direction as the last oneifcrac_setpoint_delta==self.last_actionandaction!=0:self.consecutive_actions+=1else:self.consecutive_actions=1self.action_scaling_factor=1# Reset scaling factor if the direction changes# Adjust the scaling factor based on consecutive actionsifself.consecutive_actions>3:self.action_scaling_factor+=1# Increase the scale factor after every 3 consecutive actionsself.raw_curr_stpt+=crac_setpoint_delta*self.action_scaling_factorself.raw_curr_stpt=max(min(self.raw_curr_stpt,self.max_temp),self.min_temp)ITE_load_pct_list=[self.cpu_load_frac*100foriinrange(self.DC_Config.NUM_RACKS)]# Util, Setpoint, Average return temperature, Average CRAC Ret Temp, DC ITE power, CT power, Chiller power# 0, 15, 26.26, 23.16, 656710# 0, 21, 31.97, 28.87, 871450# 100, 15, 35.45, 32.44, 1248170# 100, 21, 36.88, 26.78, 1462910self.rackwise_cpu_pwr,self.rackwise_itfan_pwr,self.rackwise_outlet_temp= \
self.dc.compute_datacenter_IT_load_outlet_temp(ITE_load_pct_list=ITE_load_pct_list,CRAC_setpoint=self.raw_curr_stpt)# for a in [0, 100]:# for b in [15, 23]:# ITE_load_pct_list = [a for i in range(self.DC_Config.NUM_RACKS)]# _, _, outlet = self.dc.compute_datacenter_IT_load_outlet_temp(ITE_load_pct_list=ITE_load_pct_list, CRAC_setpoint=b)# print(f'CPU util: {a}, Setpoint: {b}, Average Outlet Temp: {np.mean(outlet)}')avg_CRAC_return_temp=DataCenter.calculate_avg_CRAC_return_temp(rack_return_approach_temp_list=self.DC_Config.RACK_RETURN_APPROACH_TEMP_LIST,rackwise_outlet_temp=self.rackwise_outlet_temp)data_center_total_ITE_Load=sum(self.rackwise_cpu_pwr)+sum(self.rackwise_itfan_pwr)# Now, I want to obtain the CT_Cooling_load and the Compressor_load for the whole range of input parameters (setpoint, CRAC return temp, ambient temp)# for a in [15, 21]:# for c in [5, 20, 30, 40]:# for util in [0, 100]:# ITE_load_pct_list = [util for i in range(self.DC_Config.NUM_RACKS)]# self.rackwise_cpu_pwr, self.rackwise_itfan_pwr, self.rackwise_outlet_temp = \# self.dc.compute_datacenter_IT_load_outlet_temp(ITE_load_pct_list=ITE_load_pct_list, CRAC_setpoint=a)# avg_CRAC_return_temp = DataCenter.calculate_avg_CRAC_return_temp(rack_return_approach_temp_list=self.DC_Config.RACK_RETURN_APPROACH_TEMP_LIST,# rackwise_outlet_temp=self.rackwise_outlet_temp)# data_center_total_ITE_Load = sum(self.rackwise_cpu_pwr) + sum(self.rackwise_itfan_pwr)# self.CRAC_Fan_load, self.CT_Cooling_load, self.CRAC_Cooling_load, self.Compressor_load, self.CW_pump_load, self.CT_pump_load = DataCenter.calculate_HVAC_power(CRAC_setpoint=a,# avg_CRAC_return_temp=avg_CRAC_return_temp,# ambient_temp=c,# data_center_full_load=data_center_total_ITE_Load,# DC_Config=self.DC_Config)# # print(f'CRAC setpoint:{a}, CPU util: {util}, Return temp: {avg_CRAC_return_temp}, Amb temp: {c}, HVAC CT power: {self.CT_Cooling_load}, Compressor power: {self.Compressor_load}')# # PUE = (data_center_total_ITE_Load + self.CT_Cooling_load + self.Compressor_load) / data_center_total_ITE_Load# print(f'{a} | {c} | {util} | {avg_CRAC_return_temp:.0f} | {data_center_total_ITE_Load:.0f} | {self.CT_Cooling_load:.0f} | {self.Compressor_load:.0f} | {(data_center_total_ITE_Load + self.CT_Cooling_load + self.Compressor_load)/data_center_total_ITE_Load:.3f}')# Setpoint | Ambient | CPU Util | Avg CRAC Return Temp | DC ITE Load | HVAC CT Power | Compressor Power | PUE# 15 | 5 | 0 | 23 | 656834 | 1952 | 161933 | 1.250# 15 | 5 | 100 | 32 | 1248294 | 130864 | 585781 | 1.574# 15 | 20 | 0 | 23 | 656834 | 4628 | 149192 | 1.234# 15 | 20 | 100 | 32 | 1248294 | 310196 | 637508 | 1.759# 15 | 30 | 0 | 23 | 656834 | 9836 | 135291 | 1.221# 15 | 30 | 100 | 32 | 1248294 | 659281 | 596291 | 2.006# 15 | 40 | 0 | 23 | 656834 | 26990 | 119636 | 1.223# 15 | 40 | 100 | 32 | 1248294 | 1809066 | 493200 | 2.844# 21 | 5 | 0 | 29 | 871574 | 3077 | 207725 | 1.242# 21 | 5 | 100 | 37 | 1463034 | 117203 | 585781 | 1.480# 21 | 20 | 0 | 29 | 871574 | 6668 | 189589 | 1.225# 21 | 20 | 100 | 37 | 1463034 | 254015 | 637508 | 1.609# 21 | 30 | 0 | 29 | 871574 | 12835 | 170185 | 1.210# 21 | 30 | 100 | 37 | 1463034 | 488897 | 640734 | 1.772# 21 | 40 | 0 | 29 | 871574 | 29693 | 148729 | 1.205# 21 | 40 | 100 | 37 | 1463034 | 1131057 | 528365 | 2.134self.CRAC_Fan_load,self.CT_Cooling_load,self.CRAC_Cooling_load,self.Compressor_load,self.CW_pump_load,self.CT_pump_load=DataCenter.calculate_HVAC_power(CRAC_setpoint=self.raw_curr_stpt,avg_CRAC_return_temp=avg_CRAC_return_temp,ambient_temp=self.ambient_temp,data_center_full_load=data_center_total_ITE_Load,DC_Config=self.DC_Config)self.HVAC_load=self.CT_Cooling_load+self.Compressor_load# for a in [15, 24]:# for c in [5, 20, 30]:# for util in [0, 100]:# ITE_load_pct_list = [util for i in range(self.DC_Config.NUM_RACKS)]# self.rackwise_cpu_pwr, self.rackwise_itfan_pwr, self.rackwise_outlet_temp = \# self.dc.compute_datacenter_IT_load_outlet_temp(ITE_load_pct_list=ITE_load_pct_list, CRAC_setpoint=self.raw_curr_stpt)# avg_CRAC_return_temp = DataCenter.calculate_avg_CRAC_return_temp(rack_return_approach_temp_list=self.DC_Config.RACK_RETURN_APPROACH_TEMP_LIST,# rackwise_outlet_temp=self.rackwise_outlet_temp)# data_center_total_ITE_Load = sum(self.rackwise_cpu_pwr) + sum(self.rackwise_itfan_pwr)# _, ct, _, _, _, _ = DataCenter.calculate_HVAC_power(CRAC_setpoint=a,# avg_CRAC_return_temp=avg_CRAC_return_temp,# ambient_temp=c,# data_center_full_load=data_center_total_ITE_Load,# DC_Config=self.DC_Config)# print(f'CRAC setpoint:{a}, CPU util: {util}, Return temp: {avg_CRAC_return_temp}, Amb temp: {c}, HVAC CT power: {ct}')# Set the additional attributes for the cooling tower water usage calculationself.dc.hot_water_temp=avg_CRAC_return_temp# °Cself.dc.cold_water_temp=self.raw_curr_stpt# °Cself.dc.wet_bulb_temp=self.wet_bulb# °C from weather data# Calculate the cooling tower water usageself.water_usage=self.dc.calculate_cooling_tower_water_usage()# water_usage_meth2 = DataCenter.calculate_water_consumption_15min(self.CRAC_Cooling_load, self.dc.hot_water_temp, self.dc.cold_water_temp)# print(f"Estimated cooling tower water usage method1 (liters per 15 min): {water_usage}")# print(f"Estimated cooling tower water usage method2 (liters per 15 min): {water_usage_meth2}")# calculate rewardself.reward=0# calculate self.raw_next_stateself.raw_next_state=self.get_obs()# Update the last actionself.last_action=crac_setpoint_delta# add info dictionary self.info={'dc_ITE_total_power_kW':data_center_total_ITE_Load/1e3,'dc_CT_total_power_kW':self.CT_Cooling_load/1e3,'dc_Compressor_total_power_kW':self.Compressor_load/1e3,'dc_HVAC_total_power_kW':(self.CT_Cooling_load+self.Compressor_load)/1e3,'dc_total_power_kW':(data_center_total_ITE_Load+self.CT_Cooling_load+self.Compressor_load)/1e3,'dc_crac_setpoint_delta':crac_setpoint_delta,'dc_crac_setpoint':self.raw_curr_stpt,'dc_cpu_workload_fraction':self.cpu_load_frac,'dc_int_temperature':np.mean(self.rackwise_outlet_temp),'dc_exterior_ambient_temp':self.ambient_temp,'dc_power_lb_kW':self.power_lb_kW,'dc_power_ub_kW':self.power_ub_kW,'dc_CW_pump_power_kW':self.CW_pump_load,'dc_CT_pump_power_kW':self.CT_pump_load,'dc_water_usage':self.water_usage,}#Done and truncated are managed by the main class, implement individual function if neededtruncated=Falsedone=False# return processed/unprocessed state to agentifself.scale_obs:returnself.normalize(self.raw_next_state),self.reward,done,truncated,self.info
[docs]defNormalizeObservation(self,):""" Obtains the value for normalizing the observation. """self.scale_obs=Trueforobs_varinself.observation_variables:self.obs_min.append(self.ranges[obs_var][0])self.obs_max.append(self.ranges[obs_var][1])self.obs_min=np.array(self.obs_min)self.obs_max=np.array(self.obs_max)self.obs_delta=self.obs_max-self.obs_min
[docs]defnormalize(self,obs):""" Normalizes the observation. """returnnp.float32((obs-self.obs_min)/self.obs_delta)
[docs]defget_obs(self):""" Returns the observation at the current time step. Returns: observation (List[float]): Current state of the environmment. """zone_air_therm_cooling_stpt=self.min_temp# in C, default for reset stateifself.raw_curr_stptisnotNone:zone_air_therm_cooling_stpt=self.raw_curr_stptzone_air_temp=self.obs_min[2]# in C, default for reset stateifself.rackwise_outlet_temp:zone_air_temp=sum(self.rackwise_outlet_temp)/len(self.rackwise_outlet_temp)# 'Facility Total HVAC Electricity Demand Rate(Whole Building)' ie 'HVAC POWER'hvac_power=self.HVAC_load#self.CT_Cooling_load + self.Compressor_load# 'Facility Total Building Electricity Demand Rate(Whole Building)' ie 'IT POWER'ifself.rackwise_cpu_pwr:it_power=sum(self.rackwise_cpu_pwr)+sum(self.rackwise_itfan_pwr)else:it_power=self.ranges['Facility Total Building Electricity Demand Rate(Whole Building)'][0]return[self.ambient_temp,zone_air_therm_cooling_stpt,zone_air_temp,hvac_power,it_power]
[docs]defset_shifted_wklds(self,cpu_load):""" Updates the current CPU workload. fraction between 0.0 and 1.0 """if0.0>cpu_loadorcpu_load>1.0:print('CPU load out of bounds')assert0.0<=cpu_load<=1.0,'CPU load out of bounds'self.cpu_load_frac=cpu_load
[docs]defset_ambient_temp(self,ambient_temp,wet_bulb):""" Updates the external temperature. """self.ambient_temp=ambient_tempself.wet_bulb=wet_bulb
[docs]defset_bat_SoC(self,bat_SoC):""" Updates the battery state of charge. """self.bat_SoC=bat_SoC