Restrictions when adding a binary variable - Local Infeasibility (Ipopt, Juniper)

Hello,
I am trying to model a nonlinear optimization problem involving binary variables and continuous variables. Specifically, I cannot understand why a binary variable “z_sys_to_HP(s)” makes the model unusable.

My model tries to minimize the Levelized Cost of Heating (LCOH) of a system. One part is the solar collectors, which provide a production and the other part is a heat pump (HP), with which a certain temperature level is to be reached.

The model works as long as lines 143 to 150 do not contain the binary variable “z_sys_to_HP(s)”. I have highlighted this in the code with “PROBLEM- START” and “PROBLEM - END”.

I would be very grateful if someone could tell me why the model cannot be solved if I include the variable “z_sys_to_HP(s)”.

Best regards
Fynn

using JuMP, XLSX, Ipopt, Juniper

ipopt = optimizer_with_attributes(Ipopt.Optimizer, "tol" => 1e-6, "print_level" => 4, "max_iter" => 20000)
optimizer = optimizer_with_attributes(Juniper.Optimizer, "nl_solver" => ipopt, "mip_gap" => 0.05, "time_limit" => 86400) #Maximal 5% Optimalitätslücke, #Zeitlimit von 24 Stunden
model = Model(optimizer)

# 0. Pfad zur Excel-Datei und Excel-Datei einlesen
    daten = XLSX.readxlsx("SOURCE")

    # Kollektoren
    Werte_Flachkollektoren = daten["Tabelle_Daten"]["B2:B11"]
    EDATA_Coll_FK_values = [Werte_Flachkollektoren[i][1] for i in 1:length(Werte_Flachkollektoren)] #Flachkollektoren - EDATA [kWh / m^2]

    # Energienachfrage
    Werte_Energienachfrage = daten["Tabelle_Daten"]["E2:E11"]
    Energienachfrage_values = [Werte_Energienachfrage[i][1] for i in 1:length(Werte_Energienachfrage)] #Energienachfrage - EDATA [MWh]

    # Temperatur-Nachfrage
    Werte_Temperatur_Nachfrage = daten["Tabelle_Daten"]["F2:F11"] #Temperaturen des Vorlaufs des Bedarfs in °C
    Temp_Bedarf_values = [Werte_Temperatur_Nachfrage[i][1] for i in 1:length(Werte_Temperatur_Nachfrage)] #Temperaturen des Vorlaufs des Bedarfs in °C - T_H (s)

    # Strompreis
    Werte_Strompreis = daten["Tabelle_Daten"]["G2:G11"] #Strompreise in €/MWh zu jeder Stunde (entnommen Agorameter, 2023)
    Strompreis_values = [Werte_Strompreis[i][1] for i in 1:length(Werte_Strompreis)] #Strompreise in €/MWh zu jeder Stunde

println("Anzahl der eingelesenen Werte (Flachkollektoren): ", length(EDATA_Coll_FK_values))
println("Anzahl der eingelesenen Werte (Energienachfrage): ", length(Energienachfrage_values))
println("Anzahl der eingelesenen Werte (Temperaturbedarf): ", length(Temp_Bedarf_values))
println("Anzahl der eingelesenen Werte (Strompreis): ", length(Strompreis_values))

# 1. Mengen definieren
S = 1:10    # Stunden

# 2. Parameter definieren
    #Diskontierungsfaktor
        diskont = 1.05  # r=0,5

    #Kollektoren
        n_opt_FK = 0.838
        a1_FK = 2.46
        a2_FK = 0.00971

        T_M_FK = 75 #°C
        T_u_peak = 15.4 #°C

    #Wärmepumpe
        E_elektr_max = 4 #MWh (Abhängig von der Nennleistung der HP - Annahme: 2 MW Leistung_Peak)

# 3. Variablen definieren
    # Nachfrage
        @variable(model, E_Demand >= 0)

    # System
        @variable(model, Ertrag_System >= 0)
        @variable(model, z_Netz[s in S], Bin)
        @variable(model, SD >= 0)

    # Zusatz
        @variable(model, OperationWartung_Zusatz >= 0)
        @variable(model, Ertrag_Zusatz >= 0)
        @variable(model, Ertrag_Zusatz_zum_Bedarf[s in S] >= 0)

    # Kollektoren
        @variable(model, I_Coll >= 0)
        @variable(model, A_Coll >= 0)
        @variable(model, I_spez_Coll >= 0)
        @variable(model, P_Coll_peak >= 0)
        @variable(model, Wirkungsgrad_Coll_non_conc >= 0)
        @variable(model, OperationWartung_Kollektor >= 0)
        @variable(model, Ertrag_Coll[s in S] >= 0)
        @variable(model, Ertrag_Coll_zur_HP[s in S] >= 0)
        @variable(model, Ertrag_Coll_zum_Netz[s in S] >= 0)

    # Wärmepumpe
        @variable(model, E_sys_zur_HP[s in S] >= 0)
        @variable(model, z_Coll_zur_HP[s in S], Bin)
        @variable(model, Temp_HP_Quelle[s in S] >= 0)
        @variable(model, COP_HP[s in S] >= 0) #COP of HP
        @variable(model, E_elektr[s in S] >= 0) #Elektrische Energie
        @variable(model, E_HP_zum_Bedarf[s in S] >= 0) #Energie, die von der HP zur Deckung des Bedarfs eingesetzt wird
        @variable(model, Price_HP[s in S]) #Preis, den die HP aufgrund der elektr. Energie verursacht
        @variable(model, I_HP >= 0) #Investitionssume der HP
        @variable(model, OperationWartung_HP)
        @variable(model, z_sys_zur_HP[s in S], Bin)

# 4. Zielfunktionsvariable(n) definieren
    @variable(model, LCOH >= 0)
    @variable(model, LCOH_System >= 0)

