Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Features/schedule profiles #869

Draft
wants to merge 6 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
98 changes: 98 additions & 0 deletions examples/min_max_uptime_example/min_max_uptime_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# -*- coding: utf-8 -*-
"""
Example that shows how to use a combination of minimum and maximum uptime.

The model consists of a volatile renewable energy source with
a fixed profile (Source: 'renewable_energy'), a cyclic industrial process
modelled as a Sink with a NonConvex flow with negative variable costs as
revenues, and an excess for surplus of renewable energy.

The cyclic industrial process has a fixed block profile and a certain time
within each cycle is required. With the attributes `maximum_uptime`,
`minimum_uptime` and `minimum_downtime`, and a given `nominal_value`
combination with the `min` attribute it is possible to model
this scheduling problem.

SPDX-FileCopyrightText: Johannes Röder <[email protected]>

SPDX-License-Identifier: MIT
"""
import os
import logging

import pandas as pd
import matplotlib.pyplot as plt

from oemof.solph import components
from oemof.solph import Flow, Bus, EnergySystem, Model
from oemof.solph._options import NonConvex
from oemof.solph import processing, views
from oemof.solph import helpers

renewable_timeseries = [
1, 1, 2, 4, 5, 6, 4, 6, 2, 6, 7, 6,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
8, 8, 2, 1, 0, 5, 6, 7, 6, 7, 3,
3, 1, 1,
]

# select periods
periods = len(renewable_timeseries)

# create an energy system
idx = pd.date_range('1/1/2022', periods=periods, freq='H')
es = EnergySystem(timeindex=idx)

bus_1 = Bus(label='bus')

es.add(bus_1)

es.add(components.Source(
label='renewable_energy',
inputs={bus_1: Flow(
fix=renewable_timeseries,
nominal_value=1,
)}
))

es.add(components.Sink(
label='cyclic_process',
outputs={bus_1: Flow(
variable_costs=-10,
nominal_value=4,
min=1,
nonconvex=NonConvex(
minimum_downtime=2,
minimum_uptime=4,
maximum_uptime=4,
),
)},
))

es.add(components.Sink(
label='excess',
outputs={bus_1: Flow(
variable_costs=0,
)},
))

# create an optimization problem and solve it
om = Model(es)

# export lp file
filename = os.path.join(
helpers.extend_basic_path('lp_files'), 'NonconvexDSM.lp')
logging.info('Store lp-file in {0}.'.format(filename))
om.write(filename, io_options={'symbolic_solver_labels': True})

# solve model
om.solve(solver='cbc', solve_kwargs={'tee': True})

# create result object
results = processing.results(om)

bus1 = views.node(results, 'bus')["sequences"]
bus1.plot(kind='line', drawstyle='steps-mid')
plt.ylim(None, 10)
plt.legend()
plt.show()
103 changes: 103 additions & 0 deletions examples/schedule_profiles_example/schedule_profiles_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# -*- coding: utf-8 -*-
"""
Example that shows how to use a combination of minimum and maximum uptime.

The model consists of a volatile renewable energy source with
a fixed profile (Source: 'renewable_energy'), a cyclic industrial process
modelled as a Sink with a NonConvex flow with negative variable costs as
revenues, and an excess for surplus of renewable energy.

The cyclic industrial process has a fixed block profile and a certain time
within each cycle is required. With the attributes `maximum_uptime`,
`minimum_uptime` and `minimum_downtime`, and a given `nominal_value`
combination with the `min` attribute it is possible to model
this scheduling problem.

SPDX-FileCopyrightText: Johannes Röder <[email protected]>

SPDX-License-Identifier: MIT
"""
import os
import logging

import pandas as pd
import matplotlib.pyplot as plt

from oemof.solph import components
from oemof.solph import Flow, Bus, EnergySystem, Model
from oemof.solph._options import NonConvex
from oemof.solph import processing, views
from oemof.solph import helpers

renewable_timeseries = [
1, 1, 2, 4, 5, 6, 4, 6, 2, 6, 7, 6,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
8, 8, 2, 1, 0, 5, 6, 7, 6, 7, 3,
3, 1, 1,
]

# select periods
periods = len(renewable_timeseries)

# create an energy system
idx = pd.date_range('1/1/2022', periods=periods, freq='H')
es = EnergySystem(timeindex=idx)

bus_1 = Bus(label='bus')

es.add(bus_1)

es.add(components.Source(
label='renewable_energy',
outputs={bus_1: Flow(
fix=renewable_timeseries,
nominal_value=1,
)}
))

es.add(components.Sink(
label='cyclic_process',
inputs={bus_1: Flow(
variable_costs=-5565,
nominal_value=10,
min=0,
nonconvex=NonConvex(
minimum_downtime=3,
minimum_uptime=4,
maximum_uptime=4,
profile=[2, 3, 4, 5],
startup_costs=10,
# shutdown_costs=2,
),
)},
))

es.add(components.Sink(
label='excess',
inputs={bus_1: Flow(
variable_costs=0,
)},
))

# create an optimization problem and solve it
om = Model(es)

