From 7b4392e63e818975e23d7b3bbe22c83c516c3dac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Tue, 3 Sep 2024 17:20:04 +0200 Subject: [PATCH] Remove internal discounting The (very similar) interest still needs to be removed. --- docs/usage.rst | 7 - src/oemof/solph/_models.py | 20 +-- .../solph/components/_generic_storage.py | 20 +-- .../components/experimental/_sink_dsm.py | 159 ++++++------------ .../solph/flows/_investment_flow_block.py | 12 +- 5 files changed, 61 insertions(+), 157 deletions(-) diff --git a/docs/usage.rst b/docs/usage.rst index 58247d9d9..025e68e46 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -1325,13 +1325,6 @@ Besides the `invest` variable, new variables are introduced as well. These are: .. note:: - * You can specify a `discount_rate` for the model. If you do not do so, 0.02 will be used as a default, corresponding - to sort of a social discount rate. If you work with costs in real terms, discounting is obsolete, so define - `discount_rate = 0` in that case. - * You can specify an `interest_rate` for every investment object. If you do not do so, it will be chosen the same - as the model's `discount_rate`. You could use this default to model a perfect competition administered by some sort of - social planner, but even in a social planner setting, you might want to deviate from the `discount_rate` - value and/or discriminate among technologies with different risk profiles and hence different interest requirements. * For storage units, the `initial_content` is not allowed combined with multi-period investments. The storage inflow and outflow are forced to zero until the storage unit is invested into. * You can specify periods of different lengths, but the frequency of your timeindex needs to be consistent. Also, diff --git a/src/oemof/solph/_models.py b/src/oemof/solph/_models.py index 5ecbbd474..170d6632a 100644 --- a/src/oemof/solph/_models.py +++ b/src/oemof/solph/_models.py @@ -52,9 +52,6 @@ class Model(po.ConcreteModel): Solph looks for these groups in the given energy system and uses them to create the constraints of the optimization problem. Defaults to `Model.CONSTRAINT_GROUPS` - discount_rate : float or None - The rate used for discounting in a multi-period model. - A 2% discount rate needs to be defined as 0.02. objective_weighting : array like (optional) Weights used for temporal objective function expressions. If nothing is passed, `timeincrement` will be used which @@ -84,17 +81,6 @@ class Model(po.ConcreteModel): rc : `pyomo.core.base.suffix.Suffix` or None Store the reduced costs of the model if pyomo suffix is set to IMPORT - Note - ---- - - * The discount rate is only applicable for a multi-period model. - * If you want to work with costs data in nominal terms, - you should specify a discount rate. - * By default, there is a discount rate of 2% in a multi-period model. - * If you want to provide your costs data in real terms, - just specify `discount_rate = 0`, i.e. effectively there will be - no discounting. - **The following basic sets are created**: @@ -134,7 +120,7 @@ class Model(po.ConcreteModel): InvestNonConvexFlowBlock, ] - def __init__(self, energysystem, discount_rate=None, **kwargs): + def __init__(self, energysystem, **kwargs): super().__init__() # Check root logger. Due to a problem with pyomo the building of the @@ -190,9 +176,7 @@ def __init__(self, energysystem, discount_rate=None, **kwargs): self.dual = None self.rc = None - if discount_rate is not None: - self.discount_rate = discount_rate - elif energysystem.periods is not None: + if energysystem.periods is not None: self._set_discount_rate_with_warning() else: pass diff --git a/src/oemof/solph/components/_generic_storage.py b/src/oemof/solph/components/_generic_storage.py index 23c164c5e..c76d72d33 100644 --- a/src/oemof/solph/components/_generic_storage.py +++ b/src/oemof/solph/components/_generic_storage.py @@ -1798,7 +1798,7 @@ def _objective_expression(self): ) investment_costs_increment = ( self.invest[n, p] * annuity * present_value_factor - ) * (1 + m.discount_rate) ** (-m.es.periods_years[p]) + ) remaining_value_difference = ( self._evaluate_remaining_value_difference( m, @@ -1839,7 +1839,7 @@ def _objective_expression(self): investment_costs_increment = ( 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]) + ) remaining_value_difference = ( self._evaluate_remaining_value_difference( m, @@ -1865,9 +1865,7 @@ def _objective_expression(self): m.es.periods_years[p] + lifetime, ) fixed_costs += sum( - self.invest[n, p] - * n.investment.fixed_costs[pp] - * (1 + m.discount_rate) ** (-pp) + self.invest[n, p] * n.investment.fixed_costs[pp] for pp in range( m.es.periods_years[p], range_limit, @@ -1882,9 +1880,7 @@ def _objective_expression(self): m.es.end_year_of_optimization, lifetime - age ) fixed_costs += sum( - n.investment.existing - * n.investment.fixed_costs[pp] - * (1 + m.discount_rate) ** (-pp) + n.investment.existing * n.investment.fixed_costs[pp] for pp in range(range_limit) ) @@ -1956,15 +1952,11 @@ def _evaluate_remaining_value_difference( self.invest[n, p] * (remaining_annuity - original_annuity) * present_value_factor_remaining - ) * (1 + m.discount_rate) ** (-end_year_of_optimization) + ) if nonconvex: return convex_investment_costs + self.invest_status[ n, p - ] * (n.investment.offset[-1] - n.investment.offset[p]) * ( - 1 + m.discount_rate - ) ** ( - -end_year_of_optimization - ) + ] * (n.investment.offset[-1] - n.investment.offset[p]) else: return convex_investment_costs else: diff --git a/src/oemof/solph/components/experimental/_sink_dsm.py b/src/oemof/solph/components/experimental/_sink_dsm.py index 4645a5899..7a0537d3b 100644 --- a/src/oemof/solph/components/experimental/_sink_dsm.py +++ b/src/oemof/solph/components/experimental/_sink_dsm.py @@ -693,22 +693,16 @@ def _objective_expression(self): self.dsm_up[g, t] * m.objective_weighting[t] * g.cost_dsm_up[t] - * (1 + m.discount_rate) ** (-m.es.periods_years[p]) ) variable_costs += ( - ( - self.dsm_do_shift[g, t] * g.cost_dsm_down_shift[t] - + self.dsm_do_shed[g, t] * g.cost_dsm_down_shed[t] - ) - * m.objective_weighting[t] - * (1 + m.discount_rate) ** (-m.es.periods_years[p]) - ) + self.dsm_do_shift[g, t] * g.cost_dsm_down_shift[t] + + self.dsm_do_shed[g, t] * g.cost_dsm_down_shed[t] + ) * m.objective_weighting[t] if valid_sequence(g.fixed_costs, len(m.PERIODS)): fixed_costs += sum( max(g.max_capacity_up, g.max_capacity_down) * g.fixed_costs[pp] - * (1 + m.discount_rate) ** (-pp) for pp in range(m.es.end_year_of_optimization) ) @@ -1398,7 +1392,7 @@ def _objective_expression(self): ) investment_costs_increment = ( self.invest[g, p] * annuity * present_value_factor - ) * (1 + m.discount_rate) ** (-m.es.periods_years[p]) + ) remaining_value_difference = ( self._evaluate_remaining_value_difference( m, @@ -1424,16 +1418,11 @@ def _objective_expression(self): self.dsm_up[g, t] * m.objective_weighting[t] * g.cost_dsm_up[t] - * (1 + m.discount_rate) ** (-m.es.periods_years[p]) ) variable_costs += ( - ( - self.dsm_do_shift[g, t] * g.cost_dsm_down_shift[t] - + self.dsm_do_shed[g, t] * g.cost_dsm_down_shed[t] - ) - * m.objective_weighting[t] - * (1 + m.discount_rate) ** (-m.es.periods_years[p]) - ) + self.dsm_do_shift[g, t] * g.cost_dsm_down_shift[t] + + self.dsm_do_shed[g, t] * g.cost_dsm_down_shed[t] + ) * m.objective_weighting[t] if valid_sequence(g.investment.fixed_costs, len(m.PERIODS)): lifetime = g.investment.lifetime @@ -1443,9 +1432,7 @@ def _objective_expression(self): m.es.periods_years[p] + lifetime, ) fixed_costs += sum( - self.invest[g, p] - * g.investment.fixed_costs[pp] - * (1 + m.discount_rate) ** (-pp) + self.invest[g, p] * g.investment.fixed_costs[pp] for pp in range( m.es.periods_years[p], range_limit, @@ -1460,9 +1447,7 @@ def _objective_expression(self): m.es.end_year_of_optimization, lifetime - age ) fixed_costs += sum( - g.investment.existing - * g.investment.fixed_costs[pp] - * (1 + m.discount_rate) ** (-pp) + g.investment.existing * g.investment.fixed_costs[pp] for pp in range(range_limit) ) @@ -1533,7 +1518,7 @@ def _evaluate_remaining_value_difference( self.invest[g, p] * (remaining_annuity - original_annuity) * present_value_factor_remaining - ) * (1 + m.discount_rate) ** (-end_year_of_optimization) + ) else: return 0 else: @@ -2184,26 +2169,17 @@ def _objective_expression(self): self.dsm_up[g, t] * m.objective_weighting[t] * g.cost_dsm_up[t] - * (1 + m.discount_rate) ** (-m.es.periods_years[p]) ) variable_costs += ( - ( - sum( - self.dsm_do_shift[g, tt, t] - for tt in m.TIMESTEPS - ) - * g.cost_dsm_down_shift[t] - + self.dsm_do_shed[g, t] * g.cost_dsm_down_shed[t] - ) - * m.objective_weighting[t] - * (1 + m.discount_rate) ** (-m.es.periods_years[p]) - ) + sum(self.dsm_do_shift[g, tt, t] for tt in m.TIMESTEPS) + * g.cost_dsm_down_shift[t] + + self.dsm_do_shed[g, t] * g.cost_dsm_down_shed[t] + ) * m.objective_weighting[t] if valid_sequence(g.fixed_costs, len(m.PERIODS)): fixed_costs += sum( max(g.max_capacity_up, g.max_capacity_down) * g.fixed_costs[pp] - * (1 + m.discount_rate) ** (-pp) for pp in range(m.es.end_year_of_optimization) ) @@ -3250,7 +3226,7 @@ def _objective_expression(self): ) investment_costs_increment = ( self.invest[g, p] * annuity * present_value_factor - ) * (1 + m.discount_rate) ** (-m.es.periods_years[p]) + ) remaining_value_difference = ( self._evaluate_remaining_value_difference( m, @@ -3276,20 +3252,12 @@ def _objective_expression(self): self.dsm_up[g, t] * m.objective_weighting[t] * g.cost_dsm_up[t] - * (1 + m.discount_rate) ** (-m.es.periods_years[p]) ) variable_costs += ( - ( - sum( - self.dsm_do_shift[g, tt, t] - for tt in m.TIMESTEPS - ) - * g.cost_dsm_down_shift[t] - + self.dsm_do_shed[g, t] * g.cost_dsm_down_shed[t] - ) - * m.objective_weighting[t] - * (1 + m.discount_rate) ** (-m.es.periods_years[p]) - ) + sum(self.dsm_do_shift[g, tt, t] for tt in m.TIMESTEPS) + * g.cost_dsm_down_shift[t] + + self.dsm_do_shed[g, t] * g.cost_dsm_down_shed[t] + ) * m.objective_weighting[t] if valid_sequence(g.investment.fixed_costs, len(m.PERIODS)): lifetime = g.investment.lifetime @@ -3299,9 +3267,7 @@ def _objective_expression(self): m.es.periods_years[p] + lifetime, ) fixed_costs += sum( - self.invest[g, p] - * g.investment.fixed_costs[pp] - * (1 + m.discount_rate) ** (-pp) + self.invest[g, p] * g.investment.fixed_costs[pp] for pp in range( m.es.periods_years[p], range_limit, @@ -3316,9 +3282,7 @@ def _objective_expression(self): m.es.end_year_of_optimization, lifetime - age ) fixed_costs += sum( - g.investment.existing - * g.investment.fixed_costs[pp] - * (1 + m.discount_rate) ** (-pp) + g.investment.existing * g.investment.fixed_costs[pp] for pp in range(range_limit) ) @@ -3389,7 +3353,7 @@ def _evaluate_remaining_value_difference( self.invest[g, p] * (remaining_annuity - original_annuity) * present_value_factor_remaining - ) * (1 + m.discount_rate) ** (-end_year_of_optimization) + ) else: return 0 else: @@ -4367,36 +4331,26 @@ def _objective_expression(self): for g in self.DR: for p, t in m.TIMEINDEX: variable_costs += ( - ( - sum( - self.dsm_up[g, h, t] - + self.balance_dsm_do[g, h, t] - for h in g.delay_time - ) - * g.cost_dsm_up[t] + sum( + self.dsm_up[g, h, t] + self.balance_dsm_do[g, h, t] + for h in g.delay_time ) - * m.objective_weighting[t] - * (1 + m.discount_rate) ** (-m.es.periods_years[p]) - ) + * g.cost_dsm_up[t] + ) * m.objective_weighting[t] variable_costs += ( - ( - sum( - self.dsm_do_shift[g, h, t] - + self.balance_dsm_up[g, h, t] - for h in g.delay_time - ) - * g.cost_dsm_down_shift[t] - + self.dsm_do_shed[g, t] * g.cost_dsm_down_shed[t] + sum( + self.dsm_do_shift[g, h, t] + + self.balance_dsm_up[g, h, t] + for h in g.delay_time ) - * m.objective_weighting[t] - * (1 + m.discount_rate) ** (-m.es.periods_years[p]) - ) + * g.cost_dsm_down_shift[t] + + self.dsm_do_shed[g, t] * g.cost_dsm_down_shed[t] + ) * m.objective_weighting[t] if valid_sequence(g.fixed_costs, len(m.PERIODS)): fixed_costs += sum( max(g.max_capacity_up, g.max_capacity_down) * g.fixed_costs[pp] - * (1 + m.discount_rate) ** (-pp) for pp in range(m.es.end_year_of_optimization) ) @@ -5744,7 +5698,7 @@ def _objective_expression(self): ) investment_costs_increment = ( self.invest[g, p] * annuity * present_value_factor - ) * (1 + m.discount_rate) ** (-m.es.periods_years[p]) + ) remaining_value_difference = ( self._evaluate_remaining_value_difference( m, @@ -5767,30 +5721,21 @@ def _objective_expression(self): for p, t in m.TIMEINDEX: variable_costs += ( - ( - sum( - self.dsm_up[g, h, t] - + self.balance_dsm_do[g, h, t] - for h in g.delay_time - ) - * g.cost_dsm_up[t] + sum( + self.dsm_up[g, h, t] + self.balance_dsm_do[g, h, t] + for h in g.delay_time ) - * m.objective_weighting[t] - * (1 + m.discount_rate) ** (-m.es.periods_years[p]) - ) + * g.cost_dsm_up[t] + ) * m.objective_weighting[t] variable_costs += ( - ( - sum( - self.dsm_do_shift[g, h, t] - + self.balance_dsm_up[g, h, t] - for h in g.delay_time - ) - * g.cost_dsm_down_shift[t] - + self.dsm_do_shed[g, t] * g.cost_dsm_down_shed[t] + sum( + self.dsm_do_shift[g, h, t] + + self.balance_dsm_up[g, h, t] + for h in g.delay_time ) - * m.objective_weighting[t] - * (1 + m.discount_rate) ** (-m.es.periods_years[p]) - ) + * g.cost_dsm_down_shift[t] + + self.dsm_do_shed[g, t] * g.cost_dsm_down_shed[t] + ) * m.objective_weighting[t] if valid_sequence(g.investment.fixed_costs, len(m.PERIODS)): lifetime = g.investment.lifetime @@ -5800,9 +5745,7 @@ def _objective_expression(self): m.es.periods_years[p] + lifetime, ) fixed_costs += sum( - self.invest[g, p] - * g.investment.fixed_costs[pp] - * (1 + m.discount_rate) ** (-pp) + self.invest[g, p] * g.investment.fixed_costs[pp] for pp in range( m.es.periods_years[p], range_limit, @@ -5817,9 +5760,7 @@ def _objective_expression(self): m.es.end_year_of_optimization, lifetime - age ) fixed_costs += sum( - g.investment.existing - * g.investment.fixed_costs[pp] - * (1 + m.discount_rate) ** (-pp) + g.investment.existing * g.investment.fixed_costs[pp] for pp in range(range_limit) ) @@ -5890,7 +5831,7 @@ def _evaluate_remaining_value_difference( self.invest[g, p] * (remaining_annuity - original_annuity) * present_value_factor_remaining - ) * (1 + m.discount_rate) ** (-end_year_of_optimization) + ) else: return 0 else: diff --git a/src/oemof/solph/flows/_investment_flow_block.py b/src/oemof/solph/flows/_investment_flow_block.py index 2a09f11a2..072fdba76 100644 --- a/src/oemof/solph/flows/_investment_flow_block.py +++ b/src/oemof/solph/flows/_investment_flow_block.py @@ -945,7 +945,7 @@ def _objective_expression(self): self.invest[i, o, p] * annuity * present_value_factor_remaining - ) * (1 + m.discount_rate) ** (-m.es.periods_years[p]) + ) remaining_value_difference = ( self._evaluate_remaining_value_difference( m, @@ -990,7 +990,7 @@ def _objective_expression(self): * present_value_factor_remaining + self.invest_status[i, o, p] * m.flows[i, o].investment.offset[p] - ) * (1 + m.discount_rate) ** (-m.es.periods_years[p]) + ) remaining_value_difference = ( self._evaluate_remaining_value_difference( m, @@ -1021,7 +1021,6 @@ def _objective_expression(self): fixed_costs += sum( self.invest[i, o, p] * m.flows[i, o].investment.fixed_costs[pp] - * (1 + m.discount_rate) ** (-pp) for pp in range(m.es.periods_years[p], range_limit) ) @@ -1037,7 +1036,6 @@ def _objective_expression(self): fixed_costs += sum( m.flows[i, o].investment.existing * m.flows[i, o].investment.fixed_costs[pp] - * (1 + m.discount_rate) ** (-pp) for pp in range(range_limit) ) @@ -1113,17 +1111,13 @@ def _evaluate_remaining_value_difference( self.invest[i, o, p] * (remaining_annuity - original_annuity) * present_value_factor_remaining - ) * (1 + m.discount_rate) ** (-end_year_of_optimization) + ) if nonconvex: return convex_investment_costs + self.invest_status[ i, o, p ] * ( m.flows[i, o].investment.offset[-1] - m.flows[i, o].investment.offset[p] - ) * ( - 1 + m.discount_rate - ) ** ( - -end_year_of_optimization ) else: return convex_investment_costs