• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

GenXProject / GenX / #371

14 Sep 2023 02:22AM UTC coverage: 0.271%. First build
#371

Pull #546

travis-ci

Pull Request #546: Thermal+storage (fusion, maintenance)

630 of 630 new or added lines in 9 files covered. (100.0%)

12 of 4424 relevant lines covered (0.27%)

0.27 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/model/generate_model.jl
1
@doc raw"""
2
        generate_model(setup::Dict,inputs::Dict,OPTIMIZER::MOI.OptimizerWithAttributes,modeloutput = nothing)
3

4
This function sets up and solves a constrained optimization model of electricity system capacity expansion and operation problem and extracts solution variables for later processing.
5

6
In addition to calling a number of other modules to create constraints for specific resources, policies, and transmission assets, this function initializes two key expressions that are successively expanded in each of the resource-specific modules: (1) the objective function; and (2) the zonal power balance expression. These two expressions are the only expressions which link together individual modules (e.g. resources, transmission assets, policies), which otherwise are self-contained in defining relevant variables, expressions, and constraints.
7

8
**Objective Function**
9

10
The objective function of GenX minimizes total annual electricity system costs over the following six components shown in the equation below:
11

12
```math
13
\begin{aligned}
14
        &\sum_{y \in \mathcal{G} } \sum_{z \in \mathcal{Z}}
15
        \left( (\pi^{INVEST}_{y,z} \times \overline{\Omega}^{size}_{y,z} \times  \Omega_{y,z})
16
        + (\pi^{FOM}_{y,z} \times \overline{\Omega}^{size}_{y,z} \times  \Delta^{total}_{y,z})\right) + \notag \\
17
        &\sum_{y \in \mathcal{O} } \sum_{z \in \mathcal{Z}}
18
        \left( (\pi^{INVEST,energy}_{y,z} \times    \Omega^{energy}_{y,z})
19
        + (\pi^{FOM,energy}_{y,z} \times  \Delta^{total,energy}_{y,z})\right) + \notag \\
20
        &\sum_{y \in \mathcal{O}^{asym} } \sum_{z \in \mathcal{Z}}
21
        \left( (\pi^{INVEST,charge}_{y,z} \times    \Omega^{charge}_{y,z})
22
        + (\pi^{FOM,charge}_{y,z} \times  \Delta^{total,charge}_{y,z})\right) + \notag \\
23
        & \sum_{y \in \mathcal{G} } \sum_{z \in \mathcal{Z}} \sum_{t \in \mathcal{T}} \left( \omega_{t}\times(\pi^{VOM}_{y,z} + \pi^{FUEL}_{y,z})\times \Theta_{y,z,t}\right) + \sum_{y \in \mathcal{O \cup DF} } \sum_{z \in \mathcal{Z}} \sum_{t \in \mathcal{T}} \left( \omega_{t}\times\pi^{VOM,charge}_{y,z} \times \Pi_{y,z,t}\right) +\notag \\
24
        &\sum_{s \in \mathcal{S} } \sum_{z \in \mathcal{Z}} \sum_{t \in \mathcal{T}}\left(\omega_{t} \times n_{s}^{slope} \times \Lambda_{s,z,t}\right) + \sum_{t \in \mathcal{T} } \left(\omega_{t} \times \pi^{unmet}_{rsv} \times r^{unmet}_{t}\right) \notag \\
25
        &\sum_{y \in \mathcal{H} } \sum_{z \in \mathcal{Z}} \sum_{t \in \mathcal{T}}\left(\omega_{t} \times \pi^{START}_{y,z} \times \chi_{s,z,t}\right) + \notag \\
26
        & \sum_{l \in \mathcal{L}}\left(\pi^{TCAP}_{l} \times \bigtriangleup\varphi^{max}_{l}\right)
27
\end{aligned}
28
```
29

30
The first summation represents the fixed costs of generation/discharge over all zones and technologies, which refects the sum of the annualized capital cost, $\pi^{INVEST}_{y,z}$, times the total new capacity added (if any),  plus the Fixed O&M cost, $\pi^{FOM}_{y,z}$, times the net installed generation capacity, $\overline{\Omega}^{size}_{y,z} \times \Delta^{total}_{y,z}$ (e.g., existing capacity less retirements plus additions).
31

32
The second summation corresponds to the fixed cost of installed energy storage capacity and is summed over only the storage resources. This term includes the sum of the annualized energy capital cost, $\pi^{INVEST,energy}_{y,z}$, times the total new energy capacity added (if any), plus the Fixed O&M cost, $\pi^{FOM, energy}_{y,z}$, times the net installed energy storage capacity, $\Delta^{total}_{y,z}$ (e.g., existing capacity less retirements plus additions).
33

34
The third summation corresponds to the fixed cost of installed charging power capacity and is summed over only over storage resources with independent/asymmetric charge and discharge power components ($\mathcal{O}^{asym}$). This term includes the sum of the annualized charging power capital cost, $\pi^{INVEST,charge}_{y,z}$, times the total new charging power capacity added (if any), plus the Fixed O&M cost, $\pi^{FOM, energy}_{y,z}$, times the net installed charging power capacity, $\Delta^{total}_{y,z}$ (e.g., existing capacity less retirements plus additions).
35

36
The fourth and fifth summations corresponds to the operational cost across all zones, technologies, and time steps. The fourth summation represents the sum of fuel cost, $\pi^{FUEL}_{y,z}$ (if any), plus variable O&M cost, $\pi^{VOM}_{y,z}$ times the energy generation/discharge by generation or storage resources (or demand satisfied via flexible demand resources, $y\in\mathcal{DF}$) in time step $t$, $\Theta_{y,z,t}$, and the weight of each time step $t$, $\omega_t$, where $\omega_t$ is equal to 1 when modeling grid operations over the entire year (8760 hours), but otherwise is equal to the number of hours in the year represented by the representative time step, $t$ such that the sum of $\omega_t \forall t \in T = 8760$, approximating annual operating costs. The fifth summation represents the variable charging O&M cost, $\pi^{VOM,charge}_{y,z}$ times the energy withdrawn for charging by storage resources (or demand deferred by flexible demand resources) in time step $t$ , $\Pi_{y,z,t}$ and the annual weight of time step $t$,$\omega_t$.
37

38
The sixth summation represents the total cost of unserved demand across all segments $s$ of a segment-wise price-elastic demand curve, equal to the marginal value of consumption (or cost of non-served energy), $n_{s}^{slope}$, times the
39
amount of non-served energy, $\Lambda_{y,z,t}$, for each segment on each zone during each time step (weighted by $\omega_t$).
40

41
The seventh summation represents the total cost of not meeting hourly operating reserve requirements, where $\pi^{unmet}_{rsv}$ is the cost penalty per unit of non-served reserve requirement, and $r^{unmet}_t$ is the amount of non-served reserve requirement in each time step (weighted by $\omega_t$).
42

43
The eighth summation corresponds to the startup costs incurred by technologies to which unit commitment decisions apply (e.g. $y \in \mathcal{UC}$), equal to the cost of start-up, $\pi^{START}_{y,z}$, times the number of startup events, $\chi_{y,z,t}$, for the cluster of units in each zone and time step (weighted by $\omega_t$).
44

45
The last term corresponds to the transmission reinforcement or construction costs, for each transmission line in the model. Transmission reinforcement costs are equal to the sum across all lines of the product between the transmission reinforcement/construction cost, $\pi^{TCAP}_{l}$, times the additional transmission capacity variable, $\bigtriangleup\varphi^{max}_{l}$. Note that fixed O\&M and replacement capital costs (depreciation) for existing transmission capacity is treated as a sunk cost and not included explicitly in the GenX objective function.
46

47
In summary, the objective function can be understood as the minimization of costs associated with five sets of different decisions: (1) where and how to invest on capacity, (2) how to dispatch or operate that capacity, (3) which consumer demand segments to serve or curtail, (4) how to cycle and commit thermal units subject to unit commitment decisions, (5) and where and how to invest in additional transmission network capacity to increase power transfer capacity between zones. Note however that each of these components are considered jointly and the optimization is performed over the whole problem at once as a monolithic co-optimization problem.
48

49
**Power Balance**
50

51
The power balance constraint of the model ensures that electricity demand is met at every time step in each zone. As shown in the constraint, electricity demand, $D_{t,z}$, at each time step and for each zone must be strictly equal to the sum of generation, $\Theta_{y,z,t}$, from thermal technologies ($\mathcal{H}$), curtailable VRE ($\mathcal{VRE}$), must-run resources ($\mathcal{MR}$), and hydro resources ($\mathcal{W}$). At the same time, energy storage devices ($\mathcal{O}$) can discharge energy, $\Theta_{y,z,t}$ to help satisfy demand, while when these devices are charging, $\Pi_{y,z,t}$, they increase demand. For the case of flexible demand resources ($\mathcal{DF}$), delaying demand (equivalent to charging virtual storage), $\Pi_{y,z,t}$, decreases demand while satisfying delayed demand (equivalent to discharging virtual demand), $\Theta_{y,z,t}$, increases demand. Price-responsive demand curtailment, $\Lambda_{s,z,t}$, also reduces demand. Finally, power flows, $\Phi_{l,t}$, on each line $l$ into or out of a zone (defined by the network map $\varphi^{map}_{l,z}$), are considered in the demand balance equation for each zone. By definition, power flows leaving their reference zone are positive, thus the minus sign in the below constraint. At the same time losses due to power flows increase demand, and one-half of losses across a line linking two zones are attributed to each connected zone. The losses function $\beta_{l,t}(\cdot)$ will depend on the configuration used to model losses (see Transmission section).
52

53
```math
54
\begin{aligned}
55
        & \sum_{y\in \mathcal{H}}{\Theta_{y,z,t}} +\sum_{y\in \mathcal{VRE}}{\Theta_{y,z,t}} +\sum_{y\in \mathcal{MR}}{\Theta_{y,z,t}} + \sum_{y\in \mathcal{O}}{(\Theta_{y,z,t}-\Pi_{y,z,t})} + \notag\\
56
        & \sum_{y\in \mathcal{DF}}{(-\Theta_{y,z,t}+\Pi_{y,z,t})} +\sum_{y\in \mathcal{W}}{\Theta_{y,z,t}}+ \notag\\
57
        &+ \sum_{s\in \mathcal{S}}{\Lambda_{s,z,t}}  - \sum_{l\in \mathcal{L}}{(\varphi^{map}_{l,z} \times \Phi_{l,t})} -\frac{1}{2} \sum_{l\in \mathcal{L}}{(\varphi^{map}_{l,z} \times \beta_{l,t}(\cdot))} = D_{z,t}
58
        \forall z\in \mathcal{Z},  t \in \mathcal{T}
59
\end{aligned}
60
```
61

62
# Arguments
63
- `setup::Dict`: Dictionary containing the settings for the model.
64
- `inputs::Dict`: Dictionary containing the inputs for the model.
65
- `OPTIMIZER::MOI.OptimizerWithAttributes`: The optimizer to use for solving the model.
66

67
# Returns
68
- `Model`: The model object containing the entire optimization problem model to be solved by solve_model.jl
69
"""
70
function generate_model(setup::Dict, inputs::Dict, OPTIMIZER::MOI.OptimizerWithAttributes)
71
    T = inputs["T"]     # Number of time steps (hours)
