def test_sample_episode_state_space_full(prices): space = StateSpace('state').from_primitives( PrimCfg('price [$/MWh]', min(prices), max(prices), 'continuous', prices), ) start, end = space.sample_episode('full') assert start == 0 assert end == len(prices)
def __init__( self, name, episode_length=24, # one day 24 hr dataset='data', **kwargs): self.metadata = {'render.modes': []} self.reward_range = (-float('inf'), float('inf')) self.name = name self.episodes = 0 self.episode_length = episode_length super().__init__(**kwargs) self.state_space = StateSpace().from_dataset(dataset) self.observation_space = self.state_space assert self.state_space.num_samples == self.observation_space.num_samples self.action_space = ActionSpace().from_primitives( Prim('Turn down', -1, 1, 'continuous', None)) # load tolerable power data self.tolerable_power_df = pd.read_csv(join(dataset, 'tolerable.csv'), parse_dates=True)[name]
def test_call_state_space(prices): space = StateSpace('state').from_primitives( PrimCfg('price [$/MWh]', min(prices), max(prices), 'continuous', prices), ) for idx in np.random.randint(0, len(prices), size=10): state = space(steps=idx, offset=0) assert state == prices[idx]
def __init__( self, power=2.0, capacity=4.0, efficiency=0.9, initial_charge=0.0, episode_length=2016, sample_strat='fixed', prices=None, dataset='example', **kwargs ): self.power = float(power) self.capacity = float(capacity) self.efficiency = float(efficiency) self.initial_charge = initial_charge self.sample_strat = sample_strat super().__init__(**kwargs) # this is f*****g messy if prices is not None: self.state_space = StateSpace().from_primitives( Prim('Price [$/MWh]', min(prices), max(prices), 'continuous', prices), Prim('Charge [MWh]', 0, self.capacity, 'continuous', 'append') ) else: self.state_space = StateSpace().from_dataset(dataset).append( Prim('Charge [MWh]', 0, self.capacity, 'continuous', 'append') ) self.observation_space = self.state_space assert self.state_space.num_samples == self.observation_space.num_samples if sample_strat == 'full': self.episode_length = self.state_space.num_samples else: self.episode_length = min(episode_length, self.state_space.num_samples) self.action_space = ActionSpace().from_primitives( Prim('Power [MW]', -self.power, power, 'continuous', None) )
def test_sample_episode_state_space_random(prices): space = StateSpace('state').from_primitives( PrimCfg('price [$/MWh]', min(prices), max(prices), 'continuous', prices), ) starts, ends = [], [] for _ in range(50): start, end = space.sample_episode('random', episode_length=10) starts.append(start) ends.append(end) assert min(starts) >= 0 assert max(starts) <= len(prices) # catch case when we always start at 0 if np.mean(starts) != 0: # check we start at different points assert np.std(starts) != 0 # check we end at different points assert np.std(ends) != 0
class Battery(BaseEnv): """ Electric battery operating in price arbitrage action = charging is positive, discharging is negative shape = (batch_size, 1) args power [MW] capacity [MWh] efficiency [%] initial_charge [% or 'random'] """ def __init__( self, power=2.0, capacity=4.0, efficiency=0.9, initial_charge=0.0, episode_length=2016, sample_strat='fixed', prices=None, dataset='example', **kwargs ): self.power = float(power) self.capacity = float(capacity) self.efficiency = float(efficiency) self.initial_charge = initial_charge self.sample_strat = sample_strat super().__init__(**kwargs) # this is f*****g messy if prices is not None: self.state_space = StateSpace().from_primitives( Prim('Price [$/MWh]', min(prices), max(prices), 'continuous', prices), Prim('Charge [MWh]', 0, self.capacity, 'continuous', 'append') ) else: self.state_space = StateSpace().from_dataset(dataset).append( Prim('Charge [MWh]', 0, self.capacity, 'continuous', 'append') ) self.observation_space = self.state_space assert self.state_space.num_samples == self.observation_space.num_samples if sample_strat == 'full': self.episode_length = self.state_space.num_samples else: self.episode_length = min(episode_length, self.state_space.num_samples) self.action_space = ActionSpace().from_primitives( Prim('Power [MW]', -self.power, power, 'continuous', None) ) def __repr__(self): return '<energypy BATTERY env - {:2.1f} MW {:2.1f} MWh>'.format( self.power, self.capacity) def _reset(self): """ samples new episode, returns initial observation """ # initial charge in percent if self.initial_charge == 'random': initial_charge = random() else: initial_charge = float(self.initial_charge) # charge in MWh self.charge = float(self.capacity * initial_charge) # new episode self.start, self.end = self.state_space.sample_episode( self.sample_strat, episode_length=self.episode_length ) # set initial state and observation self.state = self.state_space( self.steps, self.start, append={'Charge [MWh]': self.charge} ) self.observation = self.observation_space( self.steps, self.start, append={'Charge [MWh]': self.charge} ) assert self.charge <= self.capacity assert self.charge >= 0 return self.observation def _step(self, action): """ one step through the environment action = charging is positive, discharging is negative [MW] shape = (batch_size, 1) returns transition (dict) """ old_charge = self.charge # convert from MW to MWh/5 min by /12 net_charge = action / 12 # we first check to make sure this charge is within our capacity limit new_charge = np.clip(old_charge + net_charge, 0, self.capacity) # we can now calculate the gross power of charge or discharge # charging is positive, discharging negative gross_power = (new_charge - old_charge) * 12 # account for losses / the round trip efficiency # we lose electricity when we discharge if gross_power < 0: losses = abs(gross_power * (1 - self.efficiency)) # we don't lose anything when we charge else: losses = 0 net_power = gross_power + losses # the new charge of the battery after losses self.charge = old_charge + (gross_power / 12) # energy balances assert np.isclose(gross_power - net_power, -losses) # reward is the cost to charge / benefit to discharge # use net power as it accounts for losses electricity_price = self.get_state_variable('Price [$/MWh]') reward = -net_power * electricity_price / 12 # zero indexing steps if self.steps == self.episode_length - 1: done = True next_state = np.zeros((1, *self.state_space.shape)) next_observation = np.zeros((1, *self.observation_space.shape)) else: done = False next_state = self.state_space( self.steps + 1, self.start, append={'Charge [MWh]': float(self.charge)} ) next_observation = self.observation_space( self.steps + 1, self.start, append={'Charge [MWh]': float(self.charge)} ) return { 'step': int(self.steps), 'state': self.state, 'observation': self.observation, 'action': action, 'reward': float(reward), 'next_state': next_state, 'next_observation': next_observation, 'done': bool(done), 'Price [$/MWh]': float(electricity_price), 'Initial charge [MWh]': float(old_charge), 'Final charge [MWh]': float(self.charge), 'Gross [MW]': float(gross_power), 'Net [MW]': float(net_power), 'Loss [MW]': float(losses) }