EnergyModelsX / EnergyModelsInvestments.jl

MIT License
3 stars 2 forks source link

Unlimited lifetime for existing technologies #30

Open augbra opened 1 week ago

augbra commented 1 week ago

If an investment is defined with a lifetime, this lifetime is only applied when the technology is invested in. If the technology already exists at the start, it has unlimited lifetime.

JulStraus commented 1 week ago

Yes, this is true. That is mostly related to the initial design choices within the project CleanExport as we did not have existing technologies with an end of life. We are currently investigating a few approaches regarding a change in the calculation of the lifetime, including cost for retiring in Issue 26.

We are however aware that we currently do not highlight this sufficiently well in the documentation. It will be included within Pull request 28 as said PR already has significant changes in the documentation.

JulStraus commented 1 week ago

Thinking a bit further about it, it is still possible to have a retiring capacity in the current design, although this requires a bit of tweaking.

Consider you have a source node with a reduced capacity in the individual strategic periods given as

# Create the investment data for the source node
investment_data_source = SingleInvData(
    FixedProfile(300 * 1e3),  # capex [€/MW]
    FixedProfile(30),       # max installed capacity [MW]
    ContinuousInvestment(FixedProfile(0), FixedProfile(30)),
    # Line above: Investment mode with the following arguments:
    # 1. argument: min added capactity per sp [MW]
    # 2. argument: max added capactity per sp [MW]
    lifemode(20),     # Lifetime mode
)

# Create the source
source = RefSource(
    "electricity source",       # Node ID
    StrategicProfile([10, 5, 0]),  # Capacity in MW
    FixedProfile(10),           # Variable OPEX in EUR/MW
    FixedProfile(5),            # Fixed OPEX in EUR/year
    Dict(Power => 1),           # Output from the Node, in this gase, Power
    [investment_data_source],   # Additional data used for adding the investment data
)

In this situation, we do not really see the impact of the reduced capacity. One alternative to still keep it present would be given by creating two source nodes, one with investment data and without capacity and one without investment data and the declining capacity:

# Create the source node with declining capacity
source_1 = RefSource(
    "electricity source 1",       # Node ID
    StrategicProfile([10, 5, 0]),  # Capacity in MW
    FixedProfile(10),           # Variable OPEX in EUR/MW
    FixedProfile(5),            # Fixed OPEX in EUR/year
    Dict(Power => 1),           # Output from the Node, in this gase, Power
)

# Create the source node with investments
source_2 = RefSource(
    "electricity source 2",       # Node ID
    FixedProfile(0),            # Capacity in MW
    FixedProfile(10),           # Variable OPEX in EUR/MW
    FixedProfile(5),            # Fixed OPEX in EUR/year
    Dict(Power => 1),           # Output from the Node, in this gase, Power
    [investment_data_source],   # Additional data used for adding the investment data
)

This approach has the disadvantages of

  1. introducing more variables and
  2. require the introduction of more links.

It is however an equivalent approach for incorporating an existing capacity with a limited lifetime.

JulStraus commented 1 week ago

@hellemo That was something which was bothering me multiple times beforehand as well. It essentially affects the for-loop in add_investment_constraints and there specifically the last two constraints.

One approach for solving this would be given by:

# Extract the capacity in the Node
start_cap_val = start_cap(element, t_inv, inv_data, cap)

# Update the current capacity
if isnothing(t_inv_prev)
    @constraint(m, var_current[t_inv] == start_cap_val + var_add[t_inv])
else
    @constraint(
        var_current[t_inv] ==
        start_cap_val +
        sum(var_add[sp] for sp ∈ Iterators.take(𝒯ᴵⁿᵛ, t_inv.sp)) -
        sum(var_rem[ante_sp] for ante_sp ∈ Iterators.take(𝒯ᴵⁿᵛ, t_inv_prev.sp))
    )
end

The if-loop is still required as t_inv_prev.sp would not work if t_inv_prev = nothing.

I have not tested in out, but I want to check the impact on model speed for a couple of trial runs. In practice, it should be equivalent when the capacity was provided as FixedProfile previously.