72
    Z = inputs["Z"]     # Number of zones
73

74
    ## Start pre-solve timer
×
75
    presolver_start_time = time()
76

×
77
    # Generate Energy Portfolio (EP) Model
×
78
    EP = if Bool(setup["EnableJuMPDirectModel"])
79
        opt_instance = MOI.instantiate(OPTIMIZER)
80
        direct_model(opt_instance)
×
81
    else
82
        Model(OPTIMIZER)
83
    end
×
84
    set_string_names_on_creation(EP, Bool(setup["EnableJuMPStringNames"]))
×
85

86
    # Initialize Power Balance Expression
87
    # Expression for "baseline" power balance constraint
×
88
    create_empty_expression!(EP, :ePowerBalance, (T, Z))
89

90
    # Initialize Objective Function Expression
91
    EP[:eObj] = AffExpr(0.0)
×
92

93
    create_empty_expression!(EP, :eGenerationByZone, (Z, T))
94

×
95
    # Energy losses related to technologies
96
    create_empty_expression!(EP, :eELOSSByZone, Z)
97

98
    # Initialize Capacity Reserve Margin Expression
×
99
    if setup["CapacityReserveMargin"] > 0
100
        create_empty_expression!(EP,
×
101
            :eCapResMarBalance,
×
102
            (inputs["NCapacityReserveMargin"], T))
