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

Compare differences in remaining value vs. actual value #997

Merged
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
5 changes: 5 additions & 0 deletions docs/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1270,6 +1270,11 @@ Besides the `invest` variable, new variables are introduced as well. These are:
has not yet been tested.
* For now, both, the `timeindex` as well as the `timeincrement` of an energy system have to be defined since they
have to be of the same length for a multi-period model.
* You can choose whether or not to re-evaluate assets at the end of the optimization horizon. If you set attribute
`use_remaining_value` of the energy system to True (defaults to False), this leads to the model evaluating the
different in value at the end of the optimization horizon vs. at the time the investment was made. The difference
in value is added to or subtracted from the respective investment costs increment, assuming assets are to be
liquidated / re-evaluated at the end of the optimization horizon.
* Also please be aware, that periods correspond to years by default. You could also choose
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.
Expand Down
5 changes: 5 additions & 0 deletions docs/whatsnew/v0-5-2.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,14 @@ v0.5.2 (????)
API changes
###########

* New bool attribute `use_remaining_value` of `oemof.solph.EnergySystem`

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

* Allow for evaluating differences in the remaining vs. the original value
for multi-period investments.

Documentation
#############

Expand Down
11 changes: 8 additions & 3 deletions src/oemof/solph/_energy_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ class EnergySystem(es.EnergySystem):
For a standard model, periods are not (to be) declared, i.e. None.
A list with one entry is derived, i.e. [0].

use_remaining_value : bool
If True, compare the remaining value of an investment to the
original value (only applicable for multi-period models)

kwargs
"""

Expand All @@ -71,6 +75,7 @@ def __init__(
timeincrement=None,
infer_last_interval=None,
periods=None,
use_remaining_value=False,
**kwargs,
):
# Doing imports at runtime is generally frowned upon, but should work
Expand Down Expand Up @@ -160,7 +165,8 @@ def __init__(
timeindex=timeindex, timeincrement=timeincrement, **kwargs
)

if periods is not None:
self.periods = periods
if self.periods is not None:
msg = (
"CAUTION! You specified the 'periods' attribute for your "
"energy system.\n This will lead to creating "
Expand All @@ -171,11 +177,10 @@ def __init__(
"please report them."
)
warnings.warn(msg, debugging.SuspiciousUsageWarning)
self.periods = periods
if self.periods is not None:
self._extract_periods_years()
self._extract_periods_matrix()
self._extract_end_year_of_optimization()
self.use_remaining_value = use_remaining_value

def _extract_periods_years(self):
"""Map years in optimization to respective period based on time indices
Expand Down
142 changes: 139 additions & 3 deletions src/oemof/solph/components/_generic_storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -902,6 +902,21 @@ class GenericInvestmentStorageBlock(ScalarBlock):
&
\forall p \in \textrm{PERIODS}

In case, the remaining lifetime of a storage is greater than 0 and
attribute `use_remaining_value` of the energy system is True,
the difference in value for the investment period compared to the
last period of the optimization horizon is accounted for
as an adder to the investment costs:

.. math::
&
E_{invest}(p) \cdot (A(c_{invest,var}(p), l_{r}, ir) -
A(c_{invest,var}(|P|), l_{r}, ir)\\
& \cdot \frac {1}{ANF(l_{r}, ir)} \cdot DF^{-|P|}\\
&\\
&
\forall p \in \textrm{PERIODS}

* :attr:`nonconvex = True`

.. math::
Expand All @@ -913,6 +928,24 @@ class GenericInvestmentStorageBlock(ScalarBlock):
&
\forall p \in \textrm{PERIODS}

In case, the remaining lifetime of a storage is greater than 0 and
attribute `use_remaining_value` of the energy system is True,
the difference in value for the investment period compared to the
last period of the optimization horizon is accounted for
as an adder to the investment costs:

.. math::
&
(E_{invest}(p) \cdot (A(c_{invest,var}(p), l_{r}, ir) -
A(c_{invest,var}(|P|), l_{r}, ir)\\
& \cdot \frac {1}{ANF(l_{r}, ir)} \cdot DF^{-|P|}\\
&
+ (c_{invest,fix}(p) - c_{invest,fix}(|P|))
\cdot b_{invest}(p)) \cdot DF^{-p}\\
&\\
&
\forall p \in \textrm{PERIODS}

* :attr:`fixed_costs` not None for investments

.. math::
Expand All @@ -929,12 +962,14 @@ class GenericInvestmentStorageBlock(ScalarBlock):
\sum_{pp=0}^{limit_{exo}} E_{exist} \cdot c_{fixed}(pp)
\cdot DF^{-pp}


whereby:

* :math:`A(c_{invest,var}(p), l, ir)` A is the annuity for
investment expenses :math:`c_{invest,var}(p)`, lifetime :math:`l`
and interest rate :math:`ir`.
* :math:`l_{r}` is the remaining lifetime at the end of the
optimization horizon (in case it is greater than 0 and
smaller than the actual lifetime).
* :math:`ANF(d, ir)` is the annuity factor for duration :math:`d`
and interest rate :math:`ir`.
* :math:`d=min\{year_{max} - year(p), l\}` defines the
Expand Down Expand Up @@ -1765,7 +1800,19 @@ def _objective_expression(self):
investment_costs_increment = (
self.invest[n, p] * annuity * present_value_factor
) * (1 + m.discount_rate) ** (-m.es.periods_years[p])
investment_costs += investment_costs_increment
remaining_value_difference = (
self._evaluate_remaining_value_difference(
m,
p,
n,
m.es.end_year_of_optimization,
lifetime,
interest,
)
)
investment_costs += (
investment_costs_increment + remaining_value_difference
)
period_investment_costs[p] += investment_costs_increment

for n in self.NON_CONVEX_INVESTSTORAGES:
Expand Down Expand Up @@ -1794,7 +1841,20 @@ def _objective_expression(self):
self.invest[n, p] * annuity * present_value_factor
+ self.invest_status[n, p] * n.investment.offset[p]
) * (1 + m.discount_rate) ** (-m.es.periods_years[p])
investment_costs += investment_costs_increment
remaining_value_difference = (
self._evaluate_remaining_value_difference(
m,
p,
n,
m.es.end_year_of_optimization,
lifetime,
interest,
nonconvex=True,
)
)
investment_costs += (
investment_costs_increment + remaining_value_difference
)
period_investment_costs[p] += investment_costs_increment

for n in self.INVESTSTORAGES:
Expand Down Expand Up @@ -1835,3 +1895,79 @@ def _objective_expression(self):
self.costs = Expression(expr=investment_costs + fixed_costs)

return self.costs

def _evaluate_remaining_value_difference(
self,
m,
p,
n,
end_year_of_optimization,
lifetime,
interest,
nonconvex=False,
):
"""Evaluate and return the remaining value difference of an investment

The remaining value difference in the net present values if the asset
was to be liquidated at the end of the optimization horizon and the
net present value using the original investment expenses.

Parameters
----------
m : oemof.solph.models.Model
Optimization model

p : int
Period in which investment occurs

n : oemof.solph.components.GenericStorage
storage unit

end_year_of_optimization : int
Last year of the optimization horizon

lifetime : int
lifetime of investment considered

interest : float
Demanded interest rate for investment

nonconvex : bool
Indicating whether considered flow is nonconvex.
"""
if m.es.use_remaining_value:
if end_year_of_optimization - m.es.periods_years[p] < lifetime:
remaining_lifetime = lifetime - (
end_year_of_optimization - m.es.periods_years[p]
)
remaining_annuity = economics.annuity(
capex=n.investment.ep_costs[-1],
n=remaining_lifetime,
wacc=interest,
)
original_annuity = economics.annuity(
capex=n.investment.ep_costs[p],
n=remaining_lifetime,
wacc=interest,
)
present_value_factor_remaining = 1 / economics.annuity(
capex=1, n=remaining_lifetime, wacc=interest
)
if nonconvex:
return (
self.invest[n, p]
* (remaining_annuity - original_annuity)
* present_value_factor_remaining
+ self.invest_status[n, p]
* (n.investment.offset[-1] - n.investment.offset[p])
) * (1 + m.discount_rate) ** (-end_year_of_optimization)
else:
return (
self.invest[n, p]
* (remaining_annuity - original_annuity)
* present_value_factor_remaining
) * (1 + m.discount_rate) ** (-end_year_of_optimization)
else:
return 0
else:
return 0
Loading
Loading