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?