# export lp file
filename = os.path.join(
helpers.extend_basic_path('lp_files'), 'Schedule_profile.lp')
logging.info('Store lp-file in {0}.'.format(filename))
om.write(filename, io_options={'symbolic_solver_labels': True})

# solve model
om.solve(solver='gurobi', solve_kwargs={'tee': True})

# create result object
results = processing.results(om)

bus1 = views.node(results, 'bus')["sequences"]
bus1.plot(kind='line', drawstyle='steps-mid')
plt.ylim(None, 10)
plt.legend()
plt.show()


4 changes: 4 additions & 0 deletions src/oemof/solph/_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ class NonConvex:
def __init__(self, **kwargs):
scalars = [
"minimum_uptime",
"maximum_uptime",
"minimum_downtime",
"initial_status",
"maximum_startups",
Expand All @@ -168,6 +169,7 @@ def __init__(self, **kwargs):
"shutdown_costs",
"activity_costs",
"inactivity_costs",
"profile",
]
dictionaries = ["positive_gradient", "negative_gradient"]
defaults = {
Expand Down Expand Up @@ -206,6 +208,8 @@ def _calculate_max_up_down(self):
max_up_down = self.minimum_uptime
elif self.minimum_uptime is None and self.minimum_downtime is not None:
max_up_down = self.minimum_downtime
elif self.maximum_uptime is not None:
max_up_down = self.maximum_uptime
else:
max_up_down = max(self.minimum_uptime, self.minimum_downtime)

Expand Down
85 changes: 85 additions & 0 deletions src/oemof/solph/flows/_non_convex_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,13 @@ def _create(self, group=None):
or g[2].nonconvex.maximum_startups is not None
]
)
self.PROFILEFLOWS = Set(
initialize=[
(g[0], g[1])
for g in group
if g[2].nonconvex.profile is not None
]
)
self.MAXSTARTUPFLOWS = Set(
initialize=[
(g[0], g[1])
Expand Down Expand Up @@ -373,6 +380,13 @@ def _create(self, group=None):
if g[2].nonconvex.minimum_uptime is not None
]
)
self.MAXUPTIMEFLOWS = Set(
initialize=[
(g[0], g[1])
for g in group
if g[2].nonconvex.maximum_uptime is not None
]
)

self.MINDOWNTIMEFLOWS = Set(
initialize=[
Expand Down Expand Up @@ -545,6 +559,34 @@ def _min_uptime_rule(block, i, o, t):
self.MINUPTIMEFLOWS, m.TIMESTEPS, rule=_min_uptime_rule
)

def _max_uptime_rule(block, i, o, t):
"""
Rule definition for max-uptime constraints of nonconvex flows.
"""
if (
m.flows[i, o].nonconvex.max_up_down
<= t
<= m.TIMESTEPS[-1] - m.flows[i, o].nonconvex.max_up_down
):
expr = 0
expr += m.flows[i, o].nonconvex.maximum_uptime
expr += -sum(
self.status[i, o, t - u]
for u in range(
0, m.flows[i, o].nonconvex.maximum_uptime + 1
)
)
return expr >= 0
else:
expr = 0
expr += self.status[i, o, t]
expr += -m.flows[i, o].nonconvex.initial_status
return expr == 0

self.max_uptime_constr = Constraint(
self.MAXUPTIMEFLOWS, m.TIMESTEPS, rule=_max_uptime_rule
)

def _min_downtime_rule(block, i, o, t):
"""
Rule definition for min-downtime constraints of nonconvex flows.
Expand Down Expand Up @@ -620,6 +662,49 @@ def _negative_gradient_flow_rule(block):
rule=_negative_gradient_flow_rule
)

def _schedule_profile_rule(block, i, o, t):
"""Rule definition for startup constraint of nonconvex flows."""

# for i, o in self.PROFILEFLOWS:
# for t in m.TIMESTEPS:

profile_reversed = m.flows[i, o].nonconvex.profile[::-1]
len_profile = len(m.flows[i, o].nonconvex.profile)

if (
len(m.flows[i, o].nonconvex.profile) - 1
<= t
< len(m.TIMESTEPS) - len(m.flows[i, o].nonconvex.profile)
):
expr = 0
expr += - m.flow[i, o, t]
print("f_(", t, ") =\n")
for d in range(len_profile):
print(profile_reversed[d], " * binary(", t + d - len_profile + 1, ") + ")
expr += self.startup[i, o, t + d - len_profile + 1] *\
profile_reversed[d]

return expr == 0

else:
expr = 0
expr += - m.flow[i, o, t]
return expr == 0

# ####
# for t in m.TIMESTEPS:
# if t < len(m.TIMESTEPS) - len(m.flows[i, o].nonconvex.profile):
# for i in range(len(m.flows[i, o].nonconvex.profile)):
#
# expr = (
# m.flow[i, o, t] = self.startup[i, o, t]
# )
# return expr

self.schedule_profile_constr = Constraint(
self.PROFILEFLOWS, m.TIMESTEPS, rule=_schedule_profile_rule
)

def _objective_expression(self):
r"""Objective expression for nonconvex flows."""
if not hasattr(self, "NONCONVEX_FLOWS"):
Expand Down