Simulating an Orkney Aggregator

Posted by Niels Ørbæk Chemnitz on Tue, Sep 10, 2019
Tags: Orkney, Energy Systems, Aggregator, Simulation

Simulating an Orkney Aggregator

- Evaluating different decision models in a simulated Orkney aggregator.


In a previous post we looked at how well the prices of the electricity market reflected energy generation in Orkney, using time series datasets reflecting the generation and demand of electricity in Orkney1 and the closing prices of the N2EX electricity market.2 We concluded that there was no noticeable correlation between generation and price, and that using price as deciding factor would be a poor choice if ones goal was to optimize utilization of the renewable energy produced Orkney.

In this post we will get a bit more detailed in our estimations, going beyond simply “good” or “not good”. We will use our datasets to simulate an aggregator, using Python to step through the datasets chronologically, making decisions based on different optimization models. This can give us concrete estimations of the price and usage of such an aggregator.

Table of Contents:

  1. Why prioritize local energy?
  2. Defining the specifications
  3. Designing the simulation
  4. Decision models
  5. Simulation Results
  6. Conclusions

Why prioritize local energy?

The UK energy systems and corresponding electricity markets are designed based on the idea that all electrons, once in the grid, are equal. That it does not matter for a consumer whether the electricity they use have been produced locally or in the opposite end of the grid, and by whom. And while this might be true for the majority of users, it is not the case in Orkney.

Orkney is at the forefront of renewable energy innovation, producing large amounts of wind energy and testing wave and tidal energy generators. These plentiful energy sources result from the geographical situation of Orkney: A relatively flat archipelago off the northeastern coast of Scotland, with all of the winds, waves and tidal currents that come with this territory.

But this also situates Orkney at the edge of the power grid, with a single bottleneck connection to mainland Scotland, and an internal grid that is often unable to accommodate the energy that is produced on the islands. This results in frequent generator curtailment, the intentional slowing or halting of generators, to ensure that the grid is not over capacity. The result of this is to purposefully produce less low-emission energy at one point in time, while at another importing high-emission energy from mainland Scotland to power the islands.

One way to attack this problem is to use the energy locally, reducing the amount of energy that needs to be transported through the constraint points in the grid. This is our main motivation for using an aggregator to add flexibility to the grid.

Another reason to increase the use of local energy, is that a lot of the generators in Orkney are community owned. Reducing curtailment also increases revenue for many local development trusts, allowing them to invest in their island communities.

Defining the specifications

In order to carry out simulations, we need to first define the specifications of our aggregator. For this we take our outset in the proposed components of the ReFLEX project in Orkney, consisting of up to 600 electric vehicles (EVs), 500 domestic batteries, and 100 large-scale batteries.

For each type of assets, we estimate an average battery capacity and charge speed,3 allowing us to describe the total capacity and maximum load of each asset type (Table 1).

Asset Type Amount Est. Total Capacity Est. Total Charge Speed
Electric Vehicles 600 12 MWh 1.38 MW
Domestic Batteries 500 6.75 MWh 2 MW
Large-scale Batteries 100 2.4 MWh 2.4 MW
Table 1: Assets types

Furthermore we need to estimate the load, meaning how much electricity is needed to meet the continuous demand of the assets. In this simulation, we will disregard the aspect of selling electricity back to the grid, and rather focus on the flexibility in consumption that an aggregator provides. We want the aggregator to buy enough power to match our consumption, but when to buy the power is up to its decision engine.

To make the simulation simpler, we define an average daily load based on government statistics of the power consumption of domestic and non-domestic clients in Orkney,4 the average amount of miles driven in rural areas,5 and the average power consumption per kilometer for EVs6 (Table 2).

Type Yearly Load per Asset Daily Load per Asset Daily Load Total Daily Charge
Electric Vehicles 2,525 kWh (14,792 km) 7.1 kWh (41.6 km) 4.26 MWh 4.26 MWh
Domestic Clients 5,459 kWh 15.3 kWh 7.65 MWh 6.75 MWh
Non-domestic Clients 23,804 kWh 66.9 kWh 6.69 MWh 2.4 MWh
Table 2: Estimations of load per asset type

For these simulations, we will define the task for the aggregator as a fixed daily load for each asset type. For simplicity’s sake, we will disregard the patterns in use from these assets. We allow the aggregator to charge at any time during a day, as long as power equivalent to the average load is delivered sometime during the days 24 hours.

For the domestic and large-scale batteries the load exceeds the battery capacity, in which case we limit the aggregators daily load to the maximum capacity. We also disregard the round trip efficiency of the assets, which would contribute a loss of power, further increasing the amount of power needed to meet the demand. We collect all the specifications in a single file, making it easy to revise our estimations in the event of new information (Listing 1).