103
    end
104

105
    # Energy Share Requirement
×
106
    if setup["EnergyShareRequirement"] >= 1
×
107
        create_empty_expression!(EP, :eESR, inputs["nESR"])
108
    end
109

×
110
    # Hourly Matching Requirement
×
111
    if setup["HourlyMatching"] == 1
112
        create_empty_expression!(EP, :eHM, (T, Z))
113
    end
×
114

×
115
    if setup["MinCapReq"] == 1
116
        create_empty_expression!(EP, :eMinCapRes, inputs["NumberOfMinCapReqs"])
117
    end
118

×
119
    if setup["MaxCapReq"] == 1
120
        create_empty_expression!(EP, :eMaxCapRes, inputs["NumberOfMaxCapReqs"])
×
121
    end
122

×
123
    if setup["HydrogenMinimumProduction"] > 0
124
        create_empty_expression!(EP, :eH2DemandRes, inputs["NumberOfH2DemandReqs"])
×
125
    end
×
126

127
    # Infrastructure
128
    discharge!(EP, inputs, setup)
×
129

130
    non_served_energy!(EP, inputs, setup)
×
131

×
132
    investment_discharge!(EP, inputs, setup)
133

134
    if setup["UCommit"] > 0
×
135
        ucommit!(EP, inputs, setup)
×
136
    end
137

138
    fuel!(EP, inputs, setup)
139

140
    co2!(EP, inputs)
141

×
142
    if setup["OperationalReserves"] > 0
×
143
        operational_reserves!(EP, inputs, setup)
144
    end
145

146
    if Z > 1
×
147
        investment_transmission!(EP, inputs, setup)
×
148
        transmission!(EP, inputs, setup)
149
    end
150

151
    if Z > 1 && setup["DC_OPF"] != 0
×
152
        dcopf_transmission!(EP, inputs, setup)
×
153
    end
154

155
    # Technologies
156
    # Model constraints, variables, expression related to dispatchable renewable resources
×
157

×
158
    if !isempty(inputs["VRE"])
159
        curtailable_variable_renewable!(EP, inputs, setup)
160
    end
161

×
162
    # Model constraints, variables, expression related to non-dispatchable renewable resources
×
163
    if !isempty(inputs["MUST_RUN"])
164
        must_run!(EP, inputs, setup)
165
    end
166

×
167
    # Model constraints, variables, expression related to energy storage modeling
×
168
    if !isempty(inputs["STOR_ALL"])
169
        storage!(EP, inputs, setup)
170
    end
×
171

×
172
    # Model constraints, variables, expression related to reservoir hydropower resources
173
    if !isempty(inputs["HYDRO_RES"])
174
        hydro_res!(EP, inputs, setup)
175
    end
×
176

×
177
    # Allam Cycle LOX
178
    if !isempty(inputs["ALLAM_CYCLE_LOX"])
179
        allamcyclelox!(EP, inputs, setup)
180
    end
×
181

