Skip to content

Commit

Permalink
Merge pull request #8 from gilbertgong/ggong/consumption-prep
Browse files Browse the repository at this point in the history
preparation for consumption - slight refactor, prepare for tests, tweak graphs
  • Loading branch information
peterdudfield authored Nov 25, 2024
2 parents 2429fab + b3331e4 commit ab03167
Show file tree
Hide file tree
Showing 10 changed files with 124 additions and 47 deletions.
11 changes: 5 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ pip install solar-and-storage
Import the packages
```python
import numpy as np
import plotly.graph_objects as go
from plotly.subplots import make_subplots

from solar_and_storage.solar_and_storage import SolarAndStorage

Expand Down Expand Up @@ -52,14 +50,15 @@ result_df = solar_and_storage.get_results()

Now plot the data
```python
fig = solar_and_storage.get_fig()
fig = solar_and_storage.get_figure()

fig.show(rendered="browser")
```


![Example1](https://raw.githubusercontent.com/openclimatefix/solar-and-storage/main/examples/solar_and_storage.png)
The first plot shows the solar profile, the second shows the prices that day. The third shows the battery profile.
![Example1](examples/images/battery_solar.png)

The first plot shows the solar profile, the second shows the prices that day. The third shows the battery profile. Finally the fourth shows profit.
You can see that the battery charged from the solar site at the end of the solar maximum


Expand Down Expand Up @@ -94,4 +93,4 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d

<!-- ALL-CONTRIBUTORS-LIST:END -->

This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
5 changes: 5 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
- add tests
- refactor to use net_transfer_to_grid
- incorporate consumption
- example with 0 solar + 0 battery
- build from here
30 changes: 12 additions & 18 deletions examples/battery_only.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,29 @@
import numpy as np

from solar_and_storage.solar_and_storage import SolarAndStorage
from solar_and_storage.example import prices, no_solar

HTML_OUTPUT = "examples/battery_only.html" # Change this to your desired path or leave as an empty string
HTML_OUTPUT = "" # set path or empty will skip writing HTML
PNG_OUTPUT = "examples/images/battery_only.png" # empty will skip writing PNG

hours_per_day = 24


# make prices
prices = np.zeros(hours_per_day) + 30
prices[6:19] = 40
prices[9] = 50
prices[12:14] = 30
prices[16:18] = 50
prices[17] = 60

# make zero solar profile
solar = np.zeros(hours_per_day)

solar_and_storage = SolarAndStorage(prices=prices, solar_generation=list(solar))
solar_and_storage.run_optimization()
# use example prices and no generation profile
solar_and_storage = SolarAndStorage(prices=prices, solar_generation=list(no_solar))
result_df = solar_and_storage.get_results()

# data is available for direct access
power = result_df["power"]
e_soc = result_df["e_soc"]
solar_power_to_grid = result_df["solar_power_to_grid"]
profit = result_df["profit"]

# plot
fig = solar_and_storage.get_fig()
fig = solar_and_storage.get_figure()

fig.show(rendered="browser")
if HTML_OUTPUT:
fig.write_html(HTML_OUTPUT)
if PNG_OUTPUT:
fig.write_image(PNG_OUTPUT, format="png")
print(result_df.attrs["message"])
total_profit = solar_and_storage.get_total_profit()
print(f'total profit: {total_profit}')
32 changes: 12 additions & 20 deletions examples/battery_solar.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,37 +4,29 @@
import os

from solar_and_storage.solar_and_storage import SolarAndStorage
from solar_and_storage.example import prices, with_solar

HTML_OUTPUT = "examples/battery_solar.html" # Change this to your desired path or leave as an empty string
HTML_OUTPUT = "" # set path or empty will skip writing HTML
PNG_OUTPUT = "examples/images/battery_solar.png" # empty will skip writing PNG

hours_per_day = 24


# make prices
prices = np.zeros(hours_per_day) + 30
prices[6:19] = 40
prices[9] = 50
prices[12:14] = 30
prices[16:18] = 50
prices[17] = 60

# make solar profile
solar = np.zeros(hours_per_day)
solar[8:16] = 2.0
solar[10:14] = 4.0

solar_and_storage = SolarAndStorage(prices=prices, solar_generation=list(solar))
solar_and_storage.run_optimization()
# use example prices and solar generation profile
solar_and_storage = SolarAndStorage(prices=prices, solar_generation=list(with_solar))
result_df = solar_and_storage.get_results()

# data is available for direct access
power = result_df["power"]
e_soc = result_df["e_soc"]
solar_power_to_grid = result_df["solar_power_to_grid"]
profit = result_df["profit"]

# plot
fig = solar_and_storage.get_fig()
fig = solar_and_storage.get_figure()

fig.show(rendered="browser")
if HTML_OUTPUT:
fig.write_html(HTML_OUTPUT)
if PNG_OUTPUT:
fig.write_image(PNG_OUTPUT, format="png")
print(result_df.attrs["message"])
total_profit = solar_and_storage.get_total_profit()
print(f'total profit: {total_profit}')
Binary file added examples/images/battery_only.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/images/battery_solar.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed examples/solar_and_storage.png
Binary file not shown.
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ scipy==1.8.1
cvxopt
cvxpy==1.2.2
plotly
kaleido
25 changes: 25 additions & 0 deletions solar_and_storage/example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
""" These are used in the /examples directory as well as (planned) tests """

import numpy as np

# examples use 24 hour day for analysis period
# any granularity should be supported but only 24 hour day has been tested
hours_per_day = 24

prices = np.zeros(hours_per_day) + 30
prices[6:19] = 40
prices[9] = 50
prices[12:14] = 30
prices[16:18] = 50
prices[17] = 60

no_solar = np.zeros(hours_per_day)

with_solar = np.zeros(hours_per_day)
with_solar[8:16] = 2.0
with_solar[10:14] = 4.0

solar_generation = {
"no_solar": no_solar,
"with_solar": with_solar,
}
67 changes: 64 additions & 3 deletions solar_and_storage/solar_and_storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ def __init__(
should be between 0 and 1.
:param grid_connection_capacity: the amount of power that can be delivered to the grid
"""
self.prob = None
self.battery_soc_min = battery_soc_min
self.battery_soc_max = battery_soc_max
self.battery_capacity = battery_capacity
Expand Down Expand Up @@ -132,6 +133,13 @@ def __init__(
self.constraints = constraints
self.objective_function = objective_function

def get_status(self) -> str:
"""Runs optimization if not already run, and returns status"""

if self.prob is None:
self.run_optimization()
return self.prob.status

def run_optimization(self):
"""
Run optimization problem
Expand All @@ -145,6 +153,16 @@ def run_optimization(self):

def get_results(self) -> pd.DataFrame:
"""Get optimization results (after running)"""

status = self.get_status()

if status != "optimal":
# Return an empty DataFrame with metadata for non-optimal cases
result_df = pd.DataFrame()
result_df.attrs["status"] = status
result_df.attrs["message"] = message
return result_df

# run plot resutls
power = np.round(
self.battery_power_charge_cp_variable.value - self.power_discharge_cp_variable.value, 2
Expand All @@ -157,26 +175,69 @@ def get_results(self) -> pd.DataFrame:

data = np.array([power, e_soc[:HOURS_PER_DAY], solar_power_to_grid, profit]).transpose()

return pd.DataFrame(
result_df = pd.DataFrame(
data=data,
columns=["power", "e_soc", "solar_power_to_grid", "profit"],
)
result_df.attrs["status"] = status
result_df.attrs["message"] = "Optimization successful"

return result_df

def get_total_profit(self) -> float:
results = self.get_results()
if results.attrs["status"] != "optimal":
raise ValueError(f"Cannot calculate total profit: {results.attrs['message']}")
return sum(results["profit"])

def get_figure(self) -> go.Figure:
"""Generate figure on successful optimization"""

status = self.get_status()

if status != "optimal":
fig = go.Figure()
fig.update_layout(
title=f"Optimization Failed: {status.capitalize()}",
title_x=0.5,
)
return fig

def get_fig(self) -> go.Figure:
result_df = self.get_results()
total_profit = self.get_total_profit()

# run plot resutls
power = result_df["power"]
e_soc = result_df["e_soc"]
solar_power_to_grid = result_df["solar_power_to_grid"]
profit = result_df["profit"]

# plot
fig = make_subplots(rows=3, cols=1, subplot_titles=["Solar profile", "Price", "SOC"])
fig = make_subplots(rows=4, cols=1, subplot_titles=["Solar profile", "Price", "SOC", "Profit"])
fig.add_trace(go.Scatter(y=e_soc[:24], name="SOC"), row=3, col=1)
fig.add_trace(go.Scatter(y=self.solar_generation, name="solar", line_shape="hv"), row=1, col=1)
fig.add_trace(
go.Scatter(y=solar_power_to_grid, name="solar to gird", line_shape="hv"), row=1, col=1
)
fig.add_trace(go.Scatter(y=self.prices, name="price", line_shape="hv"), row=2, col=1)
fig.add_trace(go.Scatter(y=profit, name="profit", line_shape="hv"), row=4, col=1)

# Add title
fig.update_layout(
title="Solar and Storage Optimization Results",
title_x=0.5,
)

# Add total profit as an annotation below the chart
fig.update_layout(
annotations=[
dict(
text=f"Total Profit: {total_profit:.2f}",
yref="paper",
y=-0.2, # Position below the chart
font=dict(size=14)
)
]
)

return fig

0 comments on commit ab03167

Please sign in to comment.