# Number of each type of asset
num_EVs = 600
num_dom_batts = 500
num_non_dom_batts = 100

# Battery capacity of each asset type in kWh
capacity_EVs = 20
capacity_dom_batts = 13.5
capacity_non_dom_batts = 24

# Battery capacity of each asset type in kW
charge_speed_EVs = 2.3
charge_speed_dom_batts = 5
charge_speed_non_dom_batts = 24

# Average miles driven (2017) (source:
miles_driven_rural_town = 8787
miles_driven_rural_village = 10122

town_to_village_ratio = 14000/20000 # Based on people in Kirkwall+Stromness vs total Orkney population

miles_driven_weighted_average = miles_driven_rural_town * town_to_village_ratio + miles_driven_rural_village * (1 - town_to_village_ratio)

km_pr_mile = 1.61
mean_yearly_km = miles_driven_weighted_average * km_pr_mile

EV_kWh_pr_km = 17.5 / 100 # (source:
EV_mean_daily_kWh = mean_yearly_km * EV_kWh_pr_km / 365
EV_max_daily = min(EV_mean_daily_kWh,capacity_EVs)

# Yearly mean consumption (2017) (Source:
domestic_mean_yearly_kWh = 5597
domestic_mean_daily_kWh = domestic_mean_yearly_kWh / 365
domestic_max_daily = min(domestic_mean_daily_kWh,capacity_dom_batts)

non_domestic_mean_yearly_kWh = 24406
non_domestic_mean_daily_kWh = non_domestic_mean_yearly_kWh / 365
non_domestic_max_daily = min(non_domestic_mean_daily_kWh,capacity_non_dom_batts)

types = ["EV","Domestic","Non-Domestic"]