×
182
    # Model constraints, variables, expression related to reservoir hydropower resources with long duration storage
183
    if inputs["REP_PERIOD"] > 1 && !isempty(inputs["STOR_HYDRO_LONG_DURATION"])
184
        hydro_inter_period_linkage!(EP, inputs, setup)
185
    end
186

×
187
    # Model constraints, variables, expression related to demand flexibility resources
×
188
    if !isempty(inputs["FLEX"])
189
        flexible_demand!(EP, inputs, setup)
190
    end
191

×
192
    # Model constraints, variables, expression related to thermal resource technologies
×
193
    if !isempty(inputs["THERM_ALL"])
194
        thermal!(EP, inputs, setup)
195
    end
196

×
197
    # Model constraints, variables, expression related to retrofit technologies
×
198
    if !isempty(inputs["RETROFIT_OPTIONS"])
199
        EP = retrofit(EP, inputs)
200
    end
201

×
202
    # Model constraints, variables, expressions related to the co-located VRE-storage resources
×
203
    if !isempty(inputs["VRE_STOR"])
204
        vre_stor!(EP, inputs, setup)
205
    end
×
206

×
207
    # Model constraints, variables, expressions related to telectrolyzers
208
    if !isempty(inputs["ELECTROLYZER"]) ||
209
       (!isempty(inputs["VRE_STOR"]) && !isempty(inputs["VS_ELEC"]))
×
210
        electrolyzer!(EP, inputs, setup)
×
211
    end
212
    # Policies
213

214
    if setup["OperationalReserves"] > 0
×
215
        operational_reserves_constraints!(EP, inputs)
216
    end
217

218
    # CO2 emissions limits
219
    if setup["CO2Cap"] > 0
×
220
        co2_cap!(EP, inputs, setup)
221
    end
222

×
223
    # Endogenous Retirements
×
224
    if setup["MultiStage"] > 0
×
225
        endogenous_retirement!(EP, inputs, setup)
×
226
    end
×
227

228
    # Energy Share Requirement
229
    if setup["EnergyShareRequirement"] >= 1
×
230
        energy_share_requirement!(EP, inputs, setup)
231
    end
232

233
    # Energy Share Requirement
234
    if setup["HourlyMatching"] == 1
235
        hourly_matching!(EP, inputs)
236
    end
237

238
    #Capacity Reserve Margin
239
    if setup["CapacityReserveMargin"] > 0
240
        cap_reserve_margin!(EP, inputs, setup)
241
    end
242

243
    if (setup["MinCapReq"] == 1)
244
        minimum_capacity_requirement!(EP, inputs, setup)
245
    end
246

247
    if setup["MaxCapReq"] == 1
248
        maximum_capacity_requirement!(EP, inputs, setup)
249
    end
250

251
    # Hydrogen demand limits
252
    if setup["HydrogenMinimumProduction"] > 0
253
        hydrogen_demand!(EP, inputs, setup)
254
    end
255

256
    if setup["ModelingToGenerateAlternatives"] == 1
257
        mga!(EP, inputs, setup)
258
    end
259

260
    ## Define the objective function
261
    @objective(EP, Min, setup["ObjScale"]*EP[:eObj])
262

263
    ## Power balance constraints
264
    # demand = generation + storage discharge - storage charge - demand deferral + deferred demand satisfaction - demand curtailment (NSE)
265
    #          + incoming power flows - outgoing power flows - flow losses - charge of heat storage + generation from NACC
266
    @constraint(EP,
267
        cPowerBalance[t = 1:T, z = 1:Z],
268
        EP[:ePowerBalance][t, z]==inputs["pD"][t, z])
269

270
    ## Record pre-solver time
271
    presolver_time = time() - presolver_start_time
272
    if setup["PrintModel"] == 1
273
        filepath = joinpath(pwd(), "YourModel.lp")
274
        JuMP.write_to_file(EP, filepath)
275
        println("Model Printed")
276
    end
277

278
    return EP
279
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc