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

blue-marble / gridpath / 16356053796

17 Jul 2025 09:09PM UTC coverage: 88.961%. Remained the same
16356053796

push

github

anamileva
Curtailment cost inputs for subproblem's periods only

27343 of 30736 relevant lines covered (88.96%)

2.67 hits per line

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

94.87
/gridpath/project/operations/operational_types/dispatchable_load.py
1
# Copyright 2016-2025 Blue Marble Analytics LLC.
2
#
3
# Licensed under the Apache License, Version 2.0 (the "License");
4
# you may not use this file except in compliance with the License.
5
# You may obtain a copy of the License at
6
#
7
#     http://www.apache.org/licenses/LICENSE-2.0
8
#
9
# Unless required by applicable law or agreed to in writing, software
10
# distributed under the License is distributed on an "AS IS" BASIS,
11
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
# See the License for the specific language governing permissions and
13
# limitations under the License.
14

15
"""
16
This operational type describes dispatchable loads. Their "power output" is
17
subtracted from the power-production side of the load-balance constraints.
18

19
Projects of this type may have a heat rate associated with them, for example
20
to model direct air capture facilities that consume power in order to capture
21
carbon from the atmosphere. Note that this should be modeled to burn
22
a fuel with a negative emissions intensity and they do require a simple heat
23
rate. Also note that projects of this type must be assigned a carbon cap zone
24
in order to contribute net negative emissions to the carbon constraint.
25

26
Project of this type may have a consumption ratio associated with them, for example
27
synchornous condenser consumes 1%-3% of their nameplate capacity in order to operate
28
and produce inertia.
29
"""
30

31
from pyomo.environ import (
3✔
32
    Set,
33
    Param,
34
    Var,
35
    Constraint,
36
    NonNegativeReals,
37
    PercentFraction,
38
)
39

40
from gridpath.auxiliary.auxiliary import (
3✔
41
    subset_init_by_param_value,
42
    cursor_to_df,
43
    subset_init_by_set_membership,
44
)
45
from gridpath.auxiliary.validations import (
3✔
46
    write_validation_to_database,
47
    validate_single_input,
48
)
49
from gridpath.project.common_functions import (
3✔
50
    check_if_first_timepoint,
51
    check_boundary_type,
52
)
53
from gridpath.project.operations.operational_types.common_functions import (
3✔
54
    load_optype_model_data,
55
    validate_opchars,
56
)
57

58

59
def add_model_components(
3✔
60
    m,
61
    d,
62
    scenario_directory,
63
    weather_iteration,
64
    hydro_iteration,
65
    availability_iteration,
66
    subproblem,
67
    stage,
68
):
69
    """
70
    The following Pyomo model components are defined in this module:
71

72
    +-------------------------------------------------------------------------+
73
    | Sets                                                                    |
74
    +=========================================================================+
75
    | | :code:`DISPATCHABLE_LOAD_PRJS`                                        |
76
    |                                                                         |
77
    | The set of generators of the :code:`dispatchable_load` operational type.|
78
    +-------------------------------------------------------------------------+
79
    | | :code:`DISPATCHABLE_LOAD_PRJS_OPR_TMPS`                               |
80
    |                                                                         |
81
    | Two-dimensional set with projects of the :code:`dispatchable_load`      |
82
    | operational type and their operational timepoints.                      |
83
    +-------------------------------------------------------------------------+
84

85
    |
86

87
    +-------------------------------------------------------------------------+
88
    | Linked Input Params                                                     |
89
    +=========================================================================+
90
    | | :code:`dispatchable_load_energy_requirement_factor_param`             |
91
    | | *Defined over*: :code:`DISPATCHABLE_LOAD_PRJS`                        |
92
    | | *Within*: :code:`PercentFraction`                                     |
93
    |                                                                         |
94
    | The project's power provision in the linked timepoints.                 |
95
    +-------------------------------------------------------------------------+
96

97
    |
98

99
    +-------------------------------------------------------------------------+
100
    | Variables                                                               |
101
    +=========================================================================+
102
    | | :code:`DispatchableLoadPrj_Consume_Power_MW`                          |
103
    | | *Defined over*: :code:`DISPATCHABLE_LOAD_PRJS_OPR_TMPS`               |
104
    | | *Within*: :code:`NonNegativeReals`                                    |
105
    |                                                                         |
106
    | Power consumption in MW from this project in each timepoint in which    |
107
    | the project is operational (capacity exists and the project is          |
108
    | available).                                                             |
109
    +-------------------------------------------------------------------------+
110

111
    |
112

113
    +-------------------------------------------------------------------------+
114
    | Constraints                                                             |
115
    +=========================================================================+
116
    | Power                                                                   |
117
    +-------------------------------------------------------------------------+
118
    | | :code:`DispatchableLoadPrj_Max_Power_Constraint`                      |
119
    | | *Defined over*: :code:`DISPATCHABLE_LOAD_PRJS_OPR_TMPS`               |
120
    |                                                                         |
121
    | Limits the power consumption to the available capacity.                 |
122
    +-------------------------------------------------------------------------+
123

124
    """
125

