Skip to content

Commit 8653c54

Browse files
committed
initial commit
0 parents  commit 8653c54

27 files changed

+4864
-0
lines changed

.github/workflows/lint.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: Ruff Lint
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
- 'feature/*'
8+
- develop
9+
pull_request:
10+
branches:
11+
- main
12+
- 'feature/*'
13+
- develop
14+
15+
jobs:
16+
pre-commit:
17+
runs-on: ubuntu-latest
18+
steps:
19+
- uses: actions/checkout@v4
20+
21+
- name: Set up Python
22+
uses: actions/setup-python@v5
23+
with:
24+
python-version: '3.12'
25+
26+
- name: Install uv
27+
uses: astral-sh/setup-uv@v4
28+
29+
- name: Install dependencies
30+
run: uv sync
31+
32+
- name: Run Ruff
33+
run: uv run ruff check

.github/workflows/tests.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
name: Unit Tests
2+
3+
on: [push, pull_request]
4+
5+
jobs:
6+
test:
7+
runs-on: ubuntu-latest
8+
steps:
9+
- uses: actions/checkout@v4
10+
11+
- name: Set up Python
12+
uses: actions/setup-python@v5
13+
with:
14+
python-version: '3.12'
15+
16+
- name: Install uv
17+
uses: astral-sh/setup-uv@v4
18+
19+
- name: Install dependencies
20+
run: uv sync
21+
22+
- name: Run tests
23+
run: uv run pytest

.gitignore

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# Archive files
2+
*.tar
3+
*.zip
4+
*.gzip
5+
*.gz
6+
7+
# Data files
8+
*.csv
9+
*.txt
10+
*.parquet
11+
*.xls
12+
13+
# External configurations
14+
.claude/
15+
.vscode/
16+
17+
# Virtual envs
18+
.venv*
19+
20+
# pytest
21+
.coverage
22+
23+
# Pickle files
24+
*.pkl
25+
26+
# Generated figures
27+
*.pdf
28+
*.png
29+
30+
# Byte-compiled / optimized / DLL files
31+
__pycache__/
32+
*.py[cod]
33+
*$py.class
34+
35+
# Jupyter Notebook
36+
.ipynb_checkpoints
37+
38+
# Environments
39+
.env
40+
41+
# Mac file systems
42+
*.DS_Store
43+
44+
# Setup.py
45+
.eggs
46+
*.egg-info
47+
48+
# LaTeX
49+
*.aux
50+
*.bbl
51+
*.bcf
52+
*.blg
53+
*.dvi
54+
*.log
55+
*.out
56+
*.synctex.gz
57+
*.toc
58+
*.xml
59+

.pre-commit-config.yaml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
repos:
2+
- repo: local
3+
hooks:
4+
- id: ruff
5+
name: ruff
6+
entry: uv run ruff check
7+
language: system
8+
types: [python]
9+
stages: [pre-commit]
10+
11+
- repo: local
12+
hooks:
13+
- id: run-pytest
14+
name: Run pytest before push
15+
entry: uv run pytest
16+
language: system
17+
pass_filenames: false
18+
always_run: true
19+
stages: [pre-push]

