From 3dcb19f5f68fb7170fb7f61fbf7515016e596ac8 Mon Sep 17 00:00:00 2001 From: Petra Reisz Date: Mon, 5 Jan 2026 08:26:23 +0100 Subject: [PATCH 1/2] feat: skeleton of piecewise linear efficiency --- .../examples/61_piecewise_linear_addon.yaml | 41 ++++++++++++++ assets/examples/files/61/CustomMath.jl | 55 +++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 assets/examples/61_piecewise_linear_addon.yaml create mode 100644 assets/examples/files/61/CustomMath.jl diff --git a/assets/examples/61_piecewise_linear_addon.yaml b/assets/examples/61_piecewise_linear_addon.yaml new file mode 100644 index 0000000..ce1f7ad --- /dev/null +++ b/assets/examples/61_piecewise_linear_addon.yaml @@ -0,0 +1,41 @@ +config: + general: + version: + core: 2.6.3 + optimization: + problem_type: MILP + snapshots: + count: 9 + solver: + name: highs + paths: + addons: files/61 + +addons: + CustomMath: {} + +carriers: + electricity: {} + gas: {} + +components: + grid_electricity: + type: Node + carrier: electricity + + grid_gas: + type: Node + carrier: gas + + demand_elec: + type: Profile + carrier: electricity + node_from: grid_electricity + value: [2.75, 5.50, 7.00, 8.00, 9.00, 10.00, 5.00, 5.00, 15.00] + + create_gas: + type: Profile + carrier: gas + node_to: grid_gas + mode: create + cost: 50.0 \ No newline at end of file diff --git a/assets/examples/files/61/CustomMath.jl b/assets/examples/files/61/CustomMath.jl new file mode 100644 index 0000000..497acf2 --- /dev/null +++ b/assets/examples/files/61/CustomMath.jl @@ -0,0 +1,55 @@ +module IESoptAddon_CustomMath + +using IESopt +import JuMP + +function initialize!(model::JuMP.Model, config::Dict) + # All functions are expected to return `true` if everything went well. + return true +end + +# The following functions are called after they were called for all core components: +# - setup! +# - construct_expressions! +# - construct_variables! +# - construct_constraints! +# - construct_objective! +# +# If you do not need to modify the model during a specific step, you can just not implement the function. + +function construct_variables!(model::JuMP.Model, config::Dict) + T = get_T(model) + + node_input = get_component(model, "grid_gas") + node_output = get_component(model, "grid_electricity") + + # we create here the breakpoints of the inputs + # We did not set any limit on the upper bound, let's take 20 as end of BPs + input_bp = 0.0:2:20.0 + eta(x, Pmax) = (x/Pmax)^1.5 + 0.1 + ouptput_bp = eta.(input_bp, 25.0) + N = length(input_bp) + @assert N == length(ouptput_bp) + + JuMP.@variable(model, 0<=λ[1:N]<=1) + + JuMP.@variable(model, var_input[t in T], lower_bound=0.0) + node_output.exp.generation = 1.1 .* JuMP.@variable(model, var_output[t in T], lower_bound=0.0, container=Array) + + JuMP.@constraints( + model, + var_input[t] == + [t in T], + # NOTE: This could be a more complex mathematical expression, e.g., a piecewise interpolation. + node_output.exp.generation[t] == 0.5 * model[:var_input][t] + ) + + for t in T + JuMP.add_to_expression!(node_input.exp.injection[t], model[:var_input][t], -1.0) + JuMP.add_to_expression!(node_output.exp.injection[t], node_output.exp.generation[t], 1.0) + end + + return true +end + +end \ No newline at end of file From 5e3645f8a390be5bcdf4367e9c1a2c492dd64935 Mon Sep 17 00:00:00 2001 From: Petra Reisz Date: Mon, 5 Jan 2026 11:30:49 +0100 Subject: [PATCH 2/2] feat: model solved for piecewise linear conversion --- .../61_piecewise_linear_addon.iesopt.yaml | 41 +++++++++++++++++ assets/examples/files/61/CustomMath.jl | 44 ++++++++++++------- assets/examples/files/61/efficiency_plot.jl | 34 ++++++++++++++ 3 files changed, 103 insertions(+), 16 deletions(-) create mode 100644 assets/examples/61_piecewise_linear_addon.iesopt.yaml create mode 100644 assets/examples/files/61/efficiency_plot.jl diff --git a/assets/examples/61_piecewise_linear_addon.iesopt.yaml b/assets/examples/61_piecewise_linear_addon.iesopt.yaml new file mode 100644 index 0000000..d9e26da --- /dev/null +++ b/assets/examples/61_piecewise_linear_addon.iesopt.yaml @@ -0,0 +1,41 @@ +config: + general: + version: + core: 2.6.4 + optimization: + problem_type: MILP + snapshots: + count: 9 + solver: + name: highs + paths: + addons: files/61 + +addons: + CustomMath: {} + +carriers: + electricity: {} + gas: {} + +components: + grid_electricity: + type: Node + carrier: electricity + + grid_gas: + type: Node + carrier: gas + + demand_elec: + type: Profile + carrier: electricity + node_from: grid_electricity + value: [2.75, 5.50, 7.00, 8.00, 9.00, 10.00, 5.00, 5.00, 15.00] + + create_gas: + type: Profile + carrier: gas + node_to: grid_gas + mode: create + cost: 50.0 \ No newline at end of file diff --git a/assets/examples/files/61/CustomMath.jl b/assets/examples/files/61/CustomMath.jl index 497acf2..8fba931 100644 --- a/assets/examples/files/61/CustomMath.jl +++ b/assets/examples/files/61/CustomMath.jl @@ -23,26 +23,38 @@ function construct_variables!(model::JuMP.Model, config::Dict) node_input = get_component(model, "grid_gas") node_output = get_component(model, "grid_electricity") - # we create here the breakpoints of the inputs - # We did not set any limit on the upper bound, let's take 20 as end of BPs - input_bp = 0.0:2:20.0 - eta(x, Pmax) = (x/Pmax)^1.5 + 0.1 - ouptput_bp = eta.(input_bp, 25.0) + # we create here the breakpoints of the inputs let's take 20 BPs + Pmax = 20. + a,b,c = [0.7012, 0.0662, 0.3671 ] # coeffs for efficiency + + function eta(P::Float64) + prel = P/Pmax + return 0.1 + c *( prel^a / (prel^a + b^a) ) + end + function power_input(P::Float64) + return P/eta(P) + end + + output_bp = collect(range(0.0, Pmax, length=10)) # electricity out + input_bp = power_input.(output_bp) # gas in N = length(input_bp) - @assert N == length(ouptput_bp) + ## ====================== - JuMP.@variable(model, 0<=λ[1:N]<=1) + @assert N == length(output_bp) JuMP.@variable(model, var_input[t in T], lower_bound=0.0) - node_output.exp.generation = 1.1 .* JuMP.@variable(model, var_output[t in T], lower_bound=0.0, container=Array) - - JuMP.@constraints( - model, - var_input[t] == - [t in T], - # NOTE: This could be a more complex mathematical expression, e.g., a piecewise interpolation. - node_output.exp.generation[t] == 0.5 * model[:var_input][t] - ) + JuMP.@variable(model, var_output[t in T] >= 0.0, container=Array) + + node_output.exp.generation = [1.0 * var_output[t] for t in T] ## IESopt exp fields want AffExpr / Vector{AffExpr}, so make it explicitly affine + + JuMP.@variable(model, 0<= λ[t in T, i in 1:N] <= 1.0) # SOS2 weights PER snapshot + + JuMP.@constraints( model, begin + [t in T], var_input[t] == sum(λ[t,i] * input_bp[i] for i in 1:N) + [t in T], var_output[t] == sum(λ[t,i] * output_bp[i] for i in 1:N) + [t in T], sum(λ[t,i] for i in 1:N) == 1 + end) + JuMP.@constraint( model,[t in T], λ[t, 1:N] in JuMP.SOS2() ) for t in T JuMP.add_to_expression!(node_input.exp.injection[t], model[:var_input][t], -1.0) diff --git a/assets/examples/files/61/efficiency_plot.jl b/assets/examples/files/61/efficiency_plot.jl new file mode 100644 index 0000000..f7c1d96 --- /dev/null +++ b/assets/examples/files/61/efficiency_plot.jl @@ -0,0 +1,34 @@ +using Plots +import JuMP +Pmax = 100. +a,b,c = [0.7012, 0.0662, 0.3671 ] # coeffs for efficiency + +function eta(P::Float64) + prel = P/Pmax + return 0.1 + c *( prel^a / (prel^a + b^a) ) +end + +function eta_const(P::Float64) + return 0.35 +end +P = collect( range(0, Pmax, 110) ) +eta_P = eta.(P) + +p1 = plot(P, eta_P, label="True efficiency") + +input_bp = collect( range(0, Pmax, 20) ) #20 breakpoints +ouptput_bp = eta.(input_bp) + + +plot!(p1, input_bp, ouptput_bp, label="piecewise linear, 20 BPs") +plot!(p1, input_bp, eta_const.(input_bp), label="constant efficiency") + +input_bp = collect( range(0, Pmax, 10) ) #20 breakpoints +ouptput_bp = eta.(input_bp) + +plot!(p1, input_bp, ouptput_bp, label="piecewise linear, 10 BPs") + +input_bp = [0, 5, 10, 15.0, 20, 25, 45, 60, 80, 100] +ouptput_bp = eta.(input_bp) + +plot!(p1, input_bp, ouptput_bp, label="piecewise linear, 10 BPs - not equally spaced") \ No newline at end of file