126
    # Sets
127
    ###########################################################################
128

129
    m.DISPATCHABLE_LOAD_PRJS = Set(
3✔
130
        within=m.PROJECTS,
131
        initialize=lambda mod: subset_init_by_param_value(
132
            mod, "PROJECTS", "operational_type", "dispatchable_load"
133
        ),
134
    )
135

136
    m.DISPATCHABLE_LOAD_PRJS_OPR_TMPS = Set(
3✔
137
        dimen=2,
138
        within=m.PRJ_OPR_TMPS,
139
        initialize=lambda mod: subset_init_by_set_membership(
140
            mod=mod,
141
            superset="PRJ_OPR_TMPS",
142
            index=0,
143
            membership_set=mod.DISPATCHABLE_LOAD_PRJS,
144
        ),
145
    )
146
    # Param
147
    ###########################################################################
148
    # dispatchable_load_energy_requirement_derate_param is used when a project
149
    # activates his capacity by using only a fraction of the power.
150
    # For example, when a synchronous condenser operates it consumes 1% to 3%
151
    # of its nameplate capacity so while the online capacity is 100%, it power
152
    # consumption is 1%
153

154
    m.dispatchable_load_energy_requirement_factor = Param(
3✔
155
        m.DISPATCHABLE_LOAD_PRJS, within=PercentFraction, default=1
156
    )
157

158
    # Variables
159
    ###########################################################################
160

161
    m.DispatchableLoadPrj_Consume_Power_MW = Var(
3✔
162
        m.DISPATCHABLE_LOAD_PRJS_OPR_TMPS, within=NonNegativeReals
163
    )
164

165
    # Constraints
166
    ###########################################################################
167

168
    m.DispatchableLoadPrj_Max_Power_Constraint = Constraint(
3✔
169
        m.DISPATCHABLE_LOAD_PRJS_OPR_TMPS, rule=max_power_rule
170
    )
171

172

173
# Constraint Formulation Rules
174
###############################################################################
175

176

177
# Power
178
def max_power_rule(mod, g, tmp):
3✔
179
    """
180
    **Constraint Name**: DispatchableLoadPrj_Max_Power_Constraint
181
    **Enforced Over**: DISPATCHABLE_LOAD_PRJS_OPR_TMPS
182

183
    Power consumption cannot exceed capacity.
184
    """
185
    return mod.DispatchableLoadPrj_Consume_Power_MW[g, tmp] <= (
3✔
186
        mod.Capacity_MW[g, mod.period[tmp]]
187
        * mod.Availability_Derate[g, tmp]
188
        * mod.dispatchable_load_energy_requirement_factor[g]
189
    )
190

191

192
# Operational Type Methods
193
###############################################################################
194

195

196
def power_provision_rule(mod, prj, tmp):
3✔
197
    """
198
    Power provision from DISPATCHABLE_LOAD_PRJS is the negative of the power
199
    consumption.
200
    """
201
    return -mod.DispatchableLoadPrj_Consume_Power_MW[prj, tmp]
3✔
202

203

204
def variable_om_cost_rule(mod, prj, tmp):
3✔
205
    """
206
    Must be defined rather than take the default, as Project_Power_Provision_MW
207
    for this operational type is negative downstream.
208
    """
209
    return (
3✔
210
        mod.DispatchableLoadPrj_Consume_Power_MW[prj, tmp]
211
        * mod.variable_om_cost_per_mwh[prj]
212
    )
213

214

215
def variable_om_by_period_cost_rule(mod, prj, tmp):
3✔
216
    """
217
    Must be defined rather than take the default, as Project_Power_Provision_MW
218
    for this operational type is negative downstream.
219
    """
220
    return (
×
221
        mod.DispatchableLoadPrj_Consume_Power_MW[prj, tmp]
222
        * mod.variable_om_cost_per_mwh_by_period[prj, mod.period[tmp]]
223
    )
224

225

226
def variable_om_by_timepoint_cost_rule(mod, prj, tmp):
3✔
227
    """
228
    Must be defined rather than take the default, as Project_Power_Provision_MW
229
    for this operational type is negative downstream.
230
    """
231
    return (
3✔
232
        mod.DispatchableLoadPrj_Consume_Power_MW[prj, tmp]
233
        * mod.variable_om_cost_per_mwh_by_timepoint[prj, tmp]
234
    )
235

236

237
def fuel_burn_rule(mod, prj, tmp):
3✔
238
    """
239
    Fuel burn is the product of the fuel burn slope and the power output. The
240
    project fuel burn is later multiplied by the fuel emissions intensity to get the
241
    total captured emissions, so fuel_burn_slope x emissions_intensity should equal
242
    the amount of emissions captured per unit of consumed power.
243
    """
244
    if prj in mod.FUEL_PRJS:
3✔
245
        return (
3✔
246
            mod.fuel_burn_slope_mmbtu_per_mwh[prj, mod.period[tmp], 0]
247
            * mod.DispatchableLoadPrj_Consume_Power_MW[prj, tmp]
248
        )
249
    else:
250
        return 0
×
251

252

253
def power_delta_rule(mod, g, tmp):
3✔
254
    """
255
    This rule is only used in tuning costs, so fine to skip for linked
256
    horizon's first timepoint.
257
    """
258
    if check_if_first_timepoint(
3✔
259
        mod=mod, tmp=tmp, balancing_type=mod.balancing_type_project[g]
260
    ) and (
261
        check_boundary_type(
262
            mod=mod,
263
            tmp=tmp,
264
            balancing_type=mod.balancing_type_project[g],
265
            boundary_type="linear",
266
        )
267
        or check_boundary_type(
268
            mod=mod,
269
            tmp=tmp,
270
            balancing_type=mod.balancing_type_project[g],
271
            boundary_type="linked",
272
        )
273
    ):
274
        pass
2✔
275
    else:
276
        return (
3✔
277
            mod.DispatchableLoadPrj_Consume_Power_MW[g, tmp]
278
            - mod.DispatchableLoadPrj_Consume_Power_MW[
279
                g, mod.prev_tmp[tmp, mod.balancing_type_project[g]]
280
            ]
281
        )
282

283

284
def capacity_providing_inertia_rule(mod, g, tmp):
3✔
285
    """
286
    Capacity providing inertia for DISPATCHABLE_LOAD_PRJS is assume to be
287
    proportional to the power output
288
    """
289
    return (
3✔
290
        mod.DispatchableLoadPrj_Consume_Power_MW[g, tmp]
291
        / mod.dispatchable_load_energy_requirement_factor[g]
292
    )
293

294

295
# Input-Output
296
###############################################################################
297

298

299
def load_model_data(
3✔
300
    mod,
301
    d,
302
    data_portal,
303
    scenario_directory,
304
    weather_iteration,
305
    hydro_iteration,
306
    availability_iteration,
307
    subproblem,
308
    stage,
309
):
310
    """
311

312
    :param mod:
313
    :param data_portal:
314
    :param scenario_directory:
315
    :param subproblem:
316
    :param stage:
317
    :return:
318
    """
319

320
    projects = load_optype_model_data(
3✔
321
        mod=mod,
322
        data_portal=data_portal,
323
        scenario_directory=scenario_directory,
324
        weather_iteration=weather_iteration,
325
        hydro_iteration=hydro_iteration,
326
        availability_iteration=availability_iteration,
327
        subproblem=subproblem,
328
        stage=stage,
329
        op_type="dispatchable_load",
330
    )
331

332

333
# Validation
334
###############################################################################
335

336

337
def validate_inputs(
3✔
338
    scenario_id,
339
    subscenarios,
340
    weather_iteration,
341
    hydro_iteration,
342
    availability_iteration,
343
    subproblem,
344
    stage,
345
    conn,
346
):
347
    """
348
    Get inputs from database and validate the inputs
349
    :param subscenarios: SubScenarios object with all subscenario info
350
    :param subproblem:
351
    :param stage:
352
    :param conn: database connection
353
    :return:
354
    """
355

356
    # Validate operational chars table inputs
357
    validate_opchars(
3✔
358
        scenario_id,
359
        subscenarios,
360
        weather_iteration,
361
        hydro_iteration,
362
        availability_iteration,
363
        subproblem,
364
        stage,
365
        conn,
366
        "dispatchable_load",
367
    )
368

369
    # Other module specific validations
370

371
    c = conn.cursor()
3✔
372
    heat_rates = c.execute(
3✔
373
        """
374
        SELECT project, load_point_fraction
375
        FROM inputs_project_portfolios
376
        INNER JOIN
377
        (SELECT project, operational_type, heat_rate_curves_scenario_id
378
        FROM inputs_project_operational_chars
379
        WHERE project_operational_chars_scenario_id = {}
380
        AND operational_type = '{}') AS op_char
381
        USING(project)
382
        INNER JOIN
383
        (SELECT project, heat_rate_curves_scenario_id, load_point_fraction
384
        FROM inputs_project_heat_rate_curves) as heat_rates
385
        USING(project, heat_rate_curves_scenario_id)
386
        WHERE project_portfolio_scenario_id = {}
387
        """.format(
388
            subscenarios.PROJECT_OPERATIONAL_CHARS_SCENARIO_ID,
389
            "dispatchable_load",
390
            subscenarios.PROJECT_PORTFOLIO_SCENARIO_ID,
391
        )
392
    )
393

394
    # Convert inputs to dataframe
395
    hr_df = cursor_to_df(heat_rates)
3✔
396

397
    # Check that there is only one load point (constant heat rate)
398
    write_validation_to_database(
3✔
399
        conn=conn,
400
        scenario_id=scenario_id,
401
        weather_iteration=weather_iteration,
402
        hydro_iteration=hydro_iteration,
403
        availability_iteration=availability_iteration,
404
        subproblem_id=subproblem,
405
        stage_id=stage,
406
        gridpath_module=__name__,
407
        db_table="inputs_project_heat_rate_curves",
408
        severity="Mid",
409
        errors=validate_single_input(
410
            df=hr_df,
411
            msg="dispatchable_load can only have one load point (constant heat rate).",
412
        ),
413
    )
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