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

Allow for fixing investments #1007

Open
wants to merge 47 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
b001fe6
Introduce draft for fixing investments (yet untested)
jokochems Oct 31, 2023
63e449f
Reformat using black
jokochems Oct 31, 2023
ebe0fa2
Revise architecture and fix erroneous indexing
jokochems Nov 1, 2023
9ae659f
Add / refactor test(s)
jokochems Nov 1, 2023
670a534
Add fixes
jokochems Nov 1, 2023
d66e556
Fix black issues
jokochems Nov 1, 2023
11a7fdb
Add further tests
jokochems Nov 1, 2023
a0a8ea3
Add lp files
jokochems Nov 1, 2023
bdc49ae
Add minor black fix
jokochems Nov 1, 2023
d67d4cf
Fix import
jokochems Nov 1, 2023
2bfa151
Add test for newly introduced error message
jokochems Nov 1, 2023
93752f4
Rename to address line length issue
jokochems Nov 1, 2023
74ffe1c
Satisfy our picky CI
jokochems Nov 1, 2023
3dfeb76
Alter import order
jokochems Nov 1, 2023
427d461
Fix imports once more
jokochems Nov 1, 2023
ce68649
Allow for fixing investments in GenericStorage and SinkDSM
jokochems Nov 1, 2023
fac2b97
Extend test to all DSM modelling approaches
jokochems Nov 1, 2023
2c9766a
Extend docs and changelog
jokochems Nov 1, 2023
536dd91
Add constraint test for fixed storage investment
jokochems Nov 1, 2023
95f53c4
Add fix
jokochems Nov 1, 2023
944a049
Add tests for dsm units
jokochems Nov 1, 2023
21e766f
Merge branch 'dev' into features/fix-investment-results-in-repeated-s…
jokochems Dec 8, 2023
1c5b64a
Introduce draft for fixing investments (yet untested)
jokochems Oct 31, 2023
9953714
Reformat using black
jokochems Oct 31, 2023
3d9d334
Revise architecture and fix erroneous indexing
jokochems Nov 1, 2023
011471b
Add / refactor test(s)
jokochems Nov 1, 2023
8893b11
Add fixes
jokochems Nov 1, 2023
d3c78aa
Fix black issues
jokochems Nov 1, 2023
4d8a002
Add further tests
jokochems Nov 1, 2023
4401b9a
Add lp files
jokochems Nov 1, 2023
cb6b9f8
Add minor black fix
jokochems Nov 1, 2023
817d092
Fix import
jokochems Nov 1, 2023
b8b1f2e
Add test for newly introduced error message
jokochems Nov 1, 2023
296934d
Rename to address line length issue
jokochems Nov 1, 2023
71808ce
Satisfy our picky CI
jokochems Nov 1, 2023
4a6a5cf
Alter import order
jokochems Nov 1, 2023
19a98b2
Fix imports once more
jokochems Nov 1, 2023
a1f9692
Allow for fixing investments in GenericStorage and SinkDSM
jokochems Nov 1, 2023
d412725
Extend test to all DSM modelling approaches
jokochems Nov 1, 2023
a2add52
Extend docs and changelog
jokochems Nov 1, 2023
f611d86
Add constraint test for fixed storage investment
jokochems Nov 1, 2023
759a219
Add fix
jokochems Nov 1, 2023
72980a9
Add tests for dsm units
jokochems Nov 1, 2023
10a6f70
Merge remote-tracking branch 'upstream/features/fix-investment-result…
jokochems Dec 8, 2023
5b535fd
Allow for rounding in repeated solving
jokochems Dec 8, 2023
ba35e54
Add / fix tests
jokochems Dec 8, 2023
91669ff
Alter import order
jokochems Dec 8, 2023
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
27 changes: 27 additions & 0 deletions docs/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1279,6 +1279,33 @@ Besides the `invest` variable, new variables are introduced as well. These are:
monthly periods, but you would need to be very careful in parameterizing your energy system and your model and also,
this would mean monthly discounting (if applicable) as well as specifying your plants lifetimes in months.

Repeated solving with fixed investment results
----------------------------------------------

You can rerun a given optimization model with fixed investments.
Let's assume you have set up a model instance called ``model``
containing investments and solved it by calling:

.. code-block:: python

model = solph.Model(energysystem)
model.solve()

You can now fix the investment results from the previous solving and
only solve the dispatch-related part via a call of the method
``fix_investments`` as well as a second solving of your model:

.. code-block:: python

model.fix_investments()
model.solve()

This will now take the investment variables as parameters. Thus, their
values from the previous solving are parameters in the second solving. Only
the dispatch-related variables will be optimized. This two-stage approach
for instance may allow you to interpret dual values of balancing constraints
which you cannot sensibly interpret in the presence of investment variables.

Modelling cellular energy systems and modularizing energy system models
-----------------------------------------------------------------------

Expand Down
3 changes: 3 additions & 0 deletions docs/whatsnew/v0-5-2.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@ API changes
###########

* New bool attribute `use_remaining_value` of `oemof.solph.EnergySystem`
* New method `fix_investments` of `oemof.solph.Model`

New features
############