.python-version

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3.12

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2023 NoviaIntSysGroup
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
<p align="center">
2+
<img src="images/logo.png" alt="load-shift-optimizer logo" width="256px" >
3+
</p>
4+
5+
<h2 align="center"> Shift loads optimally to minimize electricity costs </h2>
6+
7+
<div align="center">
8+
9+
10+
[![Ruff Lint](https://github.com/NoviaIntSysGroup/load-shift-optimizer/actions/workflows/lint.yml/badge.svg)](https://github.com/NoviaIntSysGroup/load-shift-optimizer/actions/workflows/lint.yml)
11+
[![Unit Tests](https://github.com/NoviaIntSysGroup/load-shift-optimizer/actions/workflows/tests.yml/badge.svg)](https://github.com/NoviaIntSysGroup/load-shift-optimizer/actions/workflows/tests.yml)
12+
13+
14+
![Python](https://img.shields.io/badge/python-3.12%2B-blue)
15+
![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)
16+
17+
</div>
18+
19+
<div align="center">
20+
21+
`loadshift` helps you optimally shift loads based on known prices and a given flexibility level, where the flexibility level indicates how many hours earlier or later a load can run. The package can be used to determine optimal load shifts based on day-ahead electricity prices, or to evaluate potential savings from various load-shifting scenarios.
22+
23+
</div>
24+
25+
## Features
26+
27+
***Cost Optimization** - Shift loads optimally to minimize costs based on known electricity prices.
28+
* 🎛️ **Flexible Constraints** - Define how many hours loads can shift earlier or later, transfer rate limits, and power capacity to match your use case.
29+
* 📅 **Moving Horizon** - Daily optimization approach that replicates real-world day-ahead market scenarios.
30+
31+
## Example
32+
The example below shows how electricity prices and residential loads change over a typical day: prices peak in the morning and evening, while residential loads peak in the evening when people get home. The two rightmost panels illustrate the results of shifting loads optimally for two flexibility levels: ±2 hours and ±4 hours. Observe how more and more consumption shifts towards nighttime and the afternoon as the flexibility level increases.
33+
34+
35+
<p align="center">
36+
<img src="examples/load_shift_example.png" style="max-width: 600px; width: 100%; height: auto;" />
37+
</p>
38+
39+
## Installation
40+
41+
### From PyPI (recommended)
42+
43+
To use `load-shift-optimizer` in your project, install it from PyPI:
44+
45+
```bash
46+
pip install loadshift
47+
```
48+
49+
By default, this installs the free open-source MIP solver (CBC backend). For better performance on large problems, you can install with Gurobi support if you have a Gurobi license:
50+
51+
```bash
52+
pip install loadshift[gurobi]
53+
```
54+
55+
### From source (for examples and development)
56+
57+
If you want to explore the examples or contribute to the project, follow these steps to install from source:
58+
59+
```bash
60+
# clone repository
61+
$ git clone https://github.com/NoviaIntSysGroup/load-shift-optimizer.git
62+
$ cd load-shift-optimizer
63+
64+
# install package and development dependencies (with MIP solver)
65+
$ uv sync
66+
67+
# OR install with Gurobi support (requires a license)
68+
$ uv sync --extra gurobi
69+
```
70+
71+
## Usage
72+
73+
### Basic Optimization
74+
75+
```python
76+
import numpy as np
77+
from loadshift import LoadShifter
78+
79+
# Define your price and demand data
80+
price = np.array([30, 80, 20, 40, 35, 25]) # ct/kWh
81+
demand = np.array([10, 15, 8, 12, 10, 9]) # kWh
82+
83+
# Create optimizer with flexibility constraints
84+
optimizer = LoadShifter(
85+
max_demand_advance=2, # Can shift loads up to 2 hours earlier
86+
max_demand_delay=3, # Can shift loads up to 3 hours later
87+
max_hourly_purchase=20, # Maximum 20 kWh per hour
88+
max_rate=10 # Maximum 10 kW transfer rate
89+
)
90+
91+
# Optimize demand
92+
result = optimizer.optimize_demand(price, demand)
93+
94+
print("Optimal demand:", result["optimal_demand"])
95+
print("Demand shift:", result["optimal_shift"])
96+
```
97+
98+
### Moving Horizon Optimization
99+
100+
```python
101+
import pandas as pd
102+
from loadshift import moving_horizon
103+
104+
# Create DataFrames with a datetime index
105+
index = pd.date_range("2024-01-01", periods=72, freq="h")
106+
price_data = pd.DataFrame({"price": price_values}, index=index)
107+
demand_data = pd.DataFrame({"demand": demand_values}, index=index)
108+
109+
# Configuration
110+
config = {
111+
"daily_decision_hour": 12, # Make decisions at noon each day
112+
"n_lookahead_hours": 36, # Look ahead 36 hours
113+
"load_shift": {
114+
"max_demand_advance": 2,
115+
"max_demand_delay": 3,
116+
"max_hourly_purchase": 20,
117+
"max_rate": 10
118+
}
119+
}
120+
121+
# Run moving horizon optimization
122+
result = moving_horizon(price_data, demand_data, config)
123+
124+
# Access optimized demand and shifts
125+
optimized = result["results"]
126+
print(optimized.head())
127+
```
128+
129+
## Development
130+
131+
Install development requirements and set up the hooks:
132+
133+
```bash
134+
uv sync
135+
uv run pre-commit install --hook-type pre-commit --hook-type pre-push
136+
```
137+
138+
Before committing or pushing run:
139+
140+
```bash
141+
uv run ruff check .
142+
uv run pytest
143+
```
144+
145+
## Contributing
146+
147+
We welcome contributions! Please follow these steps:
148+
149+
1. Fork the repository
150+
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
151+
3. Make your changes
152+
4. Run the test suite (`uv run pytest`)
153+
5. Commit your changes (`git commit -m 'Add amazing feature'`)
154+
6. Push to the branch (`git push origin feature/amazing-feature`)
155+
7. Open a Pull Request
156+
157+
Please ensure your code follows our style guidelines:
158+
- Use Ruff for code formatting and linting
159+
- Follow Google's Python style guide for docstrings
160+
- Include type annotations for all functions
161+
- Add tests for new functionality
162+
163+
## License
164+
165+
This project is released under the [MIT License](LICENSE).

data/example_data.csv

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
,demand,price
2+
2025-01-01 00:00:00,0.2345101639344261,0.0268965027322404
3+
2025-01-01 01:00:00,0.1809027322404371,0.0249476775956284
4+
2025-01-01 02:00:00,0.1520043989071038,0.0231079781420765
5+
2025-01-01 03:00:00,0.1359747540983606,0.0222952459016393
6+
2025-01-01 04:00:00,0.1311820491803278,0.0228220765027322
7+
2025-01-01 05:00:00,0.1422871584699453,0.0282744808743169
8+
2025-01-01 06:00:00,0.1782268032786885,0.0428678961748633
9+
2025-01-01 07:00:00,0.2066672677595628,0.0609973497267759
10+
2025-01-01 08:00:00,0.2310762021857924,0.0697775409836065
11+
2025-01-01 09:00:00,0.2442133606557377,0.0637794535519125
12+
2025-01-01 10:00:00,0.2526845081967213,0.0568427595628415
13+
2025-01-01 11:00:00,0.2602418032786885,0.0513094808743169
14+
2025-01-01 12:00:00,0.2529652185792349,0.0481831967213114
15+
2025-01-01 13:00:00,0.2439892076502732,0.0453708196721311
16+
2025-01-01 14:00:00,0.2485369398907103,0.0435783060109289
17+
2025-01-01 15:00:00,0.2742496721311476,0.0453695901639344
18+
2025-01-01 16:00:00,0.3187727049180328,0.0537437704918032
19+
2025-01-01 17:00:00,0.3558767486338798,0.0594277322404371
20+
2025-01-01 18:00:00,0.3982414480874316,0.0675186612021857
21+
2025-01-01 19:00:00,0.4331073224043715,0.0680278142076502
22+
2025-01-01 20:00:00,0.4226514480874316,0.0542883060109289
23+
2025-01-01 21:00:00,0.3624636612021857,0.0448266120218579
24+
2025-01-01 22:00:00,0.3936629781420765,0.038751693989071
25+
2025-01-01 23:00:00,0.3160239178082191,0.0305900546448087

0 commit comments

Comments
 (0)