# 5. Nebenbedingungen aufstellen
    # Kollektor
        @constraint(model, A_Coll * I_spez_Coll == I_Coll)
        @constraint(model, (504.25 * A_Coll^(-0.077)) == I_spez_Coll)
        @constraint(model, P_Coll_peak <= 10)
        @constraint(model, P_Coll_peak >= 5)
        @constraint(model, 9.066375 * (10^(-4)) * Wirkungsgrad_Coll_non_conc * A_Coll == P_Coll_peak)
        @constraint(model, (n_opt_FK - ((a1_FK*(T_M_FK-T_u_peak) + a2_FK * ((T_M_FK-T_u_peak)^2)) / 906.6375)) == Wirkungsgrad_Coll_non_conc)

        for s in S
            @constraint(model, Ertrag_Coll[s] == (EDATA_Coll_FK_values[s] * A_Coll) * (1 / 1000))
        end

        @constraint(model, OperationWartung_Kollektor == 0.0075 * I_Coll)

        for s in S
            @constraint(model, Temp_Bedarf_values[s] <= 90 + 80*(1-z_Netz[s]))
        end

        for s in S
            @constraint(model, Temp_Bedarf_values[s] >= 90 - 80*z_Netz[s]) #FEHLERPOTENTIAL 1????
        end

        for s in S 
            @constraint(model, Ertrag_Coll[s] == Ertrag_Coll_zur_HP[s] + Ertrag_Coll_zum_Netz[s])
        end

        for s in S
            @constraint(model, Ertrag_Coll_zum_Netz[s] <= 100000 * z_Netz[s])
        end

    # Wärmepumpe

        for s in S 
            @constraint(model, E_sys_zur_HP[s] == Ertrag_Coll_zur_HP[s])
        end

        for s in S 
            @constraint(model, Ertrag_Coll_zur_HP[s] <= 10000*z_Coll_zur_HP[s])
        end

        for s in S 
            @constraint(model, Ertrag_Coll_zur_HP[s] >= z_Coll_zur_HP[s])
        end

        for s in S 
            @constraint(model, Temp_HP_Quelle[s] == (90+273.15))
        end
 
        for s in S 
            @constraint(model, COP_HP[s] == (0.5 * ((Temp_Bedarf_values[s]+273.15)/((Temp_Bedarf_values[s]+273.15)-Temp_HP_Quelle[s])))*z_Coll_zur_HP[s])
        end

        # PROBLEM - START!!!!
        for s in S 
            @constraint(model, z_sys_zur_HP[s] == z_Coll_zur_HP[s])
        end

        for s in S 
            @constraint(model, E_elektr[s] <= E_elektr_max * z_sys_zur_HP[s])
        end

        for s in S 
            @constraint(model, E_elektr[s] >= z_sys_zur_HP[s])
        end
        # PROBLEM - END!!!!

        for s in S 
            @constraint(model, E_elektr[s] * COP_HP[s] == E_HP_zum_Bedarf[s])
        end

        for s in S 
            @constraint(model, Price_HP[s] == E_elektr[s] * Strompreis_values[s])
        end

        @constraint(model, Ertrag_System == sum(E_HP_zum_Bedarf[s] for s in S) + sum(Ertrag_Coll_zum_Netz[s] for s in S))

        @constraint(model, I_HP == E_elektr_max * (5881.1*(E_elektr_max^(-0.779)))) #MUSS NOCH ANGEPASST WERDEN -> BISHER NUR BEISPIELHAFT!!!!!!

        @constraint(model, OperationWartung_HP == 0.01*I_HP + sum(Price_HP[s] for s in S)) #Annahme: 1% der Investitionskosten jedes Jahr

    # Weitere Bedingungen
        
        for s in S 
            @constraint(model, Ertrag_Zusatz_zum_Bedarf[s] == Energienachfrage_values[s] - (E_HP_zum_Bedarf[s] + Ertrag_Coll_zum_Netz[s]))
        end

        @constraint(model, Ertrag_Zusatz == sum(Ertrag_Zusatz_zum_Bedarf[s] for s in S))

        @constraint(model, OperationWartung_Zusatz == 100000*Ertrag_Zusatz)

        @constraint(model, E_Demand == sum(Energienachfrage_values[s] for s in S))

        @constraint(model, SD == Ertrag_System / E_Demand)
        
    # LCOH Zwischenausdrücke
        # Diskontierte Betriebs- & Wartungskosten
            @expression(model, diskontierte_wartungskosten, (OperationWartung_Kollektor + OperationWartung_HP + OperationWartung_Zusatz) / diskont)
        # Diskontierte Erträge
            @expression(model, diskontierte_erträge, (Ertrag_Zusatz + Ertrag_System) / diskont)

    # LCOH_System Zwischenausdrücke
        # Diskontierte Betriebs- & Wartungskosten (System)
            @expression(model, diskontierte_wartungskosten_system, (OperationWartung_Kollektor + OperationWartung_HP) / diskont)
        # Diskontierte Erträge
            @expression(model, diskontierte_erträge_system, (Ertrag_System) / diskont)

    # LCOH Definieren
        @constraint(model, LCOH == (I_Coll + I_HP + diskontierte_wartungskosten) / (diskontierte_erträge))

    # LCOH_System Definieren
        @constraint(model, LCOH_System == (I_Coll + I_HP + diskontierte_wartungskosten_system) / (diskontierte_erträge_system))

# 6. Ziel definieren
    @objective(model, Min, LCOH)

# 7. Modell lösen
    optimize!(model)

# 8. Stauts ausgeben
    status = termination_status(model)
    println("Status: ", status)

Hi @fynnhoffmann54, welcome to the forum :smile:

Here’s the documentation for how to debug a infeasible JuMP model:

The issue is likely not those constraints in isolation, but how they interact with other constraints.

It’s a bit hard to offer more specific advice without the input data though. Can you share it?

p.s., I edited your post to wrap all code in backticks for formatting

Hi @odow,

thank you very much for your reply.

I am happy to share the input data. These come from an Excel sheet and the colums B2:B11, E2:E11, F2:F11 and G2:G11 are relevant.

The problem is that is also happens that at some hours there are negative electricity prices and the optimization problem would try to use them to minimize the operation & maintenance costs. However, the heat pump (HP) must not consume any electricity when it is not active (i.e. E_sys_to_HP[s] = 0 for the element s of the set S).

I hope the input data is helpful and you / someone can help me further.

I would really appreciate any feedback or further questions.

Best regards
Fynn

Can you provide input that we can copy paste? Im not going to transcribe data from an image.

I’d follow the instructions in the documentation.

Simplify the problem by removing constraints and variables, solve a feasibility relaxation, etc. Assuming the problem has a solution for the input data, there is an error somewhere in your logic.

Hello @odow,

sorry for my late reply - my internet was down for a while.

First of all, thank you for your advice. You were absolutely right that there must be an error in the code logic. I’m not that familiar with Julia and Ipopt yet, so I thought it had something to do with the solver settings. But I found the error and the program is now working.

However, I am currently encountering another problem. If I limit the set to a small number (about up to 100), the program works finde and I get a solution. But if I enter a large set, e.g. is equal to 2000, after a while I get the message “converged to a point of local infeasibility - problem is locally infeasible”.

Do you or anyone else know if this could have something to do with my solver settings? I have set the maximum iteration to 200000, relaxed the tolerance to 1e-2 and also specified valid start values.

I have completely reworked the code and can provide it again if needed.

I would ge grateful if someone could give me a hint where the problem might lie when working with a large set.

Many thanks and best regards,
Fynn

You’re trying to solve a non-convex MINLP. These are notoriously difficult to solve.

It’s hard to tell without the full log, but it usually means that either the problem has no feasible solution, or that your starting point was infeasible and Ipopt could not find a feasible point. Note that Juniper does a branch-and-bound search on the integer values, so even if your initial point is feasible, Juniper may not have a feasible point for other subproblems that it solves.

If you can provide a reproducible data set and your working code, there might be improvements that we can suggest.

Hi @odow

thank you for your patience. I know that the problem is difficult to solve. Unfortunately, however, I would like to avoid that the model no longer includes the non-linearity of the costs.

Attached is my complete program code and also a few comments:

  1. I have introduced the binary variable “z_Temp[s]”, this is not relevant for the model and is not used, but if I delete this line, the model cannot be solved → why is not clear to me
  2. The two inequality constraints in the section “#Kollektor”, which deal with the binary variable “z_Netz[s]” are my biggest problem and give me some puzzles:
  • a) If only the row section of the Excel sheet of 669:672 is considered and the temperature column “F” is set to 90°C for all four values, the model cannot be solved
  • b) if all four values are set to 91°C or all four values are set to 80°C, the model can be solved; but not if two are set to 91°C and two are set to 80°C
  • c) if, however, two values are set to 100°C and two to 80°C, the problem can be solved.
using JuMP, XLSX, MathOptInterface, Ipopt, Juniper

ipopt = optimizer_with_attributes(Ipopt.Optimizer, "print_level" => 4, "max_iter" => 20000, "tol" => 1e-3)
optimizer = optimizer_with_attributes(Juniper.Optimizer, "nl_solver" => ipopt, "mip_gap" => 0.05, "time_limit" => 86400) #Maximal 5% Optimalitätslücke, #Zeitlimit von 24 Stunden
model = Model(optimizer)

# 0. Pfad zur Excel-Datei und Excel-Datei einlesen
    daten = XLSX.readxlsx("C:/Users/.../Source.xlsx") #have to be adjusted according to the corresponding path

    # Kollektoren
    Werte_Flachkollektoren = daten["Tabelle_Daten"]["B2:B2191"]
    EDATA_Coll_FK_values = [Werte_Flachkollektoren[i][1] for i in 1:length(Werte_Flachkollektoren)] #Flachkollektoren - EDATA [kWh / m^2]

    # Energienachfrage
    Werte_Energienachfrage = daten["Tabelle_Daten"]["E2:E2191"]
    Energienachfrage_values = [Werte_Energienachfrage[i][1] for i in 1:length(Werte_Energienachfrage)] #Energienachfrage - EDATA [MWh]

    # Temperatur-Nachfrage
    Werte_Temperatur_Nachfrage = daten["Tabelle_Daten"]["F2:F2191"] #Temperaturen des Vorlaufs des Bedarfs in °C
    Temp_Bedarf_values = [Werte_Temperatur_Nachfrage[i][1] for i in 1:length(Werte_Temperatur_Nachfrage)] #Temperaturen des Vorlaufs des Bedarfs in °C - T_H (s)

    # Strompreis
    Werte_Strompreis = daten["Tabelle_Daten"]["G2:G2191"] #Strompreise in €/MWh zu jeder Stunde (entnommen Agorameter, 2023)
    Strompreis_values = [Werte_Strompreis[i][1] for i in 1:length(Werte_Strompreis)] #Strompreise in €/MWh zu jeder Stunde

println("Anzahl der eingelesenen Werte (Flachkollektoren): ", length(EDATA_Coll_FK_values))
println("Anzahl der eingelesenen Werte (Energienachfrage): ", length(Energienachfrage_values))
println("Anzahl der eingelesenen Werte (Temperaturbedarf): ", length(Temp_Bedarf_values))
println("Anzahl der eingelesenen Werte (Strompreis): ", length(Strompreis_values))

# 1. Mengen definieren
S = 1:2190    # Stunden

# 2. Parameter definieren
    # Stundendelta
        DeltaHour = 4

    #Diskontierungsfaktor
        diskont = 1.05  # r=0,5

    #Kollektoren
        n_opt_FK = 0.838
        a1_FK = 2.46
        a2_FK = 0.00971

        T_M_FK = 75 #°C
        T_u_peak = 15.4 #°C

    #Wärmepumpe
        E_elektr_max = 2 #MWh (Abhängig von der Nennleistung der HP - Annahme: 2 MW Leistung_Peak)
        COP_max = 5 # Maximale COP einstellen, die erreicht werden kann

# 3. Variablen definieren
    # Nachfrage
        @variable(model, E_Demand >= 0)

        @variable(model, z_Temp[s in S], Bin, start = (Temp_Bedarf_values[s] > 90 ? 0 : 1)) # is not required for modeling, but if omitted, the result is that the model is "unsolvable" for temp. <90°C -> not understandable why

    # System
        @variable(model, Ertrag_System >= 0)
        @variable(model, z_Netz[s in S], Bin, start = (Temp_Bedarf_values[s] > 90 ? 0 : 1))
        @variable(model, SD >= 0)

    # Zusatz
        @variable(model, OperationWartung_Zusatz >= 0)
        @variable(model, Ertrag_Zusatz >= 0)
        @variable(model, Ertrag_Zusatz_zum_Bedarf[s in S] >= 0)

    # Kollektoren
        @variable(model, I_Coll >= 0)
        @variable(model, A_Coll >= 0)
        @variable(model, I_spez_Coll >= 0)
        @variable(model, P_Coll_peak >= 0, start = 5.0)
        @variable(model, Wirkungsgrad_Coll_non_conc >= 0)
        @variable(model, OperationWartung_Kollektor >= 0)
        @variable(model, Ertrag_Coll[s in S] >= 0)
        @variable(model, Ertrag_Coll_zur_HP[s in S] >= 0)
        @variable(model, Ertrag_Coll_zum_Netz[s in S] >= 0)
        @variable(model, Ertrag_Coll_Vernichten[s in S] >= 0) # Energie, die vernichtet wird (wird bestraft mit hohen Kosten)
        @variable(model, Ertrag_Vernichten >= 0) # Energie, die vernichtet wird

    # Wärmepumpe
        @variable(model, E_sys_zur_HP[s in S] >= 0)
        @variable(model, Temp_HP_Quelle[s in S] >= 0)
        @variable(model, COP_HP[s in S] >= 0) #COP of HP
        @variable(model, E_elektr[s in S] >= 0) #Elektrische Energie
        @variable(model, E_HP_zum_Bedarf[s in S] >= 0) #Energie, die von der HP zur Deckung des Bedarfs eingesetzt wird
        @variable(model, Price_HP[s in S]) #Preis, den die HP aufgrund der elektr. Energie verursacht
        @variable(model, I_HP >= 0) #Investitionssume der HP
        @variable(model, OperationWartung_HP)
        @variable(model, z_Sys_zur_HP[s in S], Bin)

# 4. Zielfunktionsvariable(n) definieren
    @variable(model, LCOH >= 0)

