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

InvestNonConvexFlowBlock does not eliminate Investment offset when no investment is made #1172

Open
jfreissmann opened this issue Mar 11, 2025 · 0 comments

Comments

@jfreissmann
Copy link

Short description:

If a NonConvex flow has an investment to be optimized and the solph.Investment object contains an offset (i.e. fix cost independent of the flows nominal_capacity), this offset is added to the objective function, whether or not the flow is optimized to a nominal_capacity greater then zero.

Why this is a bug:

This behavior is most likely not what most users would expect when setting the offset parameter. Even if it was, it is not in line with other components, e.g. the GenericStorage class, which adds the invest_status binary variable. The varible is 0 when no investment is made, so it can be multiplied with the offset in the objective function.

How to reproduce:

Add a cost to the offset parameter of an solph.Investment object in a flow of a Converter, that itself is set to be NonConvex (e.g. setting a min and max value, so NonConvex allows the flow to be zero nevertheless). Use the model.write method and investigate the objective expression. The cost set in the offset parameter should be added onto other constant cost/revenues in the ONE_VAR_CONSTANT expression.

Additional information:

This excerpt of the LP generated using the model.write method shows how the GenericInvestmentStorageBlock handels the constant additional cost differently to the InvestNonConvexFlowBlock.

LP file excerpt

* Source Pyomo model name=Model *\

min
objective:
+774240.8738468975 ONE_VAR_CONSTANT
+20.5 flow(electricity_network_heat_pump_0_0)
+0.01 flow(heat_network_st_tes_0_0)
-35.32 flow(spotmarket_node_spotmarket_0_0)
+28.75154 flow(gas_source_gas_network_0_0)
+60.34 flow(electricity_source_electricity_network_0_0)
+1.2 flow(heat_pump_heat_network_0_0)
+4.4 flow(ccet_ccet_node_0_0)
+6.6 flow(peak_load_boiler_heat_network_0_0)
+0.01 flow(st_tes_heat_network_0_0)
+185784.83113797754 InvestNonConvexFlowBlock_invest(ccet_heat_network_0)
+24824.439073550922 InvestNonConvexFlowBlock_invest(heat_pump_heat_network_0)
+6764.555231441481 InvestNonConvexFlowBlock_invest(peak_load_boiler_heat_network_0)
+21.9765694447266 GenericInvestmentStorageBlock_invest(st_tes_0)
+16635.489556160563 GenericInvestmentStorageBlock_invest_status(st_tes_0)

Here is a comparison between the objective expressions of the GenericInvestmentStorageBlock and InvestNonConvexFlowBlock:

Excerpt of the objective_expression of the GenericInvestmentStorageBlock
  def _objective_expression(self):
      """Objective expression with fixed and investment costs."""
      m = self.parent_block()

      investment_costs = 0
      period_investment_costs = {p: 0 for p in m.PERIODS}
      fixed_costs = 0

      if m.es.periods is None:
          for n in self.CONVEX_INVESTSTORAGES:
              for p in m.PERIODS:
                  investment_costs += (
                      self.invest[n, p] * n.investment.ep_costs[p]
                  )
          for n in self.NON_CONVEX_INVESTSTORAGES:
              for p in m.PERIODS:
                  investment_costs += (
                      self.invest[n, p] * n.investment.ep_costs[p]
                      + self.invest_status[n, p] * n.investment.offset[p]    # <- here the offset is eliminated in case no investment is made
                  )
Excerpt of the objective_expression of the InvestNonConvexFlowBlock
  def _objective_expression(self):
      r"""Objective expression for nonconvex investment flows.

          If `nonconvex.startup_costs` is set by the user:
              .. math::
                  \sum_{i, o \in STARTUPFLOWS} \sum_t  startup(i, o, t) \
                  \cdot c_{startup}

          If `nonconvex.shutdown_costs` is set by the user:
              .. math::
                  \sum_{i, o \in SHUTDOWNFLOWS} \sum_t shutdown(i, o, t) \
                  \cdot c_{shutdown}

          .. math::
              P_{invest} \cdot c_{invest,var}
      """
      if not hasattr(self, "INVEST_NON_CONVEX_FLOWS"):
          return 0

      m = self.parent_block()

      startup_costs = self._startup_costs()
      shutdown_costs = self._shutdown_costs()
      activity_costs = self._activity_costs()
      inactivity_costs = self._inactivity_costs()
      investment_costs = 0

      for i, o in self.INVEST_NON_CONVEX_FLOWS:
          for p in m.PERIODS:
              investment_costs += (
                  self.invest[i, o, p] * m.flows[i, o].investment.ep_costs[p]
                  + m.flows[i, o].investment.offset[p]    # <- here the offset is added whether or not an investment is made
              )

      self.investment_costs = Expression(expr=investment_costs)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants