Spaces:
Sleeping
Sleeping
Daniel Varga commited on
Commit ·
fcbe190
1
Parent(s): 07d9d06
reproducing previous version
Browse files- v2/app.py +82 -37
- v2/data_processing.py +5 -2
v2/app.py
CHANGED
|
@@ -4,6 +4,7 @@
|
|
| 4 |
import numpy as np
|
| 5 |
import pandas as pd
|
| 6 |
import gradio as gr
|
|
|
|
| 7 |
|
| 8 |
# from simulation import *
|
| 9 |
from data_processing import *
|
|
@@ -24,26 +25,14 @@ met_2021_data, cons_2021_data = read_datasets()
|
|
| 24 |
# TODO move out everything that should not be recalculated.
|
| 25 |
# TODO actually use uiParameters. (base_price and peak_price were just to mock up the Accordion gui.)
|
| 26 |
# TODO ui_refresh spawns its own Supplier, which is just dumb.
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
parameters = SolarParameters()
|
| 32 |
-
for k, v in uiParameters.items():
|
| 33 |
-
setattr(parameters, k, v)
|
| 34 |
-
|
| 35 |
np.random.seed(1)
|
| 36 |
|
| 37 |
-
supplier =
|
| 38 |
-
# nine-to-five increased price.
|
| 39 |
-
supplier.set_price_for_daily_interval(9, 17, 150)
|
| 40 |
-
# midnight-to-three decreased price, to test network charge.
|
| 41 |
-
supplier.set_price_for_daily_interval(0, 3, 20)
|
| 42 |
-
# peak_demand dimension is kWh, but it's interpreted as the full consumption
|
| 43 |
-
# during a 15 minute timestep.
|
| 44 |
-
supplier.set_demand_charge(peak_demand=2.5, surcharge_per_kwh=500) # kWh in a 15 minutes interval, Ft/kWh
|
| 45 |
|
| 46 |
-
solar_parameters = SolarParameters()
|
| 47 |
|
| 48 |
add_production_field(met_2021_data, solar_parameters)
|
| 49 |
all_data = interpolate_and_join(met_2021_data, cons_2021_data)
|
|
@@ -51,14 +40,16 @@ def recalculate(**uiParameters):
|
|
| 51 |
time_interval_min = all_data.index.freq.n
|
| 52 |
time_interval_h = time_interval_min / 60
|
| 53 |
|
|
|
|
|
|
|
| 54 |
# for faster testing:
|
| 55 |
DATASET_TRUNCATED_SIZE = None
|
| 56 |
if DATASET_TRUNCATED_SIZE is not None:
|
| 57 |
print("Truncating dataset to", DATASET_TRUNCATED_SIZE, "datapoints, that is", DATASET_TRUNCATED_SIZE * time_interval_h / 24, "days")
|
| 58 |
all_data = all_data.iloc[:DATASET_TRUNCATED_SIZE]
|
| 59 |
|
| 60 |
-
if
|
| 61 |
-
all_data['Consumption'] =
|
| 62 |
|
| 63 |
print("Working with", solar_parameters.solar_cell_num, "solar cells, that's a maximum production of", all_data['Production'].max(), "kW.")
|
| 64 |
|
|
@@ -69,22 +60,63 @@ def recalculate(**uiParameters):
|
|
| 69 |
# we delete the supplier to avoid accidentally calling it instead of precalculated_supplier
|
| 70 |
supplier = None
|
| 71 |
|
| 72 |
-
all_data_with_predictions['Consumption_fees'] = precalculated_supplier.consumption_fees # [HUF / kWh]
|
| 73 |
-
|
| 74 |
-
battery_model = BatteryModel(capacity_Ah=600, time_interval_h=time_interval_h)
|
| 75 |
-
|
| 76 |
# param_1 is prob of choosing PASSIVE
|
| 77 |
# param_2 is prob of choosing NETWORK_CHARGE
|
|
|
|
|
|
|
| 78 |
decider = RandomDecider(np.array([0.0, 0.0]), precalculated_supplier)
|
| 79 |
|
|
|
|
|
|
|
| 80 |
results, total_network_fee = simulator(battery_model, all_data_with_predictions, decider)
|
| 81 |
print(f"{total_network_fee=}")
|
| 82 |
return results
|
| 83 |
|
| 84 |
|
| 85 |
-
|
| 86 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
|
|
|
|
| 88 |
fig1 = plotly_visualize_simulation(results, date_range=("2021-02-01", "2021-02-07"))
|
| 89 |
fig2 = plotly_visualize_simulation(results, date_range=("2021-08-02", "2021-08-08"))
|
| 90 |
|
|
@@ -98,11 +130,7 @@ def ui_refresh(solar_cell_num, bess_nominal_capacity, fixed_consumption, base_pr
|
|
| 98 |
for column, column_name in zip((network, solar, bess), ("Network", "Solar directly", "Solar via BESS")):
|
| 99 |
html += f"<tr><td>Yearly consumption served by {column_name}: </td><td>{column:0.2f} MWh</td></tr>\n"
|
| 100 |
|
| 101 |
-
supplier =
|
| 102 |
-
supplier.set_price_for_daily_interval_on_workdays(start=6, end=22, price=100)
|
| 103 |
-
|
| 104 |
-
# not realistic, just for testing the effect
|
| 105 |
-
# supplier.set_demand_charge(peak_demand=100, surcharge_per_kw=1000)
|
| 106 |
|
| 107 |
fee = supplier.fee(results["consumption_from_network"])
|
| 108 |
html += f"<tr><td>{fee/1e6:.3f} million HUF billed by energy supplier</td></tr>\n"
|
|
@@ -116,13 +144,22 @@ with gr.Blocks() as ui:
|
|
| 116 |
with gr.Row():
|
| 117 |
# LEFT: Input controls
|
| 118 |
with gr.Column(scale=1): # narrower column
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 122 |
|
| 123 |
-
with gr.Accordion("
|
| 124 |
-
|
| 125 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 126 |
|
| 127 |
run_btn = gr.Button("Run Simulation")
|
| 128 |
|
|
@@ -133,9 +170,17 @@ with gr.Blocks() as ui:
|
|
| 133 |
plot2 = gr.Plot()
|
| 134 |
plot3 = gr.Plot()
|
| 135 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 136 |
run_btn.click(
|
| 137 |
ui_refresh,
|
| 138 |
-
inputs=
|
|
|
|
| 139 |
outputs=[html_out, plot1, plot2, plot3],
|
| 140 |
)
|
| 141 |
|
|
|
|
| 4 |
import numpy as np
|
| 5 |
import pandas as pd
|
| 6 |
import gradio as gr
|
| 7 |
+
from types import SimpleNamespace
|
| 8 |
|
| 9 |
# from simulation import *
|
| 10 |
from data_processing import *
|
|
|
|
| 25 |
# TODO move out everything that should not be recalculated.
|
| 26 |
# TODO actually use uiParameters. (base_price and peak_price were just to mock up the Accordion gui.)
|
| 27 |
# TODO ui_refresh spawns its own Supplier, which is just dumb.
|
| 28 |
+
# TODO some gui to upload consumption data.
|
| 29 |
+
# and maybe solar data? but we don't have a solar data -> production model built in. just scale and switch years from a dropdown?
|
| 30 |
+
def recalculate(ui):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
np.random.seed(1)
|
| 32 |
|
| 33 |
+
supplier = create_supplier(ui)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
|
| 35 |
+
solar_parameters = SolarParameters(solar_cell_num=ui.solar_cell_num, panel_power_at_NOCT=ui.solar_cell_nominal_capacity)
|
| 36 |
|
| 37 |
add_production_field(met_2021_data, solar_parameters)
|
| 38 |
all_data = interpolate_and_join(met_2021_data, cons_2021_data)
|
|
|
|
| 40 |
time_interval_min = all_data.index.freq.n
|
| 41 |
time_interval_h = time_interval_min / 60
|
| 42 |
|
| 43 |
+
battery_model = BatteryModel(capacity_Ah=600, time_interval_h=time_interval_h)
|
| 44 |
+
|
| 45 |
# for faster testing:
|
| 46 |
DATASET_TRUNCATED_SIZE = None
|
| 47 |
if DATASET_TRUNCATED_SIZE is not None:
|
| 48 |
print("Truncating dataset to", DATASET_TRUNCATED_SIZE, "datapoints, that is", DATASET_TRUNCATED_SIZE * time_interval_h / 24, "days")
|
| 49 |
all_data = all_data.iloc[:DATASET_TRUNCATED_SIZE]
|
| 50 |
|
| 51 |
+
if ui.fixed_consumption_kW is not None:
|
| 52 |
+
all_data['Consumption'] = ui.fixed_consumption_kW
|
| 53 |
|
| 54 |
print("Working with", solar_parameters.solar_cell_num, "solar cells, that's a maximum production of", all_data['Production'].max(), "kW.")
|
| 55 |
|
|
|
|
| 60 |
# we delete the supplier to avoid accidentally calling it instead of precalculated_supplier
|
| 61 |
supplier = None
|
| 62 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
# param_1 is prob of choosing PASSIVE
|
| 64 |
# param_2 is prob of choosing NETWORK_CHARGE
|
| 65 |
+
# so this corresponds to constant DISCHARGE mode, where the priorities are:
|
| 66 |
+
# 1. try solar 2. try bess 3. try network
|
| 67 |
decider = RandomDecider(np.array([0.0, 0.0]), precalculated_supplier)
|
| 68 |
|
| 69 |
+
all_data_with_predictions['Consumption_fees'] = precalculated_supplier.consumption_fees # [HUF / kWh]
|
| 70 |
+
|
| 71 |
results, total_network_fee = simulator(battery_model, all_data_with_predictions, decider)
|
| 72 |
print(f"{total_network_fee=}")
|
| 73 |
return results
|
| 74 |
|
| 75 |
|
| 76 |
+
# that's very error-prone, sorry. must be kept in sync with the gradio button click call.
|
| 77 |
+
# secondarily, with recalculate().
|
| 78 |
+
def list_to_namespace(
|
| 79 |
+
solar_cell_num, solar_cell_nominal_capacity,
|
| 80 |
+
bess_capacity_Ah, bess_voltage_V, bess_charge_kW, bess_discharge_kW,
|
| 81 |
+
fixed_consumption_kW,
|
| 82 |
+
base_price_HUFpkWh, peak_price_HUFpkWh):
|
| 83 |
+
return SimpleNamespace(
|
| 84 |
+
solar_cell_num=solar_cell_num,
|
| 85 |
+
solar_cell_nominal_capacity=solar_cell_nominal_capacity,
|
| 86 |
+
bess_capacity_Ah=bess_capacity_Ah,
|
| 87 |
+
bess_voltage_V=bess_voltage_V,
|
| 88 |
+
bess_charge_kW=bess_charge_kW,
|
| 89 |
+
bess_discharge_kW=bess_discharge_kW,
|
| 90 |
+
fixed_consumption_checkbox=fixed_consumption_checkbox,
|
| 91 |
+
fixed_consumption_kW=fixed_consumption_kW,
|
| 92 |
+
base_price_HUFpkWh=base_price_HUFpkWh,
|
| 93 |
+
peak_price_HUFpkWh=peak_price_HUFpkWh,
|
| 94 |
+
)
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
def create_supplier(ui):
|
| 98 |
+
supplier = Supplier(price=ui.base_price_HUFpkWh)
|
| 99 |
+
supplier.set_price_for_daily_interval_on_workdays(start=6, end=22, price=ui.peak_price_HUFpkWh)
|
| 100 |
+
return supplier
|
| 101 |
+
|
| 102 |
+
def ui_refresh(
|
| 103 |
+
solar_cell_num, solar_cell_nominal_capacity,
|
| 104 |
+
bess_capacity_Ah, bess_voltage_V, bess_charge_kW, bess_discharge_kW,
|
| 105 |
+
fixed_consumption_checkbox, fixed_consumption_kW,
|
| 106 |
+
base_price_HUFpkWh, peak_price_HUFpkWh):
|
| 107 |
+
|
| 108 |
+
if not fixed_consumption_checkbox:
|
| 109 |
+
fixed_consumption_kW = None # None means use dataframe.
|
| 110 |
+
|
| 111 |
+
ui = list_to_namespace(
|
| 112 |
+
solar_cell_num, solar_cell_nominal_capacity,
|
| 113 |
+
bess_capacity_Ah, bess_voltage_V, bess_charge_kW, bess_discharge_kW,
|
| 114 |
+
fixed_consumption_kW,
|
| 115 |
+
base_price_HUFpkWh, peak_price_HUFpkWh)
|
| 116 |
+
|
| 117 |
+
results = recalculate(ui)
|
| 118 |
|
| 119 |
+
# TODO generalize to other input ranges
|
| 120 |
fig1 = plotly_visualize_simulation(results, date_range=("2021-02-01", "2021-02-07"))
|
| 121 |
fig2 = plotly_visualize_simulation(results, date_range=("2021-08-02", "2021-08-08"))
|
| 122 |
|
|
|
|
| 130 |
for column, column_name in zip((network, solar, bess), ("Network", "Solar directly", "Solar via BESS")):
|
| 131 |
html += f"<tr><td>Yearly consumption served by {column_name}: </td><td>{column:0.2f} MWh</td></tr>\n"
|
| 132 |
|
| 133 |
+
supplier = create_supplier(ui)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
|
| 135 |
fee = supplier.fee(results["consumption_from_network"])
|
| 136 |
html += f"<tr><td>{fee/1e6:.3f} million HUF billed by energy supplier</td></tr>\n"
|
|
|
|
| 144 |
with gr.Row():
|
| 145 |
# LEFT: Input controls
|
| 146 |
with gr.Column(scale=1): # narrower column
|
| 147 |
+
solar_cell_num = gr.Slider(0, 3000, 1140, label="number of installed solar cells")
|
| 148 |
+
solar_cell_nominal_capacity = gr.Slider(0, 1000, 280, label="solar cell nominal capacity at NOCT [W]")
|
| 149 |
+
|
| 150 |
+
bess_capacity_Ah = gr.Slider(0, 2000, 330, label="BESS nominal capacity [Ah]")
|
| 151 |
+
|
| 152 |
+
fixed_consumption_checkbox = gr.Checkbox(value=False, label="Use fixed consumption")
|
| 153 |
+
fixed_consumption_kW = gr.Slider(0, 100, 10, label="Amount of fixed consumption [kW]")
|
| 154 |
|
| 155 |
+
with gr.Accordion("Extra BESS settings", open=False):
|
| 156 |
+
bess_voltage_V = gr.Number(value=600, label="BESS voltage [V]")
|
| 157 |
+
bess_charge_kW = gr.Number(value=50, label="BESS charge [kW]")
|
| 158 |
+
bess_discharge_kW = gr.Number(value=60, label="BESS discharge [kW]")
|
| 159 |
+
|
| 160 |
+
with gr.Accordion("Pricing settings", open=False):
|
| 161 |
+
peak_price_HUFpkWh = gr.Number(value=60, label="Peak energy price [HUF/kWh]")
|
| 162 |
+
base_price_HUFpkWh = gr.Number(value=60, label="Base energy price [HUF/kWh]")
|
| 163 |
|
| 164 |
run_btn = gr.Button("Run Simulation")
|
| 165 |
|
|
|
|
| 170 |
plot2 = gr.Plot()
|
| 171 |
plot3 = gr.Plot()
|
| 172 |
|
| 173 |
+
inputs = [
|
| 174 |
+
solar_cell_num, solar_cell_nominal_capacity,
|
| 175 |
+
bess_capacity_Ah, bess_voltage_V, bess_charge_kW, bess_discharge_kW,
|
| 176 |
+
fixed_consumption_checkbox, fixed_consumption_kW,
|
| 177 |
+
base_price_HUFpkWh, peak_price_HUFpkWh
|
| 178 |
+
]
|
| 179 |
+
|
| 180 |
run_btn.click(
|
| 181 |
ui_refresh,
|
| 182 |
+
inputs=inputs,
|
| 183 |
+
# [solar_cell_num_slider, bess_slider, fixed_consumption_slider, base_price, peak_price],
|
| 184 |
outputs=[html_out, plot1, plot2, plot3],
|
| 185 |
)
|
| 186 |
|
v2/data_processing.py
CHANGED
|
@@ -46,15 +46,18 @@ def read_datasets(mini=False):
|
|
| 46 |
class SolarParameters:
|
| 47 |
solar_cell_num: float = 1140 # units
|
| 48 |
solar_efficiency: float = 0.93 * 0.96 # [dimensionless]
|
| 49 |
-
|
|
|
|
| 50 |
NOCT_irradiation: float = 800 # [W/m^2]
|
| 51 |
|
| 52 |
|
| 53 |
# mutates met_2021_data
|
| 54 |
def add_production_field(met_2021_data, parameters):
|
|
|
|
| 55 |
sr = met_2021_data['sr']
|
| 56 |
|
| 57 |
-
|
|
|
|
| 58 |
nop_total = nop_total.clip(0)
|
| 59 |
met_2021_data['Production'] = nop_total
|
| 60 |
|
|
|
|
| 46 |
class SolarParameters:
|
| 47 |
solar_cell_num: float = 1140 # units
|
| 48 |
solar_efficiency: float = 0.93 * 0.96 # [dimensionless]
|
| 49 |
+
panel_power_at_NOCT: float = 280 # [W]
|
| 50 |
+
# this is the SR (solar radiation) level where panel_power_at_NOCT is produced:
|
| 51 |
NOCT_irradiation: float = 800 # [W/m^2]
|
| 52 |
|
| 53 |
|
| 54 |
# mutates met_2021_data
|
| 55 |
def add_production_field(met_2021_data, parameters):
|
| 56 |
+
# sr has dimension W/m^2.
|
| 57 |
sr = met_2021_data['sr']
|
| 58 |
|
| 59 |
+
# TODO use something a bit more fancy nonlinear if we have the temperature anyway.
|
| 60 |
+
nop_total = sr * parameters.solar_cell_num * parameters.solar_efficiency * parameters.panel_power_at_NOCT / parameters.NOCT_irradiation / 1e3
|
| 61 |
nop_total = nop_total.clip(0)
|
| 62 |
met_2021_data['Production'] = nop_total
|
| 63 |
|