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

blue-marble / gridpath / 15718986151

17 Jun 2025 10:04PM UTC coverage: 89.024% (+0.006%) from 89.018%
15718986151

push

github

web-flow
Storage losses limit (#1264)

18 of 18 new or added lines in 2 files covered. (100.0%)

24 existing lines in 24 files now uncovered.

26774 of 30075 relevant lines covered (89.02%)

2.67 hits per line

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

94.44
/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

27
from pyomo.environ import (
3✔
28
    Set,
29
    Var,
30
    Constraint,
31
    NonNegativeReals,
32
    PercentFraction,
33
)
34

35
from gridpath.auxiliary.auxiliary import (
3✔
36
    subset_init_by_param_value,
37
    cursor_to_df,
38
    subset_init_by_set_membership,
39
)
40
from gridpath.auxiliary.validations import (
3✔
41
    write_validation_to_database,
42
    validate_single_input,
43
)
44
from gridpath.project.common_functions import (
3✔
45
    check_if_first_timepoint,
46
    check_boundary_type,
47
)
48
from gridpath.project.operations.operational_types.common_functions import (
3✔
49
    load_optype_model_data,
50
    validate_opchars,
51
)
52

53

54
def add_model_components(
3✔
55
    m,
56
    d,
57
    scenario_directory,
58
    weather_iteration,
59
    hydro_iteration,
60
    availability_iteration,
61
    subproblem,
62
    stage,
63
):
64
    """
65
    The following Pyomo model components are defined in this module:
66

67
    +-------------------------------------------------------------------------+
68
    | Sets                                                                    |
69
    +=========================================================================+
70
    | | :code:`DISPATCHABLE_LOAD_PRJS`                                        |
71
    |                                                                         |
72
    | The set of generators of the :code:`dispatchable_load` operational type.|
73
    +-------------------------------------------------------------------------+
74
    | | :code:`DISPATCHABLE_LOAD_PRJS_OPR_TMPS`                               |
75
    |                                                                         |
76
    | Two-dimensional set with projects of the :code:`dispatchable_load`      |
77
    | operational type and their operational timepoints.                      |
78
    +-------------------------------------------------------------------------+
79

80
    |
81

82
    +-------------------------------------------------------------------------+
83
    | Variables                                                               |
84
    +=========================================================================+
85
    | | :code:`DispatchableLoadPrj_Consume_Power_MW`                          |
86
    | | *Defined over*: :code:`DISPATCHABLE_LOAD_PRJS_OPR_TMPS`               |
87
    | | *Within*: :code:`NonNegativeReals`                                    |
88
    |                                                                         |
89
    | Power consumption in MW from this project in each timepoint in which    |
90
    | the project is operational (capacity exists and the project is          |
91
    | available).                                                             |
92
    +-------------------------------------------------------------------------+
93

94
    |
95

96
    +-------------------------------------------------------------------------+
97
    | Constraints                                                             |
98
    +=========================================================================+
99
    | Power                                                                   |
100
    +-------------------------------------------------------------------------+
101
    | | :code:`DispatchableLoadPrj_Max_Power_Constraint`                      |
102
    | | *Defined over*: :code:`DISPATCHABLE_LOAD_PRJS_OPR_TMPS`               |
103
    |                                                                         |
104
    | Limits the power consumption to the available capacity.                 |
105
    +-------------------------------------------------------------------------+
106

107
    """
108

109
    # Sets
110
    ###########################################################################
111

112
    m.DISPATCHABLE_LOAD_PRJS = Set(
3✔
113
        within=m.PROJECTS,
114
        initialize=lambda mod: subset_init_by_param_value(
115
            mod, "PROJECTS", "operational_type", "dispatchable_load"
116
        ),
117
    )
118

119
    m.DISPATCHABLE_LOAD_PRJS_OPR_TMPS = Set(
3✔
120
        dimen=2,
121
        within=m.PRJ_OPR_TMPS,
122
        initialize=lambda mod: subset_init_by_set_membership(
123
            mod=mod,
124
            superset="PRJ_OPR_TMPS",
125
            index=0,
126
            membership_set=mod.DISPATCHABLE_LOAD_PRJS,
127
        ),
128
    )
129

130
    # Variables
131
    ###########################################################################
132

133
    m.DispatchableLoadPrj_Consume_Power_MW = Var(
3✔
134
        m.DISPATCHABLE_LOAD_PRJS_OPR_TMPS, within=NonNegativeReals
135
    )
136

137
    # Constraints
138
    ###########################################################################
139

140
    m.DispatchableLoadPrj_Max_Power_Constraint = Constraint(
3✔
141
        m.DISPATCHABLE_LOAD_PRJS_OPR_TMPS, rule=max_power_rule
142
    )
143

144

145
# Constraint Formulation Rules
146
###############################################################################
147

148

149
# Power
150
def max_power_rule(mod, g, tmp):
3✔
151
    """
152
    **Constraint Name**: DispatchableLoadPrj_Max_Power_Constraint
153
    **Enforced Over**: DISPATCHABLE_LOAD_PRJS_OPR_TMPS
154

155
    Power consumption cannot exceed capacity.
156
    """
157
    return (
3✔
158
        mod.DispatchableLoadPrj_Consume_Power_MW[g, tmp]
159
        <= mod.Capacity_MW[g, mod.period[tmp]] * mod.Availability_Derate[g, tmp]
160
    )
161

162

163
# Operational Type Methods
164
###############################################################################
165

166

167
def power_provision_rule(mod, prj, tmp):
3✔
168
    """
169
    Power provision from DISPATCHABLE_LOAD_PRJS is the negative of the power
170
    consumption.
171
    """
172
    return -mod.DispatchableLoadPrj_Consume_Power_MW[prj, tmp]
3✔
173

174

175
def variable_om_cost_rule(mod, prj, tmp):
3✔
176
    """
177
    Must be defined rather than take the default, as Project_Power_Provision_MW
178
    for this operational type is negative downstream.
179
    """
180
    return (
3✔
181
        mod.DispatchableLoadPrj_Consume_Power_MW[prj, tmp]
182
        * mod.variable_om_cost_per_mwh[prj]
183
    )
184

185

186
def variable_om_by_period_cost_rule(mod, prj, tmp):
3✔
187
    """
188
    Must be defined rather than take the default, as Project_Power_Provision_MW
189
    for this operational type is negative downstream.
190
    """
191
    return (
×
192
        mod.DispatchableLoadPrj_Consume_Power_MW[prj, tmp]
193
        * mod.variable_om_cost_per_mwh_by_period[prj, mod.period[tmp]]
194
    )
195

196

197
def variable_om_by_timepoint_cost_rule(mod, prj, tmp):
3✔
198
    """
199
    Must be defined rather than take the default, as Project_Power_Provision_MW
200
    for this operational type is negative downstream.
201
    """
202
    return (
3✔
203
        mod.DispatchableLoadPrj_Consume_Power_MW[prj, tmp]
204
        * mod.variable_om_cost_per_mwh_by_timepoint[prj, tmp]
205
    )
206

207

208
def fuel_burn_rule(mod, prj, tmp):
3✔
209
    """
210
    Fuel burn is the product of the fuel burn slope and the power output. The
211
    project fuel burn is later multiplied by the fuel emissions intensity to get the
212
    total captured emissions, so fuel_burn_slope x emissions_intensity should equal
213
    the amount of emissions captured per unit of consumed power.
214
    """
215
    if prj in mod.FUEL_PRJS:
3✔
216
        return (
3✔
217
            mod.fuel_burn_slope_mmbtu_per_mwh[prj, mod.period[tmp], 0]
218
            * mod.DispatchableLoadPrj_Consume_Power_MW[prj, tmp]
219
        )
220
    else:
221
        return 0
×
222

223

224
def power_delta_rule(mod, g, tmp):
3✔
225
    """
226
    This rule is only used in tuning costs, so fine to skip for linked
227
    horizon's first timepoint.
228
    """
229
    if check_if_first_timepoint(
3✔
230
        mod=mod, tmp=tmp, balancing_type=mod.balancing_type_project[g]
231
    ) and (
232
        check_boundary_type(
233
            mod=mod,
234
            tmp=tmp,
235
            balancing_type=mod.balancing_type_project[g],
236
            boundary_type="linear",
237
        )
238
        or check_boundary_type(
239
            mod=mod,
240
            tmp=tmp,
241
            balancing_type=mod.balancing_type_project[g],
242
            boundary_type="linked",
243
        )
244
    ):
UNCOV
245
        pass
2✔
246
    else:
247
        return (
3✔
248
            mod.DispatchableLoadPrj_Consume_Power_MW[g, tmp]
249
            - mod.DispatchableLoadPrj_Consume_Power_MW[
250
                g, mod.prev_tmp[tmp, mod.balancing_type_project[g]]
251
            ]
252
        )
253

254

255
# Input-Output
256
###############################################################################
257

258

259
def load_model_data(
3✔
260
    mod,
261
    d,
262
    data_portal,
263
    scenario_directory,
264
    weather_iteration,
265
    hydro_iteration,
266
    availability_iteration,
267
    subproblem,
268
    stage,
269
):
270
    """
271

272
    :param mod:
273
    :param data_portal:
274
    :param scenario_directory:
275
    :param subproblem:
276
    :param stage:
277
    :return:
278
    """
279

280
    projects = load_optype_model_data(
3✔
281
        mod=mod,
282
        data_portal=data_portal,
283
        scenario_directory=scenario_directory,
284
        weather_iteration=weather_iteration,
285
        hydro_iteration=hydro_iteration,
286
        availability_iteration=availability_iteration,
287
        subproblem=subproblem,
288
        stage=stage,
289
        op_type="dispatchable_load",
290
    )
291

292

293
# Validation
294
###############################################################################
295

296

297
def validate_inputs(
3✔
298
    scenario_id,
299
    subscenarios,
300
    weather_iteration,
301
    hydro_iteration,
302
    availability_iteration,
303
    subproblem,
304
    stage,
305
    conn,
306
):
307
    """
308
    Get inputs from database and validate the inputs
309
    :param subscenarios: SubScenarios object with all subscenario info
310
    :param subproblem:
311
    :param stage:
312
    :param conn: database connection
313
    :return:
314
    """
315

316
    # Validate operational chars table inputs
317
    validate_opchars(
3✔
318
        scenario_id,
319
        subscenarios,
320
        weather_iteration,
321
        hydro_iteration,
322
        availability_iteration,
323
        subproblem,
324
        stage,
325
        conn,
326
        "dispatchable_load",
327
    )
328

329
    # Other module specific validations
330

331
    c = conn.cursor()
3✔
332
    heat_rates = c.execute(
3✔
333
        """
334
        SELECT project, load_point_fraction
335
        FROM inputs_project_portfolios
336
        INNER JOIN
337
        (SELECT project, operational_type, heat_rate_curves_scenario_id
338
        FROM inputs_project_operational_chars
339
        WHERE project_operational_chars_scenario_id = {}
340
        AND operational_type = '{}') AS op_char
341
        USING(project)
342
        INNER JOIN
343
        (SELECT project, heat_rate_curves_scenario_id, load_point_fraction
344
        FROM inputs_project_heat_rate_curves) as heat_rates
345
        USING(project, heat_rate_curves_scenario_id)
346
        WHERE project_portfolio_scenario_id = {}
347
        """.format(
348
            subscenarios.PROJECT_OPERATIONAL_CHARS_SCENARIO_ID,
349
            "dispatchable_load",
350
            subscenarios.PROJECT_PORTFOLIO_SCENARIO_ID,
351
        )
352
    )
353

354
    # Convert inputs to dataframe
355
    hr_df = cursor_to_df(heat_rates)
3✔
356

357
    # Check that there is only one load point (constant heat rate)
358
    write_validation_to_database(
3✔
359
        conn=conn,
360
        scenario_id=scenario_id,
361
        weather_iteration=weather_iteration,
362
        hydro_iteration=hydro_iteration,
363
        availability_iteration=availability_iteration,
364
        subproblem_id=subproblem,
365
        stage_id=stage,
366
        gridpath_module=__name__,
367
        db_table="inputs_project_heat_rate_curves",
368
        severity="Mid",
369
        errors=validate_single_input(
370
            df=hr_df,
371
            msg="dispatchable_load can only have one load point (constant heat rate).",
372
        ),
373
    )
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