# 5. Nebenbedingungen aufstellen
    # Kollektor
        @constraint(model, A_Coll * I_spez_Coll <= I_Coll)
        @constraint(model, (504.25 * A_Coll^(-0.077)) == I_spez_Coll)
        @constraint(model, P_Coll_peak <= 10)
        @constraint(model, P_Coll_peak >= 5)
        @constraint(model, 9.066375 * (10^(-4)) * Wirkungsgrad_Coll_non_conc * A_Coll == P_Coll_peak)
        @constraint(model, (n_opt_FK - ((a1_FK*(T_M_FK-T_u_peak) + a2_FK * ((T_M_FK-T_u_peak)^2)) / 906.6375)) == Wirkungsgrad_Coll_non_conc)

        for s in S
            @constraint(model, Ertrag_Coll[s] == (EDATA_Coll_FK_values[s] * A_Coll) * (1 / 1000))
        end

        @constraint(model, OperationWartung_Kollektor == 0.0075 * I_Coll)

        for s in S
            @constraint(model, Temp_Bedarf_values[s] <= 90 + 85*(1-z_Netz[s])) #Does not work if the temperature = 90°C -> not understandable why
        end

        for s in S
            @constraint(model, Temp_Bedarf_values[s] >= 90 - 85*z_Netz[s]) #Does not work if the temperature = 90°C -> not understandable why
        end

        for s in S 
            @constraint(model, Ertrag_Coll[s] == Ertrag_Coll_zur_HP[s] + Ertrag_Coll_zum_Netz[s] + Ertrag_Coll_Vernichten[s])
        end

        for s in S
            @constraint(model, Ertrag_Coll_zum_Netz[s] <= 100000*z_Netz[s])
        end

    # Wärmepumpe
        for s in S 
            @constraint(model, E_sys_zur_HP[s] == Ertrag_Coll_zur_HP[s])
        end

        for s in S 
            @constraint(model, E_sys_zur_HP[s] <= 10000*z_Sys_zur_HP[s])
        end

        for s in S 
            @constraint(model, Temp_HP_Quelle[s] == (90+273.15))
        end
 
        for s in S 
            @constraint(model, COP_HP[s] <= 0.5*(((Temp_Bedarf_values[s]+273.15) / ((Temp_Bedarf_values[s]+273.15)-Temp_HP_Quelle[s])))*(1-z_Netz[s])) #Carnot-Wirkungsgrad!!
        end

        for s in S 
            @constraint(model, COP_HP[s] <= COP_max*(1-z_Netz[s]))
        end

        for s in S 
            @constraint(model, E_elektr[s] <= (E_elektr_max*DeltaHour) * z_Sys_zur_HP[s]) # MIT DELTA HOUR!!!!
        end

        for s in S 
            @constraint(model, E_elektr[s] * COP_HP[s] == E_HP_zum_Bedarf[s])
        end

        for s in S
            @constraint(model, E_HP_zum_Bedarf[s] == E_elektr[s] + E_sys_zur_HP[s])
        end

        for s in S 
            @constraint(model, Price_HP[s] == E_elektr[s] * Strompreis_values[s])
        end

        @constraint(model, Ertrag_System == sum(E_HP_zum_Bedarf[s] for s in S) + sum(Ertrag_Coll_zum_Netz[s] for s in S))


        @constraint(model, I_HP >= E_elektr_max * (5881.1*(E_elektr_max^(-0.779)))) #MUSS NOCH ANGEPASST WERDEN -> BISHER NUR BEISPIELHAFT!!!!!!

        @constraint(model, OperationWartung_HP == 0.01*I_HP + sum(Price_HP[s] for s in S)) #Annahme: 1% der Investitionskosten jedes Jahr

    # Weitere Bedingungen
        
        for s in S 
            @constraint(model, Ertrag_Zusatz_zum_Bedarf[s] == Energienachfrage_values[s] - (E_HP_zum_Bedarf[s] + Ertrag_Coll_zum_Netz[s]))
        end

        @constraint(model, Ertrag_Zusatz == sum(Ertrag_Zusatz_zum_Bedarf[s] for s in S))

        @constraint(model, OperationWartung_Zusatz == 100000*Ertrag_Zusatz + 100000*Ertrag_Vernichten)

        @constraint(model, E_Demand == sum(Energienachfrage_values[s] for s in S))

        @constraint(model, SD == (Ertrag_System / E_Demand)*100)

        @constraint(model, Ertrag_Vernichten == sum(Ertrag_Coll_Vernichten[s] for s in S))
        
    # LCOH Zwischenausdrücke
        # Diskontierte Betriebs- & Wartungskosten
            @expression(model, diskontierte_wartungskosten, (OperationWartung_Kollektor + OperationWartung_HP + OperationWartung_Zusatz) / diskont)
        # Diskontierte Erträge
            @expression(model, diskontierte_erträge, (Ertrag_Zusatz + Ertrag_System + Ertrag_Vernichten) / diskont)

    # LCOH_System Zwischenausdrücke
        # Diskontierte Betriebs- & Wartungskosten (System)
            @expression(model, diskontierte_wartungskosten_system, (OperationWartung_Kollektor + OperationWartung_HP) / diskont)
        # Diskontierte Erträge (System)
           @expression(model, diskontierte_erträge_system, (Ertrag_System) / diskont)
        # Diskontierte Investitions-, Betriebs- & Wartungskosten
           @expression(model, diskontierte_Kosten_System, I_Coll + I_HP + diskontierte_wartungskosten_system)

    # LCOH Definieren
        @constraint(model, LCOH >= (I_Coll + I_HP + diskontierte_wartungskosten) / (diskontierte_erträge))

    # LCOH_System Definieren
        @expression(model, LCOH_System, diskontierte_Kosten_System / (diskontierte_erträge_system+0.0000005)) #Für den Fall, dass Erträge des Systems = 0 wird hier ein Minimalwert addiert

# 6. Ziel definieren
    @objective(model, Min, LCOH)

# 7. Modell lösen
    optimize!(model)

# 8. Status ausgeben
    status = termination_status(model)
    println("Status: ", status)

Also attached is the data, which unfortunately I can only provide as a webp file because I cannot upload an xlsx file. However, I have managed to convert the webp file into a txt file using an online converter and then into an Excel file. If there is another way to provide the data, I would be very happy if someone could let me know.

Source_WEBP

In general, is the reason possibly that “Ipopt” is not the appropriate solver?

In general, I would first like to thank you and hope that it will be possible to uncover an error or make an adjustment that makes the model modelable.

Best regards,
Fynn

Provide the data as a plain-text CSV file?

As a general comment. Your model could be much improved.

For example, you’ve defined:

@variable(model, Temp_HP_Quelle[s in S] >= 0)

but then you have the constraint:

for s in S 
    @constraint(model, Temp_HP_Quelle[s] == (90+273.15))
end

Just do (in the data section, before the JuMP model):

Temp_HP_Quelle = 90 + 273.15

You’ve defined

@variable(model, P_Coll_peak >= 0, start = 5.0)

but then you have

@constraint(model, P_Coll_peak <= 10)
@constraint(model, P_Coll_peak >= 5)

Do instead

@variable(model, 5 <= P_Coll_peak <= 10, start = 5.0)

You’ve defined

@variable(model, Ertrag_Coll_zur_HP[s in S] >= 0)
@variable(model, E_sys_zur_HP[s in S] >= 0)

but then you have

for s in S 
    @constraint(model, E_sys_zur_HP[s] == Ertrag_Coll_zur_HP[s])
end

These are the same variable. You need only one.

Instead of

@variable(model, Ertrag_Zusatz >= 0)
@variable(model, Ertrag_Zusatz_zum_Bedarf[s in S] >= 0)
@constraint(model, Ertrag_Zusatz == sum(Ertrag_Zusatz_zum_Bedarf[s] for s in S))

do

@variable(model, Ertrag_Zusatz_zum_Bedarf[s in S] >= 0)
@expression(model, Ertrag_Zusatz, sum(Ertrag_Zusatz_zum_Bedarf[s] for s in S))

You also have (I assume, hard to tell without data) very weak big-M constraints:

for s in S
    @constraint(model, Ertrag_Coll_zum_Netz[s] <= 100_000 * z_Netz[s])
end

can you put a better upper bound on Ertrag_Coll_zum_Netz?

Hi @odow,

thank you very much for your comments.

Unfortunately, I cannot provide a CSV file either. But you are of course right that I could make improvements to the syntax for certain expressions and formulations. I have deliberately chosen to do this because in the future, in addition to the one energy source, others will also be integrated into the model.

But if necessary, I will revise this again first and would write another comment then :wink:

Thanks again for all your support, that really helped me!

Best regards
Fynn

Regarding the Big M question - the selected upper bound should be sufficient, as we are only talking about 2-10 MWh here.

I don’t know that we can be much help if you cannot provide a reproducible example that we can copy and paste into the REPL and have it run.

MINLPs are quite dependent on the model formulation. There might be a mistake in your constraints, or Juniper might just struggle to solve the problem because of the issues with the formulation.

Note that MIP solvers have a presolve routine that can simplify many of these “simple” issues like E_sys_zur_HP[s] == Ertrag_Coll_zur_HP[s]. MINLP solvers like Juniper do not have a presolve routine, so the quality of the input model matters much more.

HI @odow,

I know that it is relatively difficult to analyze a model without the data. I could possibly provide the data in a direct chat (if I can upload XLSX files there).

I have one more question: I am currently building my model as follows:

    using JuMP, XLSX, Ipopt, Juniper, SCIP, MathOptInterface

    ipopt = optimizer_with_attributes(Ipopt.Optimizer, "print_level" => 4, "max_iter" => 25000, "tol" => 1e-2) #für NL-Teil, Iterationslimit = 20000
    scip = optimizer_with_attributes(SCIP.Optimizer, "limits/time" => 86400, "display/verblevel" => 4, "limits/gap" => 0.07) 

    optimizer = optimizer_with_attributes(Juniper.Optimizer, "nl_solver" => ipopt, "mip_solver" => scip, "time_limit" => 86400) 
    model = Model(optimizer)

The model also works very well for small instances. For larger quantities it does not find a solution. The following output keeps changing in the terminal:

EXIT: Converged to a point of local infeasibility. Problem may be infeasible.
Total number of variables............................:    24553
                     variables with only lower bounds:    17522
                variables with lower and upper bounds:     4841
                     variables with only upper bounds:        0
Total number of equality constraints.................:    13141
Total number of inequality constraints...............:    24090
        inequality constraints with only lower bounds:     4380
   inequality constraints with lower and upper bounds:        0
        inequality constraints with only upper bounds:    19710


Number of Iterations....: 48

                                   (scaled)                 (unscaled)
Objective...............:   4.2476654735911397e+04    4.2476654735911397e+04
Dual infeasibility......:   2.6505443088353786e-05    2.6505443088353786e-05
Constraint violation....:   3.1691278701921282e-06    1.5120072134777729e-05
Variable bound violation:   8.5261194628189691e-09    8.5261194628189691e-09
Complementarity.........:   1.0091835204685179e-05    1.0091835204685179e-05
Overall NLP error.......:   2.1807936325926105e-05    2.6505443088353786e-05


Number of objective function evaluations             = 57
Number of objective gradient evaluations             = 49
Number of equality constraint evaluations            = 57
Number of inequality constraint evaluations          = 57
Number of equality constraint Jacobian evaluations   = 49
Number of inequality constraint Jacobian evaluations = 49
Number of Lagrangian Hessian evaluations             = 48
Total seconds in IPOPT                               = 7.148

EXIT: Optimal Solution Found.
   630     633            109591.88                    42476.64          61.24% 40582.2     -       99.9%
Total number of variables............................:    24554
                     variables with only lower bounds:    17522
                variables with lower and upper bounds:     4842
                     variables with only upper bounds:        0
Total number of equality constraints.................:    13141
Total number of inequality constraints...............:    24090
        inequality constraints with only lower bounds:     4380
   inequality constraints with lower and upper bounds:        0
        inequality constraints with only upper bounds:    19710


Number of Iterations....: 105

                                   (scaled)                 (unscaled)
Objective...............:   4.2820147573091177e+04    4.2820147573091177e+04
Dual infeasibility......:   9.9999999917695814e-01    9.9999999917695814e-01
Constraint violation....:   9.9999998999999995e-01    9.9999998999999995e-01
Variable bound violation:   0.0000000000000000e+00    0.0000000000000000e+00
Complementarity.........:   1.9271392451518327e-05    1.9271392451518327e-05
Overall NLP error.......:   9.9999999917695814e-01    9.9999999917695814e-01


Number of objective function evaluations             = 136
Number of objective gradient evaluations             = 37
Number of equality constraint evaluations            = 136
Number of inequality constraint evaluations          = 136
Number of equality constraint Jacobian evaluations   = 113
Number of inequality constraint Jacobian evaluations = 113
Number of Lagrangian Hessian evaluations             = 106
Total seconds in IPOPT                               = 14.490

EXIT: Converged to a point of local infeasibility. Problem may be infeasible.
Total number of variables............................:    24554
                     variables with only lower bounds:    17522
                variables with lower and upper bounds:     4842
                     variables with only upper bounds:        0
Total number of equality constraints.................:    13141
Total number of inequality constraints...............:    24090
        inequality constraints with only lower bounds:     4380
   inequality constraints with lower and upper bounds:        0
        inequality constraints with only upper bounds:    19710


Number of Iterations....: 48

                                   (scaled)                 (unscaled)
Objective...............:   4.2476654735778749e+04    4.2476654735778749e+04
Dual infeasibility......:   6.9820658279695635e-04    6.9820658279695635e-04
Constraint violation....:   6.5167618323869192e-07    6.5167618323869192e-07
Variable bound violation:   8.5261111330123899e-09    8.5261111330123899e-09
Complementarity.........:   1.0184396490862023e-05    1.0184396490862023e-05
Overall NLP error.......:   5.7447831478652130e-04    6.9820658279695635e-04


Number of objective function evaluations             = 55
Number of objective gradient evaluations             = 49
Number of equality constraint evaluations            = 55
Number of inequality constraint evaluations          = 55
Number of equality constraint Jacobian evaluations   = 49
Number of inequality constraint Jacobian evaluations = 49
Number of Lagrangian Hessian evaluations             = 48
Total seconds in IPOPT                               = 6.961

EXIT: Optimal Solution Found.
   630     632            109591.88                    42476.64          61.24% 40603.9     -       99.9%
Total number of variables............................:    24555
                     variables with only lower bounds:    17522
                variables with lower and upper bounds:     4843
                     variables with only upper bounds:        0
Total number of equality constraints.................:    13141
Total number of inequality constraints...............:    24090
        inequality constraints with only lower bounds:     4380
   inequality constraints with lower and upper bounds:        0
        inequality constraints with only upper bounds:    19710


Number of Iterations....: 103

                                   (scaled)                 (unscaled)
Objective...............:   4.2797770782259235e+04    4.2797770782259235e+04
Dual infeasibility......:   9.9999999914062632e-01    9.9999999914062632e-01
Constraint violation....:   9.9999998999999995e-01    9.9999998999999995e-01
Variable bound violation:   0.0000000000000000e+00    0.0000000000000000e+00
Complementarity.........:   1.9280566550511761e-05    1.9280566550511761e-05
Overall NLP error.......:   9.9999999914062632e-01    9.9999999914062632e-01


Number of objective function evaluations             = 130
Number of objective gradient evaluations             = 40
Number of equality constraint evaluations            = 130
Number of inequality constraint evaluations          = 130
Number of equality constraint Jacobian evaluations   = 111
Number of inequality constraint Jacobian evaluations = 111
Number of Lagrangian Hessian evaluations             = 104
Total seconds in IPOPT                               = 14.301

EXIT: Converged to a point of local infeasibility. Problem may be infeasible.
Total number of variables............................:    24555
                     variables with only lower bounds:    17522
                variables with lower and upper bounds:     4843
                     variables with only upper bounds:        0
Total number of equality constraints.................:    13141
Total number of inequality constraints...............:    24090
        inequality constraints with only lower bounds:     4380
   inequality constraints with lower and upper bounds:        0
        inequality constraints with only upper bounds:    19710


Number of Iterations....: 49

                                   (scaled)                 (unscaled)
Objective...............:   4.2476654735864817e+04    4.2476654735864817e+04
Dual infeasibility......:   1.4100977523412439e-06    1.4100977523412439e-06
Constraint violation....:   1.8769839016385959e-09    1.8769839016385959e-09
Variable bound violation:   8.5259936587859752e-09    8.5259936587859752e-09
Complementarity.........:   1.0000723915700557e-05    1.0000723915700557e-05
Overall NLP error.......:   8.6820133996463209e-06    1.0000723915700557e-05


Number of objective function evaluations             = 64
Number of objective gradient evaluations             = 50
Number of equality constraint evaluations            = 64
Number of inequality constraint evaluations          = 64
Number of equality constraint Jacobian evaluations   = 50
Number of inequality constraint Jacobian evaluations = 50
Number of Lagrangian Hessian evaluations             = 49
Total seconds in IPOPT                               = 7.150

EXIT: Optimal Solution Found.
   630     631            109591.88                    42476.64          61.24% 40625.7     -       99.9%   
Total number of variables............................:    24556
                     variables with only lower bounds:    17522
                variables with lower and upper bounds:     4844
                     variables with only upper bounds:        0
Total number of equality constraints.................:    13141
Total number of inequality constraints...............:    24090
        inequality constraints with only lower bounds:     4380
   inequality constraints with lower and upper bounds:        0
        inequality constraints with only upper bounds:    19710


Number of Iterations....: 106

                                   (scaled)                 (unscaled)
Objective...............:   4.2814721138162517e+04    4.2814721138162517e+04
Dual infeasibility......:   9.9999999916798588e-01    9.9999999916798588e-01
Constraint violation....:   9.9999998999999995e-01    9.9999998999999995e-01
Variable bound violation:   0.0000000000000000e+00    0.0000000000000000e+00
Complementarity.........:   1.9148006907655055e-05    1.9148006907655055e-05
Overall NLP error.......:   9.9999999916798588e-01    9.9999999916798588e-01


Number of objective function evaluations             = 129
Number of objective gradient evaluations             = 37
Number of equality constraint evaluations            = 129
Number of inequality constraint evaluations          = 129
Number of equality constraint Jacobian evaluations   = 114
Number of inequality constraint Jacobian evaluations = 114
Number of Lagrangian Hessian evaluations             = 107
Total seconds in IPOPT                               = 14.409

Can you or someone else try to explain to me why the solver does not abort when it has found an “optimal solution” several time, wich does not improve over the next iterations? Should I adjust the solver parameters and if so, how?

Many thanks in advance and best regards
Fynn

Turn off printing from Ipopt and just look at the log from Juniper.

Note that Juniper will solve many many Ipopt subproblems. You can essentially ignore the logs from Ipopt. Once you have something working (e.g., no numerical issues, etc), in most cases they are not helpful.

1 Like

Hi @odow,

