Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
fd1a74e
added grid power limits to interface and optimizer
ekkea Oct 4, 2025
67384c2
Merge branch 'andig:main' into 23-add-static-grid-limit-as-load-manag…
ekkea Oct 4, 2025
7fae266
Merge remote-tracking branch 'origin/main' into 23-add-static-grid-li…
ekkea Oct 10, 2025
d5950bd
syntax fix
ekkea Oct 10, 2025
476477b
fixed: grid parameters were not optional
ekkea Oct 10, 2025
ef390ad
linted
ekkea Oct 10, 2025
e9bda97
fixed: incorrect grid import and export constraints
ekkea Oct 10, 2025
d76880f
added limit violation result data
ekkea Oct 11, 2025
e565c19
linted
ekkea Oct 11, 2025
fdb5d3f
fixed implementation of demand rate
ekkea Oct 11, 2025
66f08ae
linted
ekkea Oct 11, 2025
36cc572
improved demand rate handling, added documentation
ekkea Oct 13, 2025
f40e9bc
cleaned up reported objective value
ekkea Oct 13, 2025
5e139c4
Merge remote-tracking branch 'origin/main' into 23-add-static-grid-li…
ekkea Oct 13, 2025
57b52cf
fixed objective recalculation
ekkea Oct 15, 2025
76fd6a0
improved test_app, added test cases
ekkea Oct 15, 2025
2ba1f70
fixed format
ekkea Oct 15, 2025
08eadb9
fixed: mix-up of power and energy, tie power limit exceeds to actuall…
ekkea Oct 16, 2025
ae1c362
added generation of test case file
ekkea Oct 16, 2025
11df2d1
linted
ekkea Oct 16, 2025
5cbe396
improved documentation
ekkea Oct 16, 2025
d8c9020
added test cases
ekkea Oct 16, 2025
2774dc9
removed test candidate directory, updated gitignore
ekkea Oct 16, 2025
f440b30
updated
ekkea Oct 16, 2025
342652e
fixed: objective value calculation
ekkea Oct 16, 2025
0ec70ac
updated test cases
ekkea Oct 16, 2025
3a6e823
Remove test export
andig Oct 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@
.vscode/
.venv/
__pycache__/
tmp/*
test_candidates/*
30 changes: 30 additions & 0 deletions client/client.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions cmd/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ func main() {
}

res := *resp.JSON200

if *vFlag {
b, _ := json.MarshalIndent(res, "", " ")
fmt.Println(string(b))
Expand Down
49 changes: 49 additions & 0 deletions openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,23 @@ components:
Sets a strategy for charging in situations where choices are cost neutral.
- none (default): no strategy set
- discharge_before_import: discharge batteries before importing from grid
GridConfig:
type: object
properties:
p_max_imp:
type: number
minimum: 0
description: Maximum grid import power in W
p_max_exp:
type: number
minimum: 0
description: Maximum grid export power in W
prc_p_imp_exc:
type: number
minimum: 0
description: |
price per W to consider in case the import limit is exceeded.
If not specified, the limit will be protected by a hard constraint.
BatteryConfig:
type: object
required:
Expand Down Expand Up @@ -295,6 +312,10 @@ components:
type: object
$ref: "#/components/schemas/OptimizerStrategy"
description: Strategy preferences
grid:
type: object
$ref: "#/components/schemas/GridConfig"
description: Grid configuration
batteries:
type: array
items:
Expand Down Expand Up @@ -343,6 +364,16 @@ components:
description: State of charge at each time step (Wh)
example: [21650, 19650, 16650, 14150, 12650, 11650]

LimitViolationResult:
type: object
properties:
grid_import_limit_exceeded:
type: boolean
description: The energy demand could only be satisfied by violating the grid import limit.
grid_export_limit_hit:
type: boolean
description: The solar yield in (Wh) that was reduced due to the limitation of grid export power.

OptimizationResult:
type: object
properties:
Expand All @@ -362,6 +393,10 @@ components:
nullable: true
description: Optimal objective function value (economic benefit in currency units). Null if not optimal.
example: 1250.75
limit_violations:
type: object
$ref: "#/components/schemas/LimitViolationResult"
description: status of power limit violations
batteries:
type: array
items:
Expand Down Expand Up @@ -391,6 +426,20 @@ components:
- 0: Import from grid
- 1: Export to grid
example: [0, 1, 1, 1, 1, 0]
grid_import_overshoot:
type: array
items:
type: number
minimum: 0
description: Energy above the power limit imported from grid at each time step (Wh)
example: [0, 0, 1000, 10, 0, 0]
grid_export_overshoot:
type: array
items:
type: number
minimum: 0
description: Energy not exported due to hitting the grid export power limit at each time step (Wh)
example: [0, 0, 1000, 10, 0, 0]

Error:
type: object
Expand Down
35 changes: 28 additions & 7 deletions src/evopt/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from flask import Flask, jsonify, request
from flask_restx import Api, Resource, fields

from .optimizer import BatteryConfig, OptimizationStrategy, Optimizer, TimeSeriesData
from .optimizer import BatteryConfig, GridConfig, OptimizationStrategy, Optimizer, TimeSeriesData

app = Flask(__name__)

Expand Down Expand Up @@ -45,6 +45,12 @@ def before_request_func():
'discharging_strategy': fields.String(required=False, description='Sets a strategy for discharging in situations where choices are cost neutral.')
})

grid_model = api.model('GridConfig', {
'p_max_imp': fields.Float(required=False, description='Maximum grid import power in W'),
'p_max_exp': fields.Float(required=False, description='Maximum grid export power in W'),
'prc_p_imp_exc': fields.Float(required=False, description='price per W to consider in case the import limit is exceeded. ')
})

battery_config_model = api.model('BatteryConfig', {
'charge_from_grid': fields.Boolean(required=False, description='Controls whether the battery can be charged from the grid.'),
'discharge_to_grid': fields.Boolean(required=False, description='Controls whether the battery can discharge to grid.'),
Expand All @@ -69,6 +75,7 @@ def before_request_func():

optimization_input_model = api.model('OptimizationInput', {
'strategy': fields.Nested(strategy_model, required=False, description='Optimization strategy'),
'grid': fields.Nested(grid_model, required=False, description='Grid import and export configuration'),
'batteries': fields.List(fields.Nested(battery_config_model), required=True, description='Battery configurations'),
'time_series': fields.Nested(time_series_model, required=True, description='Time series data'),
'eta_c': fields.Float(required=False, default=0.95, description='Charging efficiency'),
Expand All @@ -82,13 +89,21 @@ def before_request_func():
'state_of_charge': fields.List(fields.Float, description='State of charge at each time step (Wh)')
})

limit_violation_result_model = api.model('LimitViolationResult', {
'grid_import_limit_exceeded': fields.Boolean(description='The energy demand could only be satisfied by violating the grid import limit.'),
'grid_export_limit_hit': fields.Boolean(description='The solar yield was reduced due to the limitation of grid export power.')
})

optimization_result_model = api.model('OptimizationResult', {
'status': fields.String(description='Optimization status'),
'objective_value': fields.Float(description='Optimal objective function value'),
'limit_violations': fields.Nested(limit_violation_result_model, description='Collection of flags signalling the violation of defined limits'),
'batteries': fields.List(fields.Nested(battery_result_model), description='Battery optimization results'),
'grid_import': fields.List(fields.Float, description='Energy imported from grid at each time step (Wh)'),
'grid_export': fields.List(fields.Float, description='Energy exported to grid at each time step (Wh)'),
'flow_direction': fields.List(fields.Integer, description='Binary flow direction (1=export, 0=import)')
'flow_direction': fields.List(fields.Integer, description='Binary flow direction (1=export, 0=import)'),
'grid_import_overshoot': fields.List(fields.Float, description='Energy above the power limit imported from grid at each time step (Wh)'),
'grid_export_overshoot': fields.List(fields.Float, description='Energy not exported due to hitting the grid export power limit at each time step (Wh)')
})


Expand All @@ -108,12 +123,17 @@ def post(self):

# Parse strategy items with default values
strat_data = data.get('strategy', {})
charging_strat = strat_data.get('charging_strategy', 'none')
discharging_strat = strat_data.get('discharging_strategy', 'none')

strategy = OptimizationStrategy(
charging_strategy=charging_strat,
discharging_strategy=discharging_strat
charging_strategy=strat_data.get('charging_strategy', 'none'),
discharging_strategy=strat_data.get('discharging_strategy', 'none')
)

# parse grid configuration
grid_data = data.get('grid', {})
grid = GridConfig(
p_max_imp=grid_data.get('p_max_imp', None),
p_max_exp=grid_data.get('p_max_exp', None),
prc_p_exc_imp=grid_data.get('prc_p_exc_imp', None)
)

# Parse battery configurations
Expand Down Expand Up @@ -166,6 +186,7 @@ def post(self):
# Create and solve optimizer
optimizer = Optimizer(
strategy=strategy,
grid=grid,
batteries=batteries,
time_series=time_series,
eta_c=data.get('eta_c', 0.95),
Expand Down
Loading