max_daily_use = [

charge_speeds = [

hours = list(map(lambda a: a[0]/a[1], zip(max_daily_use,charge_speeds)))

total_daily_MWh = sum(max_daily_use)
Listing 1:

Designing the simulation

Now that we have defined the assets and task of our aggregator we are ready to design some simulations. We join our datasets to one large pandas DataFrame, with “generation”, “demand”, “price” and other relevant parameters as columns.

We can then step through each day, picking out the hours that best fit our decision model (e.g. “lowest price”), buying as much energy as we can (bounded by both daily load and charge speed). We then exclude the chosen hour from our dataset and repeat the process until our daily quota has been met (Listing 2).

During each simulation we keep track of the primary aspects: The energy usage, the cost, and the amount of local Orkney renewables used. We accumulate them as we go and store all of our “purchases”, enabling us to analyze them further afterwards.

Upon completion, the we output the finals stats of the simulation and return the DataFrame joined with the columns representing the individual purchases.

import estimations as est # Import our file
v = False # Verbosity flag

#### Main simulation function ####
def simulate_day_ahead(start, stop, df, opt_func, name):
    if v: print("___________ {} __________".format(name))
    start_date = datetime.strptime(start,"%Y-%m-%d")
    stop_date = datetime.strptime(stop,"%Y-%m-%d")
    df = df.loc[start_date:stop_date]
    mean_price = df["price"].mean()
    date = start_date
    day = timedelta(days=1)

    # Overall counters
    total_cost = 0
    total_energy = 0
    total_local = 0

    buys = dict() # Object to record our electricity purchases

    while date < stop_date:
        if v: print("\n############ ",date.strftime("%Y-%m-%d")," #############")
        today = df.loc[date:date+day-timedelta(minutes=1)].copy() # Pandas includes midnight the next day, which it shouldn't, so we subtract a minute to make things clear
        if len(today) > 1: # A check to skip empty data days (and a single day with just a single measurement)
            # Set daily counters
            daily_use = [0] * len(est.types)
            daily_cost = [0] * len(est.types)
            daily_local = [0] * len(est.types)

            # Keep finding the optimal hour until we have charged all we need to.
            while sum(daily_use) < est.total_daily_MWh:
                # Pick hour using optimizing function
                time = opt_func(today)
                current_hour = [0,0,0] # Counters for the current hour: Usage, Cost, Local
                if v: print(time.strftime("%H:00"))
                row = today.loc[time]
                delta = row["Generation"] - row["Demand"]
                for i, type in enumerate(est.types):
                    usage = min(est.charge_speeds[i], est.max_daily_use[i]-daily_use[i])
                    price = usage * row["price"]
                    daily_use[i] += usage
                    current_hour[0] += usage
                    daily_cost[i] += price
                    current_hour[1] += price
                    if v: print("- {} : Bought {} MWh for {} £".format(type,round(usage,2),round(usage * row["price"],2)))
                    if delta > 0 and usage > 0:
                        local = min(delta/usage,1) * usage
                        delta -= local
                        daily_local[i] += local
                        current_hour[2] += local
                today = today.drop(time) #Exclude chosen hour
                buys[time] = {
                    "usage": current_hour[0],
                    "cost": current_hour[1],
                    "local": current_hour[2]
            total_cost += sum(daily_cost)
            total_energy += sum(daily_use)
            total_local += sum(daily_local)
        date = date+day

    if v: print("\n")
    print("\n======== Optimize function: {} - FINAL RESULTS ========\n".format(name))
    print("Total energy used: {:.2f} MWh".format(total_energy))
    print("Total cost: {:.2f} £ ({:.2f} £/MWh, {:.2f}% of mean price)".format(total_cost, total_cost/total_energy, total_cost/total_energy/mean_price*100))
    print("Total Orkneys renewables energy used: {:.2f} MWh".format(total_local))
    print("Share of Orkney renewables: {:.2f}%".format(total_local/total_energy*100))

    buys = pd.DataFrame.from_dict(buys,orient="index")
    return df.join(buys, how="outer").fillna(0)
Listing 2: Main simulation function

Decision models

With the simulation structure in place, all we need to do is to feed it some data and a decision model. Our experiments will be based on the N2EX day-ahead market for wholesale electricity, which closes its daily auctions at 12:00 each day. This means that we can only based our decisions about when to buy, on information that is known to us at noon the day before delivery.

We will be evaluating 3 different day-ahead decision models:

  1. Lowest prices
  2. Highest wind speed (forecasts)
  3. Highest expected surplus generation (forecasts and statistical model)

The lowest prices are decided based on the closing prices of the N2EX day-ahead auction. The wind speeds are based on the UK Met Offices forecasts for Westray Airfield, which are collected from their open DataPoint API as 5-day ahead forecasts at 3-hour intervals. We select the forecast as it was available at noon the previous day, and apply linear interpolation between the 3-hour data points.

The third decision model also uses the wind forecasts, but combines them with the statistical average power generation at a given wind speed (what is sometimes called a power curve) and the statistical average demand at a given weekday and hour.7 This is done to estimate not just how much we generate, but how large our surplus (or deficit) is going to be.8

For comparison, we will also be simulating two decision models that are not affected by the day-ahead constraint, and that base their decisions on the actual state of Orkney generation and demand. These can be used as indicators for the optimal and worst possible decision models with regard to using local renewable energy, and gives us a baseline for our other models. Finally, we will also be evaluating a decision model optimizing for the highest price, and one making random choices.

  • Highest relative generation (optimal local)
  • Lowest relative generation (worst local)
  • Highest price
  • Random choice (mean of 100 simulations)

Each decision model is implemented as a function that takes a DataFrame with a datetime-index and rows representing the hours to choose from.9 The function returns a datetime object for the optimal hour to purchase electricity. The implementations of our decision models are shown in Listing 3. These functions can then be passed as arguments to the main simulation function.

### Simple decision models ###
def opt_price(day): return day["price"].idxmin()
def opt_wind(day): return day["wind_speed"].idxmax()
def opt_model(day): return day["wt_model"].idxmax()
### Baselines
def optimal_local(day): return (day["Generation"]-day["Demand"]).idxmax()
def worst_local(day): return (day["Generation"]-day["Demand"]).idxmin()
def max_price(day): return day["price"].idxmax()
def random(day): return day.sample().index[0]
Listing 3: Decision models

It’s worth noting here, that we treat the aggregators task as a Continuous Knapsack type problem,10 and hence we designed both the simulation and its corresponding decision models as greedy algorithms that always chooses the locally optimal choice before moving on to next iteration.

Simulation results

While we have both price and Orkney power data for the first seven months of 2019, the dataset of weather forecast only stretches back until the start of april, limiting our simulations to the four months of April through July. For these 4 months, the mean price of electricity on the N2EX market11 was 41.9 £/MWh. The accumulated usage for our aggregator was 1,447.8 MWh12, which would cost 60,663 £, if bought at the mean price. If we run our simulation on our data for these months, we get the results listed in Table 3.

Cost Local Energy
Decision Model Total £/MWh % of
Mean Price
Total %
1. Lowest price 45,691 £ 31.6 £ 75.3% 594.9 MWh 41.1%
2. Highest wind speed 58,798 £ 40.6 £ 96.9% 834.5 MWh 57.6%
3. Highest model output 55,050 £ 38.0 £ 90.8% 819.5 MWh 56.6%
4. Most local renewables 57,295 £ 39.6 £ 94.5% 989.6 MWh 68.4%
5. Least local renewables 65,171 £ 45 £ 107.4% 216.9 MWh 15.0%
6. Highest price 82,765 £ 57.2 £ 136.4% 485.2 MWh 33.5%
Mean Random (n = 100) 60,767 £ 42 £ 100.2% 578.6 MWh 40%
Table 3: Simulation results

The results in Table 3 show, as expected, that the decision model optimizing for price (Model 1) ends up using considerably less money than the mean price, a total saving of nearly 15,000 £. This decision model uses 41.4% locally produced renewable energy, which is midway between our two baselines for local energy (Models 4 & 5).

The decision model optimizing for wind speeds (Model 2) ends up with a cost very near the mean price for the period, but uses 239.6 MWh, or around 17 percentage points (pp), more local energy than Model 1.

Model 3, incorporating the historical median demand with the expected generation, somewhat surprisingly uses slightly less (~1pp) local renewables than the model just optimizing for wind speeds. But its cost is ~3700£ lower than Model 2. This indicates that while our model is not very successful in estimating the expected surplus of energy in Orkney, it does seems somewhat successful in incorporating the rhythm of the demand, thereby lowering the mean price.

This trade-off between price and local renewable energy becomes even clearer when we plot our results as in Figure 1, where this relationship is highlighted. The possible range of results within this concrete dataset is delimited by Models 1,4,5 & 6 (of which only Model 1 is a reasonable decision model), and for comparison we have also plotted the results of 100 simulations with a random decision model.

Figure 1: Relationship between price and amount of local energy used in simulation results

Figure 1 shows a slight correlation between price and local energy, in that model 4 has a lower cost than model 5, and that model 1 uses more local energy than model 6. These discrepancies are greater then the deviation in the random samples, albeit only slightly, which might be caused by the similar seasonal patterns (daily, weekly & yearly) in the Orkney demand and the N2EX prices.13 And although the demand is not the dominating factor in the Orkney grid (the fluctuations of the generation is far greater than those of the demand), it does seem to have a slight influence.


In this post we have used our collected datasets to simulate the effects of different decision models in an Orkney aggregator, looking at both their effect on the cost of electricity purchases, and on the amount of local energy used. Lowering the price is attractive for obvious reasons, but increasing the utilization of Orkney energy affects both the carbon footprint of the aggregator and the economies of Orkney island communities.

We have designed a simulation, including a mock aggregator, to evaluate different decision models based on the task of buying a daily fixed amount of electricity on the N2EX day-ahead market. We have evaluated models that seek to minimize the cost of electricity (by charging when the prices where lowest), and models that seek to maximize the use of local renewable energy (by charging when wind speeds are forecasted to highest, or when a statistical model indicated that energy surplus would be highest).

Our results indicates that, when compared to random decision models, prices were reduced by ~25% when optimizing for low costs (Model 1), with no significant change in the amount of local renewable energy used. When optimizing for wind speeds (Model 2) the prices are similar, but the amount of local energy used is increased by 44.2%. When using the statistical model (Model 3), that incorporates both wind speeds and historical demand patterns, utilization of local energy is increased by 41.6% and prices are reduced by 9.6%.

In conclusion, we have found that optimizing exclusively for price does not influence the amount of local energy used, and that optimizing exclusively for local energy does not influence the price of electricity. Finally we have also found that is is possible to make a decision model that acts as a compromise between the two, both lowering price and increasing utilization of Orkney renewables.

  1. Continuously collected from - available as CSV here. [return]
  2. Collected from - available as CSV here. [return]
  3. EVs: EV battery capacities range a lot. Estimation here is based on these examples, charge speed estimated based on the 2-3 kW home chargers described here.
    Domestic batteries: Estimation based on the Tesla Powerwall.
    Large-scale batteries: Estimation based on Tesvolt TS 48 V battery with 5 modules [return]
  4. Source: [return]
  5. Source:
    The survey differentiates between rural town and rural village. We weight Orkney as 70% town and 30% village, as the population in Kirkwall & Stromness account for approx. 70% of the population of Orkney. [return]
  6. Source: [return]
  7. For more details on the development of this model, see Section 6.2 and 10.4 in this MSc Thesis, or page 3 of the executive summary. [return]
  8. This model is somewhat crude, as it assumes that the demand will be the same no matter the time of year, which is most definitely not the case. So there is room for further improvement here. [return]
  9. Initially all 24 hours in a day, but an hour is dropped through each iteration of the inner while loop (Listing 2, line 31). [return]
  10. See [return]
  11. For this average we weight all hours equally, even though presumably more electricity is bought during the more expensive hours, since the demand is what causes the price to increase. [return]
  12. This excludes several days where we have missing data for the Orkney grid. [return]
  13. This becomes visible if we do a weekly seasonal decomposition of the Orkney demand and the N2EX prices. [return]