thanks, that helped me a lot! Hopefully this is my last question now - does anyone see or know why I get the message: “WARNING: Cycle detected!” when I try to solve the problem?
I suspect this is the reason why my calculation time is quite large (Time greater than 32h needed when trying to solve a problem with a set of S = 1:2190).

    using JuMP, XLSX, Ipopt, Juniper, SCIP, MathOptInterface
    ipopt = optimizer_with_attributes(Ipopt.Optimizer, "print_level" => 1, "max_iter" => 10000, "tol" => 1e-2) #für NL-Teil, Iterationslimit = 20000
    scip = optimizer_with_attributes(SCIP.Optimizer, "limits/time" => 80000, "display/verblevel" => 4, "limits/gap" => 0.05) #für MI-Teil - 5% Optimalitätslücke, Zeitlimit von 24 Stunden
    optimizer = optimizer_with_attributes(Juniper.Optimizer, "nl_solver" => ipopt, "mip_solver" => scip, "time_limit" => 80000, "mip_gap" => 0.05) #Maximal 5% Optimalitätslücke, #Zeitlimit von 24 Stunden
    model = Model(optimizer)
    
    # 0. Pfad zur Excel-Datei und Excel-Datei einlesen
        daten = XLSX.readxlsx("SOURCE.xlsx")
    
        # Kollektoren
        Werte_Flachkollektoren = daten["Tabelle_Daten"]["B202:B231"]
        EDATA_Coll_FK_values = [Werte_Flachkollektoren[i][1] for i in 1:length(Werte_Flachkollektoren)] #Flachkollektoren - EDATA [kWh / m^2]
    
        # Energienachfrage
        Werte_Energienachfrage = daten["Tabelle_Daten"]["E202:E231"]
        Energienachfrage_values = [Werte_Energienachfrage[i][1] for i in 1:length(Werte_Energienachfrage)] #Energienachfrage - EDATA [MWh]
    
        # Temperatur-Nachfrage
        Werte_Temperatur_Nachfrage = daten["Tabelle_Daten"]["F202:F231"] #Temperaturen des Vorlaufs des Bedarfs in °C
        Temp_Bedarf_values_eingelesen = [Werte_Temperatur_Nachfrage[i][1] for i in 1:length(Werte_Temperatur_Nachfrage)] #Temperaturen des Vorlaufs des Bedarfs in °C - T_H (s)
        Temp_Bedarf_values = [if 89 <= T <= 91 89 else T end for T in Temp_Bedarf_values_eingelesen] # ist notwendig, um die Temperatur von 90°C zu vermeiden (enthalten in der NB)

        # Strompreis
        Werte_Strompreis = daten["Tabelle_Daten"]["G202:G231"] #Strompreise in €/MWh zu jeder Stunde (entnommen Agorameter, 2023)
        Strompreis_values = [Werte_Strompreis[i][1] for i in 1:length(Werte_Strompreis)] #Strompreise in €/MWh zu jeder Stunde

    println("Anzahl der eingelesenen Werte (Flachkollektoren): ", length(EDATA_Coll_FK_values))
    println("Anzahl der eingelesenen Werte (Energienachfrage): ", length(Energienachfrage_values))
    println("Anzahl der eingelesenen Werte (Temperaturbedarf): ", length(Temp_Bedarf_values))
    println("Anzahl der eingelesenen Werte (Strompreis): ", length(Strompreis_values))
    
    # 1. Mengen definieren
    S = 1:30    # Stunden
    
    # 2. Parameter definieren
        # System
            diskont = 1.05

        # Stundendelta
            DeltaHour = 10
    
        #Kollektoren
            n_opt_FK = 0.838
            a1_FK = 2.46
            a2_FK = 0.00971
    
            T_M_FK = 75 #°C
            T_u_peak = 15.4 #°C
    
            Wirkungsgrad_Coll_non_conc = (n_opt_FK - ((a1_FK*(T_M_FK-T_u_peak) + a2_FK * ((T_M_FK-T_u_peak)^2)) / 906.6375))
            A_Coll = 17281.46 # m^2 um 10MW-Peak-Leistung zu erreichen

            MaximaleEinspeisetemperatur = 90 + 273.15

        # Pufferspeicher
            DichteWasser = 997 
            cp_Wasser_eingabe = 4183 # J/(kg*K)
            cp_Wasser = cp_Wasser_eingabe * (1/3600000) # kWh/(kg*K)
            DeltaTemperaturTagesspeicher = 90
            Effizienz_Tagesspeicher = 0.96 
            CAP_Tagesspeicher_Discharging = 40 #MW
            CAP_Tagesspeicher_Charging = 40 #MW
            MaxVolumen = 3 #m^3

        # Erdbeckenspeicher
            DeltaTemperaturSaisonalspeicher = 90
            Effizienz_Saisonalspeicher = 0.7
            CAP_Saisonalspeicher_Discharging = 40 # MW
            CAP_Saisonalspeicher_Charging = 40 #MW

        # WÄRMEPUMPE - MODELLHAFT!!!!
            MaximaleHeizleistung = 4 #MW
            COP_max = 5
            E_elektr_max = 0.75 #MW
            E_elektr_max_verwendet = E_elektr_max*DeltaHour
    
    # 3. Variablen & Expressionen definieren
        # System
            @variable(model, z_Netz[s in S], Bin, start = (Temp_Bedarf_values[s] > 91 ? 0 : 1))
    
        # Zusatz
            @variable(model, 0 <= Ertrag_Zusatz_zum_Bedarf[s in S])
    
        # Kollektoren
            @variable(model, 0 <= Ertrag_Coll_zur_HP[s in S])
            @variable(model, 0 <= Ertrag_Coll_zum_Netz[s in S])
            @variable(model, 0 <= Ertrag_Coll_zum_Tagesspeicher[s in S] <= (CAP_Tagesspeicher_Charging*DeltaHour))
            @variable(model, 0 <= Ertrag_Coll_zum_Saisonalspeicher[s in S] <= (CAP_Saisonalspeicher_Charging*DeltaHour))

        # Pufferspeicher
            @variable(model, 0.5 <= V_Tagesspeicher <= MaxVolumen, start = 0.5) #m^3
            @variable(model, 0 <= EnergieTagesspeicher[s in S])
            @variable(model, 0 <= E_Tagesspeicher_discharge[s in S])
            @variable(model, 0 <= T_Tagesspeicher[s in S] <= MaximaleEinspeisetemperatur)

        # Saisonalspeicher
            @variable(model, 50 <= V_Saisonalspeicher <= 100000, start = 50)
            @variable(model, 0 <= EnergieSaisonalspeicher[s in S])
            @variable(model, 0 <= E_Saisonalspeicher_discharge[s in S] <= (CAP_Saisonalspeicher_Discharging*DeltaHour))
            @variable(model, 0 <= T_Saisonalspeicher[s in S] <= MaximaleEinspeisetemperatur)
    
        # Wärmepumpe
            @variable(model, 0 <= E_sys_zur_HP[s in S])
            @variable(model, 0 <= E_HP_zum_Bedarf[s in S]) #Energie, die von der HP zur Deckung des Bedarfs eingesetzt wird
            @variable(model, 0 <= Temp_HP_Quelle[s in S] <= MaximaleEinspeisetemperatur)
            @variable(model, 0 <= COP_HP[s in S] <= COP_max)
            @variable(model, 0 <= E_elektr[s in S])
            @variable(model, z_Sys_zur_HP[s in S], Bin)

        # Expressionen
            # Nachfrage
                @expression(model, E_Demand, sum(Energienachfrage_values[s] for s in S))
    
            # Kollektor
                @expression(model, Ertrag_Coll[s in S], (EDATA_Coll_FK_values[s] * A_Coll) * (1 / 1000))
                @expression(model, P_Coll_peak, 9.066375 * (10^(-4)) * Wirkungsgrad_Coll_non_conc * A_Coll)
                @expression(model, I_Coll, A_Coll * (504.25 * A_Coll^(-0.077)))
                @expression(model, OperationWartung_Kollektor, 0.0075 * I_Coll)

            # Pufferspeicher
                @expression(model, C_Tagesspeicher_max, DichteWasser * V_Tagesspeicher * cp_Wasser * DeltaTemperaturTagesspeicher * (1 / 1000)) # um umzurechnen von kWh in MWh
                @expression(model, I_Tagesspeicher, V_Tagesspeicher * (5550.3 * (V_Tagesspeicher^(-0.47))))
                @expression(model, OperationWartung_Tagesspeicher, 0.01 * I_Tagesspeicher)

            # Saisonalspeicher
                @expression(model, C_Saisonalspeicher_max, DichteWasser * V_Saisonalspeicher * cp_Wasser * DeltaTemperaturSaisonalspeicher * (1 / 1000)) # um umzurechnen von kWh in MWh
                @expression(model, E_Saisonalspeicher_zur_HP[s in S], E_Saisonalspeicher_discharge[s])
                @expression(model, I_Saisonalspeicher, V_Saisonalspeicher * (1744.6 * (V_Saisonalspeicher^(-0.341))))
                @expression(model, OperationWartung_Saisonalspeicher, 0.01 * I_Saisonalspeicher)

            # Wärmepumpe
                @expression(model, Price_HP[s in S], E_elektr[s] * Strompreis_values[s])
                @expression(model, I_HP, E_elektr_max * (5881.1*(E_elektr_max^(-0.779))))
                @expression(model, OperationWartung_HP, 0.01*I_HP + sum(Price_HP[s] for s in S))
    
            # System
                @expression(model, Ertrag_System, sum(E_HP_zum_Bedarf[s] for s in S) + sum(Ertrag_Coll_zum_Netz[s] for s in S))
    
            # Zusatz
                @expression(model, Ertrag_Zusatz, sum(Ertrag_Zusatz_zum_Bedarf[s] for s in S))
                @expression(model, OperationWartung_Zusatz, 2*Ertrag_Zusatz)

    # 4. Zielfunktionsvariable
        @variable(model, LCOH >= 0)
    
    # 5. Nebenbedingungen aufstellen
        # Kollektor
            for s in S
                @constraint(model, Temp_Bedarf_values[s] <= 90 + 2000*(1-z_Netz[s]))
                @constraint(model, Temp_Bedarf_values[s] >= 90 - 85*z_Netz[s])
            end

            for s in S
                @constraint(model, Ertrag_Coll_zum_Netz[s] <= 100000*z_Netz[s])
            end

            for s in S
                @constraint(model, Ertrag_Coll_zur_HP[s] <= 10000*(1-z_Netz[s]))
            end
    
            for s in S 
                @constraint(model, Ertrag_Coll[s] == Ertrag_Coll_zum_Netz[s] + Ertrag_Coll_zum_Tagesspeicher[s] + Ertrag_Coll_zum_Saisonalspeicher[s] + Ertrag_Coll_zur_HP[s])
            end
    
        # Pufferspeicher
            for s in S
                @constraint(model, EnergieTagesspeicher[s] <= DichteWasser * V_Tagesspeicher * cp_Wasser * DeltaTemperaturTagesspeicher * (1 / 1000))
            end

            for s in 1:1
                @constraint(model, EnergieTagesspeicher[s] == 0)
            end

            for s in S[1:(end-1)]
                @constraint(model, EnergieTagesspeicher[s+1] == EnergieTagesspeicher[s] + Ertrag_Coll_zum_Tagesspeicher[s] - (E_Tagesspeicher_discharge[s] / Effizienz_Tagesspeicher))
            end

            for s in S[end:end]
                @constraint(model, EnergieTagesspeicher[s] + Ertrag_Coll_zum_Tagesspeicher[s] - (E_Tagesspeicher_discharge[s] / Effizienz_Tagesspeicher) >= 0)
            end

            for s in S
                @constraint(model, T_Tagesspeicher[s] == (EnergieTagesspeicher[s]/(DichteWasser * V_Tagesspeicher * cp_Wasser * (1 / 1000))) + (5+273.15))
            end

        # Saisonalspeicher
            for s in S
                @constraint(model, EnergieSaisonalspeicher[s] <= DichteWasser * V_Saisonalspeicher * cp_Wasser * DeltaTemperaturSaisonalspeicher * (1 / 1000))
            end

            for s in 1:1
                @constraint(model, EnergieSaisonalspeicher[s] == 0)
            end

            for s in S[1:(end-1)]
                @constraint(model, EnergieSaisonalspeicher[s+1] == EnergieSaisonalspeicher[s] + Ertrag_Coll_zum_Saisonalspeicher[s] - (E_Saisonalspeicher_discharge[s] / Effizienz_Saisonalspeicher))
            end

            for s in S[end:end]
                @constraint(model, EnergieSaisonalspeicher[s] + Ertrag_Coll_zum_Saisonalspeicher[s] - (E_Saisonalspeicher_discharge[s] / Effizienz_Saisonalspeicher) >= 0)
            end

            for s in S
                @constraint(model, T_Saisonalspeicher[s] == (EnergieSaisonalspeicher[s]/(DichteWasser * V_Saisonalspeicher * cp_Wasser * (1 / 1000))) + (5+273.15))
            end
        
        # Wärmepumpe
            for s in S[1:1]
                @constraint(model, E_sys_zur_HP[s] == 0)
            end

            for s in S[1:(end-1)]
                @constraint(model, E_sys_zur_HP[s+1] == Ertrag_Coll_zur_HP[s] + E_Tagesspeicher_discharge[s] + E_Saisonalspeicher_zur_HP[s])
            end

            for s in S 
                @constraint(model, E_sys_zur_HP[s] <= 10000*z_Sys_zur_HP[s])
                @constraint(model, E_sys_zur_HP[s] >= 0.5*(z_Sys_zur_HP[s]))
                @constraint(model, E_HP_zum_Bedarf[s] <= 10000*E_sys_zur_HP[s])
            end

            for s in S[1:(end-1)]
                @constraint(model, Temp_HP_Quelle[s+1] == ((MaximaleEinspeisetemperatur*Ertrag_Coll_zur_HP[s] + T_Tagesspeicher[s]*E_Tagesspeicher_discharge[s] + T_Saisonalspeicher[s]*E_Saisonalspeicher_discharge[s])/(E_sys_zur_HP[s]+50*(1-z_Sys_zur_HP[s]))))
            end

            for s in S
                @constraint(model, COP_HP[s] <= 0.55 * z_Sys_zur_HP[s]* ((Temp_Bedarf_values[s]+273.15) / ((Temp_Bedarf_values[s]+273.15)-Temp_HP_Quelle[s])))
                @constraint(model, COP_HP[s] >= 0.4 * z_Sys_zur_HP[s]* ((Temp_Bedarf_values[s]+273.15) / ((Temp_Bedarf_values[s]+273.15)-Temp_HP_Quelle[s])))
            end

            for s in S
                @constraint(model, E_elektr[s] * COP_HP[s] == E_HP_zum_Bedarf[s])
            end
    
            for s in S
                @constraint(model, E_HP_zum_Bedarf[s] <= E_elektr[s] + E_sys_zur_HP[s])
            end
    
        # Weitere Bedingungen
            for s in S 
                @constraint(model, Ertrag_Zusatz_zum_Bedarf[s] >= Energienachfrage_values[s] - (E_HP_zum_Bedarf[s] + Ertrag_Coll_zum_Netz[s]))
            end

        # LCOH Zwischenausdrücke & Definition
            # Diskontierte Betriebs- & Wartungskosten
                @expression(model, diskontierte_wartungskosten, (OperationWartung_Kollektor + OperationWartung_Tagesspeicher + OperationWartung_Saisonalspeicher + OperationWartung_HP + OperationWartung_Zusatz) / diskont)
            # Diskontierte Erträge
                @expression(model, diskontierte_erträge, (Ertrag_System + Ertrag_Zusatz) / diskont)

            # LCOH Definieren
                @constraint(model, LCOH == (I_Coll + I_Tagesspeicher + I_Saisonalspeicher + I_HP + diskontierte_wartungskosten) / (diskontierte_erträge))

        # LCOH_System Zwischenausdrücke & Definition
            # Diskontierte Betriebs- & Wartungskosten (System)
                @expression(model, diskontierte_wartungskosten_system, (OperationWartung_Kollektor + OperationWartung_Tagesspeicher + OperationWartung_Saisonalspeicher + OperationWartung_HP) / diskont)
            # Diskontierte Erträge (System)
                @expression(model, diskontierte_erträge_system, (Ertrag_System) / diskont)

            # LCOH_System Definieren
                @expression(model, LCOH_System, (I_Coll + I_Tagesspeicher + I_Saisonalspeicher + I_HP + diskontierte_wartungskosten_system) / (diskontierte_erträge_system+0.5)) #Für den Fall, dass Erträge des Systems = 0 wird hier ein Minimalwert addiert  

    # 6. Ziel definieren
        @objective(model, Min, ((1.01*V_Tagesspeicher*(55.503 * (V_Tagesspeicher^(-0.47))) + 1.01*V_Saisonalspeicher*(17.446 * (V_Saisonalspeicher^(-0.341))) + 100*sum(Ertrag_Zusatz_zum_Bedarf[s] for s in S) - 2*sum(COP_HP[s] for s in S) + 4*sum(E_elektr[s] for s in S)) / ((sum(E_HP_zum_Bedarf[s] for s in S) + sum(Ertrag_Coll_zum_Netz[s] for s in S)+0.5)/diskont)))
    
    # 7. Modell lösen
        optimize!(model)
    
    # 8. Status ausgeben
        status = termination_status(model)
        println("Status: ", status)

Many thanks and best regards
Fynn