* Allow for evaluating differences in the remaining vs. the original value
for multi-period investments.
* Allow for fixing investment variables to the values of a previous
optimization run, enabling a repeated solve with fixed investments.

Documentation
#############
Expand Down
22 changes: 22 additions & 0 deletions src/oemof/solph/_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,7 @@ def __init__(self, energysystem, discount_rate=None, **kwargs):
self._set_discount_rate_with_warning()
else:
pass
self._fix_investments = False
super().__init__(energysystem, **kwargs)

def _set_discount_rate_with_warning(self):
Expand Down Expand Up @@ -521,3 +522,24 @@ def _add_parent_block_variables(self):
if (o, i) in self.UNIDIRECTIONAL_FLOWS:
for p, t in self.TIMEINDEX:
self.flow[o, i, p, t].setlb(0)

def fix_investments(self):
"""Fix investment results of an already solved model"""
if self.solver_results is None:
msg = (
"Cannot fix investments as model has not yet been solved!\n"
"You have to first solve your model and then call method "
"`fix_investments()` on your model instance."
)
raise ValueError(msg)
self._fix_investments = True
if hasattr(self, "InvestmentFlowBlock"):
self.InvestmentFlowBlock.fix_investments_results()
if hasattr(self, "GenericInvestmentStorageBlock"):
self.GenericInvestmentStorageBlock.fix_investments_results()
if hasattr(self, "SinkDSMOemofInvestmentBlock"):
self.SinkDSMOemofInvestmentBlock.fix_investments_results()
if hasattr(self, "SinkDSMDIWInvestmentBlock"):
self.SinkDSMDIWInvestmentBlock.fix_investments_results()
if hasattr(self, "SinkDSMDLRInvestmentBlock"):
self.SinkDSMDLRInvestmentBlock.fix_investments_results()
12 changes: 12 additions & 0 deletions src/oemof/solph/components/_generic_storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -1968,3 +1968,15 @@ def _evaluate_remaining_value_difference(
return 0
else:
return 0

def fix_investments_results(self):
"""Fix investments if `_fix_investments` is set to True for model"""
m = self.parent_block()
for n in self.INVESTSTORAGES:
for p in m.PERIODS:
self.invest[n, p].fix()
self.total[n, p].fix()
if m.es.periods is not None:
self.old[n, p].fix()
self.old_end[n, p].fix()
self.old_exo[n, p].fix()
36 changes: 36 additions & 0 deletions src/oemof/solph/components/experimental/_sink_dsm.py
Original file line number Diff line number Diff line change
Expand Up @@ -1536,6 +1536,18 @@ def _evaluate_remaining_value_difference(
else:
return 0

def fix_investments_results(self):
"""Fix investments if `_fix_investments` is set to True for model"""
m = self.parent_block()
for g in self.investdsm:
for p in m.PERIODS:
self.invest[g, p].fix()
self.total[g, p].fix()
if m.es.periods is not None:
self.old[g, p].fix()
self.old_end[g, p].fix()
self.old_exo[g, p].fix()


class SinkDSMDIWBlock(ScalarBlock):
r"""Constraints for SinkDSM with "DIW" approach
Expand Down Expand Up @@ -3390,6 +3402,18 @@ def _evaluate_remaining_value_difference(
else:
return 0

def fix_investments_results(self):
"""Fix investments if `_fix_investments` is set to True for model"""
m = self.parent_block()
for g in self.investdsm:
for p in m.PERIODS:
self.invest[g, p].fix()
self.total[g, p].fix()
if m.es.periods is not None:
self.old[g, p].fix()
self.old_end[g, p].fix()
self.old_exo[g, p].fix()


class SinkDSMDLRBlock(ScalarBlock):
r"""Constraints for SinkDSM with "DLR" approach
Expand Down Expand Up @@ -5888,3 +5912,15 @@ def _evaluate_remaining_value_difference(
return 0
else:
return 0

def fix_investments_results(self):
"""Fix investments if `_fix_investments` is set to True for model"""
m = self.parent_block()
for g in self.INVESTDR:
for p in m.PERIODS:
self.invest[g, p].fix()
self.total[g, p].fix()
if m.es.periods is not None:
self.old[g, p].fix()
self.old_end[g, p].fix()
self.old_exo[g, p].fix()
13 changes: 13 additions & 0 deletions src/oemof/solph/flows/_investment_flow_block.py
Original file line number Diff line number Diff line change
Expand Up @@ -1175,3 +1175,16 @@ def _max_invest_rule(_):
self.maximum_rule_build = BuildAction(rule=_max_invest_rule)

return self.maximum_rule

def fix_investments_results(self):
"""Fix investments if `_fix_investments` is set to True for model"""
m = self.parent_block()
if hasattr(self, "INVESTFLOWS"):
for i, o in self.INVESTFLOWS:
for p in m.PERIODS:
self.invest[i, o, p].fix()
self.total[i, o, p].fix()
if m.es.periods is not None:
self.old[i, o, p].fix()
self.old_end[i, o, p].fix()
self.old_exo[i, o, p].fix()
165 changes: 165 additions & 0 deletions tests/constraint_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -1995,3 +1995,168 @@ def test_storage_level_constraint(self):
output_levels={out_0: 1 / 8, out_1: 1 / 2},
)
self.compare_lp_files("storage_level_constraint.lp", my_om=om)

def test_investment_fixed(self):
"""test a repeated solve with fixed investments for a converter"""
bgas = solph.buses.Bus(label="gas")
bel = solph.buses.Bus(label="electricity")

source = solph.components.Source(
label="gas_source",
outputs={bgas: solph.flows.Flow()},
)
converter = solph.components.Converter(
label="powerplant_gas",
inputs={bgas: solph.flows.Flow()},
outputs={
bel: solph.flows.Flow(
variable_costs=50,
nominal_value=solph.Investment(maximum=1000, ep_costs=20),
)
},
conversion_factors={bel: 0.58},
)
sink = solph.components.Sink(
label="electricity_consumption",
inputs={
bel: solph.flows.Flow(nominal_value=100, fix=[0.8, 0.9, 1.0])
},
)
self.energysystem.add(bgas, bel, source, converter, sink)
om = solph.Model(self.energysystem)
self.compare_lp_files("investment_before_fixing.lp", my_om=om)
om.solve()
om.fix_investments()
om.solve()
self.compare_lp_files("investment_after_fixing.lp", my_om=om)

def test_storage_investment_fixed(self):
"""test a repeated solve with fixed investments for a storage"""
bgas = solph.buses.Bus(label="gas")
bel = solph.buses.Bus(label="electricity")

source = solph.components.Source(
label="gas_source",
outputs={bgas: solph.flows.Flow()},
)
converter = solph.components.Converter(
label="powerplant_gas",
inputs={bgas: solph.flows.Flow()},
outputs={
bel: solph.flows.Flow(
variable_costs=50,
nominal_value=90,
)
},
conversion_factors={bel: 0.58},
)
storage = solph.components.GenericStorage(
label="storage",
nominal_storage_capacity=solph.Investment(maximum=90, ep_costs=20),
inputs={bel: solph.flows.Flow(nominal_value=solph.Investment())},
outputs={bel: solph.flows.Flow(nominal_value=solph.Investment())},
invest_relation_input_capacity=1,
invest_relation_input_output=1,
)
sink = solph.components.Sink(
label="electricity_consumption",
inputs={
bel: solph.flows.Flow(nominal_value=100, fix=[0.8, 0.9, 1.0])
},
)
self.energysystem.add(bgas, bel, source, converter, storage, sink)
om = solph.Model(self.energysystem)
self.compare_lp_files("investment_storage_before_fixing.lp", my_om=om)
om.solve()
om.fix_investments()
om.solve()
self.compare_lp_files("investment_storage_after_fixing.lp", my_om=om)

def test_dsm_investment_fixed(self):
"""test a repeated solve with fixed investments for a dsm unit"""
for approach in ["oemof", "DIW", "DLR"]:
energysystem = solph.EnergySystem(
timeindex=pd.date_range("1/1/2012", periods=3, freq="H")
)
bgas = solph.buses.Bus(label="gas")
bel = solph.buses.Bus(label="electricity")

source = solph.components.Source(
label="gas_source",
outputs={bgas: solph.flows.Flow()},
)
converter = solph.components.Converter(
label="powerplant_gas",
inputs={bgas: solph.flows.Flow()},
outputs={
bel: solph.flows.Flow(
variable_costs=50,
nominal_value=110,
)
},
conversion_factors={bel: 0.58},
)
kwargs_all = {
"label": "demand_dsm",
"inputs": {
bel: solph.flows.Flow(
variable_costs=0,
)
},
"demand": [20] * 3,
"capacity_up": [10] * 3,
"capacity_down": [10] * 3,
"delay_time": 1,
"max_demand": [1] * 3,
"recovery_time_shift": 0,
"cost_dsm_up": 0.01,
"cost_dsm_down_shift": 0.01,
"efficiency": 1.0,
"shed_eligibility": False,
"shift_eligibility": True,
"shift_time": 1,
}

kwargs_dict = {
"oemof": {"shift_interval": 24},
"DIW": {},
"DLR": {
"ActivateYearLimit": False,
"ActivateDayLimit": False,
"n_yearLimit_shift": 2,
"n_yearLimit_shed": 10,
"t_dayLimit": 2,
"addition": True,
"fixes": True,
},
}

dsm_unit = solph.components.experimental.SinkDSM(
**kwargs_all,
approach=approach,
**kwargs_dict[approach],
investment=solph.Investment(
maximum=20,
ep_costs=10,
),
)

sink = solph.components.Sink(
label="electricity_consumption",
inputs={
bel: solph.flows.Flow(
nominal_value=100, fix=[0.9, 1.0, 0.8]
)
},
)
energysystem.add(bgas, bel, source, converter, dsm_unit, sink)
om = solph.Model(energysystem)
self.compare_lp_files(
f"investment_dsm_{approach}_before_fixing.lp", my_om=om
)
om.solve()
om.fix_investments()
om.solve()
self.compare_lp_files(
f"investment_dsm_{approach}_after_fixing.lp", my_om=om
)
Loading
Loading