File size: 7,316 Bytes
07e817b
 
 
376d985
 
07e817b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
376d985
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
import pandas as pd
import numpy as np

from data_processing import add_production_field, interpolate_and_join, monthly_analysis


def simulator_with_solar(all_data, parameters):
    demand_np = all_data['Consumption'].to_numpy()
    production_np = all_data['Production'].to_numpy()
    assert len(demand_np) == len(production_np)
    step_in_minutes = all_data.index.freq.n
    # print("Simulating for", len(demand_np), "time steps. Each step is", step_in_minutes, "minutes.")
    soc_series = [] # soc = state_of_charge.
    # by convention, we only call end user demand, demand,
    # and we only call end user consumption, consumption.
    # in our simple model, demand is always satisfied, hence demand=consumption.
    # BESS demand is called charge.
    consumption_from_solar_series = [] # demand satisfied by solar production
    consumption_from_network_series = [] # demand satisfied by network
    consumption_from_bess_series = [] # demand satisfied by BESS
    # the previous three must sum to demand_series.
    charge_of_bess_series = [] # power taken from solar by BESS. note: power never taken from network by BESS.
    discarded_production_series = [] # solar power thrown away

    # 1 is not nominal but targeted (healthy) maximum charge.
    # we start with an empty battery, but not emptier than what's healthy for the batteries.

    # For the sake of simplicity 0 <= soc <=1
    # soc=0 means battery is emptied till it's 20% and soc=1 means battery is charged till 80% of its capacity
    # soc = 1 - maximal_depth_of_discharge
    # and will use only maximal_depth_of_discharge percent of the real battery capacity
    soc = 0
    max_cap_of_battery = parameters.bess_capacity * parameters.maximal_depth_of_discharge
    cap_of_battery = soc * max_cap_of_battery

    time_interval = step_in_minutes / 60 # amount of time step in hours
    for i, (demand, production) in enumerate(zip(demand_np, production_np)):

        # these five are modified on the appropriate codepaths:
        consumption_from_solar = 0
        consumption_from_bess = 0
        consumption_from_network = 0
        discarded_production = 0

        unsatisfied_demand = demand
        remaining_production = production

        # crucially, we never charge the BESS from the network.
        # if demand >= production:
        #   all goes to demand
        #   we try to cover the rest from BESS
        #   we cover the rest from network
        # else:
        #   demand fully satisfied by production
        #   if exploitable production still remains:
        #     if is_battery_chargeable:
        #       charge_battery
        #     else:
        #       log discarded production

        #battery_charged_enough = (soc > 1- maximal_depth_of_discharge)
        
        if parameters.bess_present:
            is_battery_charged_enough = soc > 0
            is_battery_chargeable = soc < 1.0
        else:
            is_battery_charged_enough = soc <= 0
            is_battery_chargeable = soc >= 1.0

        if unsatisfied_demand >= remaining_production:
            # all goes to demand
            consumption_from_solar = remaining_production
            unsatisfied_demand -= consumption_from_solar
            remaining_production = 0
            # we try to cover the rest from BESS
            if (unsatisfied_demand > 0 ) and parameters.bess_present:
                if is_battery_charged_enough:
                    # battery capacity is limited!
                    if cap_of_battery >= unsatisfied_demand * time_interval :
                        consumption_from_bess = unsatisfied_demand
                        unsatisfied_demand = 0
                        cap_of_battery -= consumption_from_bess * time_interval
                        soc = cap_of_battery / max_cap_of_battery
                    else:
                        discharge_of_bess = cap_of_battery / time_interval
                        discharge = min(parameters.bess_discharge, discharge_of_bess)
                        consumption_from_bess = discharge
                        unsatisfied_demand -= consumption_from_bess
                        cap_of_battery -= consumption_from_bess * time_interval
                        soc = cap_of_battery / max_cap_of_battery
                        consumption_from_network = unsatisfied_demand
                        unsatisfied_demand = 0
                else:
                    # we cover the rest from network
                    consumption_from_network = unsatisfied_demand
                    unsatisfied_demand = 0
        else:
            # demand fully satisfied by production
            consumption_from_solar = unsatisfied_demand
            remaining_production -= unsatisfied_demand
            unsatisfied_demand = 0
            if (remaining_production > 0) and parameters.bess_present:
                # exploitable production still remains:
                if is_battery_chargeable:
                    # we try to specify the BESS modell
                    if parameters.bess_charge <= remaining_production :
                        energy = parameters.bess_charge * time_interval
                        remaining_production = remaining_production - parameters.bess_charge
                    else :
                        energy = remaining_production * time_interval
                        remaining_production = 0
                    cap_of_battery += energy
                    soc = cap_of_battery / max_cap_of_battery

        discarded_production = remaining_production

        soc_series.append(soc)
        consumption_from_solar_series.append(consumption_from_solar)
        consumption_from_network_series.append(consumption_from_network)
        consumption_from_bess_series.append(consumption_from_bess)
        charge_of_bess_series.append(soc)
        discarded_production_series.append(discarded_production)

    soc_series = np.array(soc_series)
    consumption_from_solar_series = np.array(consumption_from_solar_series)
    consumption_from_network_series = np.array(consumption_from_network_series)
    consumption_from_bess_series = np.array(consumption_from_bess_series)
    charge_of_bess_series = np.array(charge_of_bess_series)
    discarded_production_series = np.array(discarded_production_series)

    results = pd.DataFrame({'soc_series': soc_series, 'consumption_from_solar': consumption_from_solar_series,
                            'consumption_from_network': consumption_from_network_series,
                            'consumption_from_bess': consumption_from_bess_series,
                            'charge_of_bess': charge_of_bess_series,
                            'discarded_production': discarded_production_series,
                            'Consumption': all_data['Consumption'],
                            'Production': all_data['Production']
                            })
    results = results.set_index(all_data.index)
    return results


def evaluate_parameters(parameters, met_2021_data, cons_2021_data):
    add_production_field(met_2021_data, parameters)
    all_2021_data = interpolate_and_join(met_2021_data, cons_2021_data)
    results = simulator_with_solar(all_2021_data, parameters)
    consumptions_in_mwh = monthly_analysis(results)
    return consumptions_in_mwh.sum(axis=0)