NREL-reV 0.8.7__py3-none-any.whl → 0.8.9__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {NREL_reV-0.8.7.dist-info → NREL_reV-0.8.9.dist-info}/METADATA +12 -10
- {NREL_reV-0.8.7.dist-info → NREL_reV-0.8.9.dist-info}/RECORD +38 -38
- {NREL_reV-0.8.7.dist-info → NREL_reV-0.8.9.dist-info}/WHEEL +1 -1
- reV/SAM/SAM.py +182 -133
- reV/SAM/econ.py +18 -14
- reV/SAM/generation.py +608 -419
- reV/SAM/windbos.py +93 -79
- reV/bespoke/bespoke.py +690 -445
- reV/bespoke/place_turbines.py +6 -6
- reV/config/project_points.py +220 -140
- reV/econ/econ.py +165 -113
- reV/econ/economies_of_scale.py +57 -34
- reV/generation/base.py +310 -183
- reV/generation/generation.py +298 -190
- reV/handlers/exclusions.py +16 -15
- reV/handlers/multi_year.py +12 -9
- reV/handlers/outputs.py +6 -5
- reV/hybrids/hybrid_methods.py +28 -30
- reV/hybrids/hybrids.py +304 -188
- reV/nrwal/nrwal.py +262 -168
- reV/qa_qc/cli_qa_qc.py +14 -10
- reV/qa_qc/qa_qc.py +217 -119
- reV/qa_qc/summary.py +228 -146
- reV/rep_profiles/rep_profiles.py +349 -230
- reV/supply_curve/aggregation.py +349 -188
- reV/supply_curve/competitive_wind_farms.py +90 -48
- reV/supply_curve/exclusions.py +138 -85
- reV/supply_curve/extent.py +75 -50
- reV/supply_curve/points.py +536 -309
- reV/supply_curve/sc_aggregation.py +366 -225
- reV/supply_curve/supply_curve.py +505 -308
- reV/supply_curve/tech_mapping.py +144 -82
- reV/utilities/__init__.py +199 -16
- reV/utilities/pytest_utils.py +8 -4
- reV/version.py +1 -1
- {NREL_reV-0.8.7.dist-info → NREL_reV-0.8.9.dist-info}/LICENSE +0 -0
- {NREL_reV-0.8.7.dist-info → NREL_reV-0.8.9.dist-info}/entry_points.txt +0 -0
- {NREL_reV-0.8.7.dist-info → NREL_reV-0.8.9.dist-info}/top_level.txt +0 -0
reV/supply_curve/supply_curve.py
CHANGED
@@ -4,23 +4,22 @@ reV supply curve module
|
|
4
4
|
- Calculation of LCOT
|
5
5
|
- Supply Curve creation
|
6
6
|
"""
|
7
|
-
from copy import deepcopy
|
8
7
|
import json
|
9
8
|
import logging
|
10
|
-
import numpy as np
|
11
9
|
import os
|
12
|
-
|
10
|
+
from copy import deepcopy
|
13
11
|
from warnings import warn
|
14
12
|
|
13
|
+
import numpy as np
|
14
|
+
import pandas as pd
|
15
|
+
from rex import Resource
|
16
|
+
from rex.utilities import SpawnProcessPool, parse_table
|
17
|
+
|
15
18
|
from reV.handlers.transmission import TransmissionCosts as TC
|
16
19
|
from reV.handlers.transmission import TransmissionFeatures as TF
|
17
20
|
from reV.supply_curve.competitive_wind_farms import CompetitiveWindFarms
|
18
|
-
from reV.utilities
|
19
|
-
from reV.utilities import
|
20
|
-
|
21
|
-
from rex import Resource
|
22
|
-
from rex.utilities import parse_table, SpawnProcessPool
|
23
|
-
|
21
|
+
from reV.utilities import SupplyCurveField, log_versions
|
22
|
+
from reV.utilities.exceptions import SupplyCurveError, SupplyCurveInputError
|
24
23
|
|
25
24
|
logger = logging.getLogger(__name__)
|
26
25
|
|
@@ -29,8 +28,8 @@ class SupplyCurve:
|
|
29
28
|
"""SupplyCurve"""
|
30
29
|
|
31
30
|
def __init__(self, sc_points, trans_table, sc_features=None,
|
32
|
-
sc_capacity_col=
|
33
|
-
"""
|
31
|
+
sc_capacity_col=SupplyCurveField.CAPACITY):
|
32
|
+
"""ReV LCOT calculation and SupplyCurve sorting class.
|
34
33
|
|
35
34
|
``reV`` supply curve computes the transmission costs associated
|
36
35
|
with each supply curve point output by ``reV`` supply curve
|
@@ -127,15 +126,17 @@ class SupplyCurve:
|
|
127
126
|
(mean_lcoe_friction + lcot) ($/MWh).
|
128
127
|
"""
|
129
128
|
log_versions(logger)
|
130
|
-
logger.info(
|
131
|
-
logger.info(
|
132
|
-
logger.info(
|
129
|
+
logger.info("Supply curve points input: {}".format(sc_points))
|
130
|
+
logger.info("Transmission table input: {}".format(trans_table))
|
131
|
+
logger.info("Supply curve capacity column: {}".format(sc_capacity_col))
|
133
132
|
|
134
133
|
self._sc_capacity_col = sc_capacity_col
|
135
|
-
self._sc_points = self._parse_sc_points(
|
136
|
-
|
137
|
-
|
138
|
-
|
134
|
+
self._sc_points = self._parse_sc_points(
|
135
|
+
sc_points, sc_features=sc_features
|
136
|
+
)
|
137
|
+
self._trans_table = self._map_tables(
|
138
|
+
self._sc_points, trans_table, sc_capacity_col=sc_capacity_col
|
139
|
+
)
|
139
140
|
self._sc_gids, self._mask = self._parse_sc_gids(self._trans_table)
|
140
141
|
|
141
142
|
def __repr__(self):
|
@@ -177,31 +178,43 @@ class SupplyCurve:
|
|
177
178
|
DataFrame of supply curve point summary with additional features
|
178
179
|
added if supplied
|
179
180
|
"""
|
180
|
-
if isinstance(sc_points, str) and sc_points.endswith(
|
181
|
+
if isinstance(sc_points, str) and sc_points.endswith(".h5"):
|
181
182
|
with Resource(sc_points) as res:
|
182
183
|
sc_points = res.meta
|
183
|
-
sc_points.index.name =
|
184
|
+
sc_points.index.name = SupplyCurveField.SC_GID
|
184
185
|
sc_points = sc_points.reset_index()
|
185
186
|
else:
|
186
187
|
sc_points = parse_table(sc_points)
|
188
|
+
sc_points = sc_points.rename(
|
189
|
+
columns=SupplyCurveField.map_from_legacy())
|
187
190
|
|
188
|
-
logger.debug(
|
189
|
-
|
191
|
+
logger.debug(
|
192
|
+
"Supply curve points table imported with columns: {}".format(
|
193
|
+
sc_points.columns.values.tolist()
|
194
|
+
)
|
195
|
+
)
|
190
196
|
|
191
197
|
if sc_features is not None:
|
192
198
|
sc_features = parse_table(sc_features)
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
199
|
+
sc_features = sc_features.rename(
|
200
|
+
columns=SupplyCurveField.map_from_legacy())
|
201
|
+
merge_cols = [c for c in sc_features if c in sc_points]
|
202
|
+
sc_points = sc_points.merge(sc_features, on=merge_cols, how="left")
|
203
|
+
logger.debug(
|
204
|
+
"Adding Supply Curve Features table with columns: {}".format(
|
205
|
+
sc_features.columns.values.tolist()
|
206
|
+
)
|
207
|
+
)
|
208
|
+
|
209
|
+
if "transmission_multiplier" in sc_points:
|
210
|
+
col = "transmission_multiplier"
|
201
211
|
sc_points.loc[:, col] = sc_points.loc[:, col].fillna(1)
|
202
212
|
|
203
|
-
logger.debug(
|
204
|
-
|
213
|
+
logger.debug(
|
214
|
+
"Final supply curve points table has columns: {}".format(
|
215
|
+
sc_points.columns.values.tolist()
|
216
|
+
)
|
217
|
+
)
|
205
218
|
|
206
219
|
return sc_points
|
207
220
|
|
@@ -222,18 +235,20 @@ class SupplyCurve:
|
|
222
235
|
Columns to merge on which maps the sc columns (keys) to the
|
223
236
|
corresponding trans table columns (values)
|
224
237
|
"""
|
225
|
-
sc_columns = [c for c in sc_columns if c.startswith(
|
226
|
-
trans_columns = [c for c in trans_columns if c.startswith(
|
238
|
+
sc_columns = [c for c in sc_columns if c.startswith("sc_")]
|
239
|
+
trans_columns = [c for c in trans_columns if c.startswith("sc_")]
|
227
240
|
merge_cols = {}
|
228
|
-
for c_val in [
|
241
|
+
for c_val in ["row", "col"]:
|
229
242
|
trans_col = [c for c in trans_columns if c_val in c]
|
230
243
|
sc_col = [c for c in sc_columns if c_val in c]
|
231
244
|
if trans_col and sc_col:
|
232
245
|
merge_cols[sc_col[0]] = trans_col[0]
|
233
246
|
|
234
247
|
if len(merge_cols) != 2:
|
235
|
-
msg = (
|
236
|
-
|
248
|
+
msg = (
|
249
|
+
"Did not find a unique set of sc row and column ids to "
|
250
|
+
"merge on: {}".format(merge_cols)
|
251
|
+
)
|
237
252
|
logger.error(msg)
|
238
253
|
raise RuntimeError(msg)
|
239
254
|
|
@@ -266,24 +281,28 @@ class SupplyCurve:
|
|
266
281
|
# legacy name: trans_gids
|
267
282
|
# also xformer_cost_p_mw -> xformer_cost_per_mw (not sure why there
|
268
283
|
# would be a *_p_mw but here we are...)
|
269
|
-
rename_map = {
|
270
|
-
|
271
|
-
|
284
|
+
rename_map = {
|
285
|
+
"trans_line_gid": "trans_gid",
|
286
|
+
"trans_gids": "trans_line_gids",
|
287
|
+
"xformer_cost_p_mw": "xformer_cost_per_mw",
|
288
|
+
}
|
272
289
|
trans_table = trans_table.rename(columns=rename_map)
|
273
290
|
|
274
|
-
if
|
275
|
-
trans_table = trans_table.rename(columns={
|
276
|
-
trans_table[
|
291
|
+
if "dist_mi" in trans_table and "dist_km" not in trans_table:
|
292
|
+
trans_table = trans_table.rename(columns={"dist_mi": "dist_km"})
|
293
|
+
trans_table["dist_km"] *= 1.60934
|
277
294
|
|
278
|
-
drop_cols = [
|
295
|
+
drop_cols = [SupplyCurveField.SC_GID, 'cap_left',
|
296
|
+
SupplyCurveField.SC_POINT_GID]
|
279
297
|
drop_cols = [c for c in drop_cols if c in trans_table]
|
280
298
|
if drop_cols:
|
281
299
|
trans_table = trans_table.drop(columns=drop_cols)
|
282
300
|
|
283
|
-
return trans_table
|
301
|
+
return trans_table.rename(columns=SupplyCurveField.map_from_legacy())
|
284
302
|
|
285
303
|
@staticmethod
|
286
|
-
def _map_trans_capacity(trans_sc_table,
|
304
|
+
def _map_trans_capacity(trans_sc_table,
|
305
|
+
sc_capacity_col=SupplyCurveField.CAPACITY):
|
287
306
|
"""
|
288
307
|
Map SC gids to transmission features based on capacity. For any SC
|
289
308
|
gids with capacity > the maximum transmission feature capacity, map
|
@@ -310,33 +329,42 @@ class SupplyCurve:
|
|
310
329
|
based on maximum capacity
|
311
330
|
"""
|
312
331
|
|
313
|
-
nx = trans_sc_table[sc_capacity_col] / trans_sc_table[
|
332
|
+
nx = trans_sc_table[sc_capacity_col] / trans_sc_table["max_cap"]
|
314
333
|
nx = np.ceil(nx).astype(int)
|
315
|
-
trans_sc_table[
|
334
|
+
trans_sc_table["n_parallel_trans"] = nx
|
316
335
|
|
317
336
|
if (nx > 1).any():
|
318
337
|
mask = nx > 1
|
319
|
-
tie_line_cost = (
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
338
|
+
tie_line_cost = (
|
339
|
+
trans_sc_table.loc[mask, "tie_line_cost"] * nx[mask]
|
340
|
+
)
|
341
|
+
|
342
|
+
xformer_cost = (
|
343
|
+
trans_sc_table.loc[mask, "xformer_cost_per_mw"]
|
344
|
+
* trans_sc_table.loc[mask, "max_cap"]
|
345
|
+
* nx[mask]
|
346
|
+
)
|
347
|
+
|
348
|
+
conn_cost = (
|
349
|
+
xformer_cost
|
350
|
+
+ trans_sc_table.loc[mask, "sub_upgrade_cost"]
|
351
|
+
+ trans_sc_table.loc[mask, "new_sub_cost"]
|
352
|
+
)
|
328
353
|
|
329
354
|
trans_cap_cost = tie_line_cost + conn_cost
|
330
355
|
|
331
|
-
trans_sc_table.loc[mask,
|
332
|
-
trans_sc_table.loc[mask,
|
333
|
-
trans_sc_table.loc[mask,
|
334
|
-
trans_sc_table.loc[mask,
|
335
|
-
|
336
|
-
msg = (
|
337
|
-
|
338
|
-
|
339
|
-
|
356
|
+
trans_sc_table.loc[mask, "tie_line_cost"] = tie_line_cost
|
357
|
+
trans_sc_table.loc[mask, "xformer_cost"] = xformer_cost
|
358
|
+
trans_sc_table.loc[mask, "connection_cost"] = conn_cost
|
359
|
+
trans_sc_table.loc[mask, "trans_cap_cost"] = trans_cap_cost
|
360
|
+
|
361
|
+
msg = (
|
362
|
+
"{} SC points have a capacity that exceeds the maximum "
|
363
|
+
"transmission feature capacity and will be connected with "
|
364
|
+
"multiple parallel transmission features.".format(
|
365
|
+
(nx > 1).sum()
|
366
|
+
)
|
367
|
+
)
|
340
368
|
logger.info(msg)
|
341
369
|
|
342
370
|
return trans_sc_table
|
@@ -379,24 +407,30 @@ class SupplyCurve:
|
|
379
407
|
List of missing transmission line 'trans_gid's for all substations
|
380
408
|
in features table
|
381
409
|
"""
|
382
|
-
features = features.rename(
|
383
|
-
|
384
|
-
|
410
|
+
features = features.rename(
|
411
|
+
columns={
|
412
|
+
"trans_line_gid": "trans_gid",
|
413
|
+
"trans_gids": "trans_line_gids",
|
414
|
+
}
|
415
|
+
)
|
416
|
+
mask = features["category"].str.lower() == "substation"
|
385
417
|
|
386
418
|
if not any(mask):
|
387
419
|
return []
|
388
420
|
|
389
|
-
line_gids =
|
390
|
-
|
421
|
+
line_gids = features.loc[mask, "trans_line_gids"].apply(
|
422
|
+
cls._parse_trans_line_gids
|
423
|
+
)
|
391
424
|
|
392
425
|
line_gids = np.unique(np.concatenate(line_gids.values))
|
393
426
|
|
394
|
-
test = np.isin(line_gids, features[
|
427
|
+
test = np.isin(line_gids, features["trans_gid"].values)
|
395
428
|
|
396
429
|
return line_gids[~test].tolist()
|
397
430
|
|
398
431
|
@classmethod
|
399
|
-
def _check_substation_conns(cls, trans_table,
|
432
|
+
def _check_substation_conns(cls, trans_table,
|
433
|
+
sc_cols=SupplyCurveField.SC_GID):
|
400
434
|
"""
|
401
435
|
Run checks on substation transmission features to make sure that
|
402
436
|
every sc point connecting to a substation can also connect to its
|
@@ -409,7 +443,7 @@ class SupplyCurve:
|
|
409
443
|
(should already be merged with SC points).
|
410
444
|
sc_cols : str | list, optional
|
411
445
|
Column(s) in trans_table with unique supply curve id,
|
412
|
-
by default
|
446
|
+
by default SupplyCurveField.SC_GID
|
413
447
|
"""
|
414
448
|
missing = {}
|
415
449
|
for sc_point, sc_table in trans_table.groupby(sc_cols):
|
@@ -418,10 +452,13 @@ class SupplyCurve:
|
|
418
452
|
missing[sc_point] = tl_gids
|
419
453
|
|
420
454
|
if any(missing):
|
421
|
-
msg = (
|
422
|
-
|
423
|
-
|
424
|
-
|
455
|
+
msg = (
|
456
|
+
"The following sc_gid (keys) were connected to substations "
|
457
|
+
"but were not connected to the respective transmission line"
|
458
|
+
" gids (values) which is required for full SC sort: {}".format(
|
459
|
+
missing
|
460
|
+
)
|
461
|
+
)
|
425
462
|
logger.error(msg)
|
426
463
|
raise SupplyCurveInputError(msg)
|
427
464
|
|
@@ -437,37 +474,49 @@ class SupplyCurve:
|
|
437
474
|
Table mapping supply curve points to transmission features
|
438
475
|
(should already be merged with SC points).
|
439
476
|
"""
|
440
|
-
sc_gids = set(sc_points[
|
441
|
-
trans_sc_gids = set(trans_table[
|
477
|
+
sc_gids = set(sc_points[SupplyCurveField.SC_GID].unique())
|
478
|
+
trans_sc_gids = set(trans_table[SupplyCurveField.SC_GID].unique())
|
442
479
|
missing = sorted(list(sc_gids - trans_sc_gids))
|
443
480
|
if any(missing):
|
444
|
-
msg = (
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
481
|
+
msg = (
|
482
|
+
"There are {} Supply Curve points with missing "
|
483
|
+
"transmission mappings. Supply curve points with no "
|
484
|
+
"transmission features will not be connected! "
|
485
|
+
"Missing sc_gid's: {}".format(len(missing), missing)
|
486
|
+
)
|
449
487
|
logger.warning(msg)
|
450
488
|
warn(msg)
|
451
489
|
|
452
490
|
if not any(trans_sc_gids) or not any(sc_gids):
|
453
|
-
msg = (
|
454
|
-
|
455
|
-
|
456
|
-
|
491
|
+
msg = (
|
492
|
+
"Merging of sc points table and transmission features "
|
493
|
+
"table failed with {} original sc gids and {} transmission "
|
494
|
+
"sc gids after table merge.".format(
|
495
|
+
len(sc_gids), len(trans_sc_gids)
|
496
|
+
)
|
497
|
+
)
|
457
498
|
logger.error(msg)
|
458
499
|
raise SupplyCurveError(msg)
|
459
500
|
|
460
|
-
logger.debug(
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
501
|
+
logger.debug(
|
502
|
+
"There are {} original SC gids and {} sc gids in the "
|
503
|
+
"merged transmission table.".format(
|
504
|
+
len(sc_gids), len(trans_sc_gids)
|
505
|
+
)
|
506
|
+
)
|
507
|
+
logger.debug(
|
508
|
+
"Transmission Table created with columns: {}".format(
|
509
|
+
trans_table.columns.values.tolist()
|
510
|
+
)
|
511
|
+
)
|
465
512
|
|
466
513
|
@classmethod
|
467
514
|
def _merge_sc_trans_tables(cls, sc_points, trans_table,
|
468
|
-
sc_cols=(
|
469
|
-
|
470
|
-
|
515
|
+
sc_cols=(SupplyCurveField.SC_GID,
|
516
|
+
SupplyCurveField.CAPACITY,
|
517
|
+
SupplyCurveField.MEAN_CF,
|
518
|
+
SupplyCurveField.MEAN_LCOE),
|
519
|
+
sc_capacity_col=SupplyCurveField.CAPACITY):
|
471
520
|
"""
|
472
521
|
Merge the supply curve table with the transmission features table.
|
473
522
|
|
@@ -482,7 +531,8 @@ class SupplyCurve:
|
|
482
531
|
sc_cols : tuple | list, optional
|
483
532
|
List of column from sc_points to transfer into the trans table,
|
484
533
|
If the `sc_capacity_col` is not included, it will get added.
|
485
|
-
by default (
|
534
|
+
by default (SupplyCurveField.SC_GID, 'capacity', 'mean_cf',
|
535
|
+
'mean_lcoe')
|
486
536
|
sc_capacity_col : str, optional
|
487
537
|
Name of capacity column in `trans_sc_table`. The values in
|
488
538
|
this column determine the size of transmission lines built.
|
@@ -505,42 +555,55 @@ class SupplyCurve:
|
|
505
555
|
if isinstance(trans_table, (list, tuple)):
|
506
556
|
trans_sc_table = []
|
507
557
|
for table in trans_table:
|
508
|
-
trans_sc_table.append(
|
509
|
-
|
510
|
-
|
558
|
+
trans_sc_table.append(
|
559
|
+
cls._merge_sc_trans_tables(
|
560
|
+
sc_points,
|
561
|
+
table,
|
562
|
+
sc_cols=sc_cols,
|
563
|
+
sc_capacity_col=sc_capacity_col,
|
564
|
+
)
|
565
|
+
)
|
511
566
|
|
512
567
|
trans_sc_table = pd.concat(trans_sc_table)
|
513
568
|
else:
|
514
569
|
trans_table = cls._parse_trans_table(trans_table)
|
515
570
|
|
516
|
-
merge_cols = cls._get_merge_cols(
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
571
|
+
merge_cols = cls._get_merge_cols(
|
572
|
+
sc_points.columns, trans_table.columns
|
573
|
+
)
|
574
|
+
logger.info(
|
575
|
+
"Merging SC table and Trans Table with "
|
576
|
+
"{} mapping: {}".format(
|
577
|
+
"sc_table_col: trans_table_col", merge_cols
|
578
|
+
)
|
579
|
+
)
|
521
580
|
sc_points = sc_points.rename(columns=merge_cols)
|
522
581
|
merge_cols = list(merge_cols.values())
|
523
582
|
|
524
583
|
if isinstance(sc_cols, tuple):
|
525
584
|
sc_cols = list(sc_cols)
|
526
585
|
|
527
|
-
if
|
528
|
-
sc_cols.append(
|
586
|
+
if SupplyCurveField.MEAN_LCOE_FRICTION in sc_points:
|
587
|
+
sc_cols.append(SupplyCurveField.MEAN_LCOE_FRICTION)
|
529
588
|
|
530
|
-
if
|
531
|
-
sc_cols.append(
|
589
|
+
if "transmission_multiplier" in sc_points:
|
590
|
+
sc_cols.append("transmission_multiplier")
|
532
591
|
|
533
592
|
sc_cols += merge_cols
|
534
593
|
sc_points = sc_points[sc_cols].copy()
|
535
|
-
trans_sc_table = trans_table.merge(
|
536
|
-
|
594
|
+
trans_sc_table = trans_table.merge(
|
595
|
+
sc_points, on=merge_cols, how="inner"
|
596
|
+
)
|
537
597
|
|
538
598
|
return trans_sc_table
|
539
599
|
|
540
600
|
@classmethod
|
541
601
|
def _map_tables(cls, sc_points, trans_table,
|
542
|
-
sc_cols=(
|
543
|
-
|
602
|
+
sc_cols=(SupplyCurveField.SC_GID,
|
603
|
+
SupplyCurveField.CAPACITY,
|
604
|
+
SupplyCurveField.MEAN_CF,
|
605
|
+
SupplyCurveField.MEAN_LCOE),
|
606
|
+
sc_capacity_col=SupplyCurveField.CAPACITY):
|
544
607
|
"""
|
545
608
|
Map supply curve points to transmission features
|
546
609
|
|
@@ -555,7 +618,8 @@ class SupplyCurve:
|
|
555
618
|
sc_cols : tuple | list, optional
|
556
619
|
List of column from sc_points to transfer into the trans table,
|
557
620
|
If the `sc_capacity_col` is not included, it will get added.
|
558
|
-
by default (
|
621
|
+
by default (SupplyCurveField.SC_GID, SupplyCurveField.CAPACITY,
|
622
|
+
SupplyCurveField.MEAN_CF, SupplyCurveField.MEAN_LCOE)
|
559
623
|
sc_capacity_col : str, optional
|
560
624
|
Name of capacity column in `trans_sc_table`. The values in
|
561
625
|
this column determine the size of transmission lines built.
|
@@ -573,17 +637,18 @@ class SupplyCurve:
|
|
573
637
|
This is performed by an inner merging with trans_table
|
574
638
|
"""
|
575
639
|
scc = sc_capacity_col
|
576
|
-
trans_sc_table = cls._merge_sc_trans_tables(
|
577
|
-
|
578
|
-
|
640
|
+
trans_sc_table = cls._merge_sc_trans_tables(
|
641
|
+
sc_points, trans_table, sc_cols=sc_cols, sc_capacity_col=scc
|
642
|
+
)
|
579
643
|
|
580
|
-
if
|
581
|
-
trans_sc_table = cls._map_trans_capacity(
|
582
|
-
|
644
|
+
if "max_cap" in trans_sc_table:
|
645
|
+
trans_sc_table = cls._map_trans_capacity(
|
646
|
+
trans_sc_table, sc_capacity_col=scc
|
647
|
+
)
|
583
648
|
|
584
649
|
trans_sc_table = \
|
585
650
|
trans_sc_table.sort_values(
|
586
|
-
[
|
651
|
+
[SupplyCurveField.SC_GID, 'trans_gid']).reset_index(drop=True)
|
587
652
|
|
588
653
|
cls._check_sc_trans_table(sc_points, trans_sc_table)
|
589
654
|
|
@@ -619,13 +684,14 @@ class SupplyCurve:
|
|
619
684
|
else:
|
620
685
|
kwargs = {}
|
621
686
|
|
622
|
-
trans_features = TF(
|
623
|
-
|
687
|
+
trans_features = TF(
|
688
|
+
trans_table, avail_cap_frac=avail_cap_frac, **kwargs
|
689
|
+
)
|
624
690
|
|
625
691
|
return trans_features
|
626
692
|
|
627
693
|
@staticmethod
|
628
|
-
def _parse_sc_gids(trans_table, gid_key=
|
694
|
+
def _parse_sc_gids(trans_table, gid_key=SupplyCurveField.SC_GID):
|
629
695
|
"""Extract unique sc gids, make bool mask from tranmission table
|
630
696
|
|
631
697
|
Parameters
|
@@ -652,7 +718,7 @@ class SupplyCurve:
|
|
652
718
|
|
653
719
|
@staticmethod
|
654
720
|
def _get_capacity(sc_gid, sc_table, connectable=True,
|
655
|
-
sc_capacity_col=
|
721
|
+
sc_capacity_col=SupplyCurveField.CAPACITY):
|
656
722
|
"""
|
657
723
|
Get capacity of supply curve point
|
658
724
|
|
@@ -686,9 +752,10 @@ class SupplyCurve:
|
|
686
752
|
if len(capacity) == 1:
|
687
753
|
capacity = capacity[0]
|
688
754
|
else:
|
689
|
-
msg = (
|
690
|
-
|
691
|
-
|
755
|
+
msg = (
|
756
|
+
"Each supply curve point should only have "
|
757
|
+
"a single capacity, but {} has {}".format(sc_gid, capacity)
|
758
|
+
)
|
692
759
|
logger.error(msg)
|
693
760
|
raise RuntimeError(msg)
|
694
761
|
else:
|
@@ -700,7 +767,7 @@ class SupplyCurve:
|
|
700
767
|
def _compute_trans_cap_cost(cls, trans_table, trans_costs=None,
|
701
768
|
avail_cap_frac=1, max_workers=None,
|
702
769
|
connectable=True, line_limited=False,
|
703
|
-
sc_capacity_col=
|
770
|
+
sc_capacity_col=SupplyCurveField.CAPACITY):
|
704
771
|
"""
|
705
772
|
Compute levelized cost of transmission for all combinations of
|
706
773
|
supply curve points and tranmission features in trans_table
|
@@ -749,9 +816,11 @@ class SupplyCurve:
|
|
749
816
|
"""
|
750
817
|
scc = sc_capacity_col
|
751
818
|
if scc not in trans_table:
|
752
|
-
raise SupplyCurveInputError(
|
753
|
-
|
754
|
-
|
819
|
+
raise SupplyCurveInputError(
|
820
|
+
"Supply curve table must have "
|
821
|
+
"supply curve point capacity column"
|
822
|
+
"({}) to compute lcot".format(scc)
|
823
|
+
)
|
755
824
|
|
756
825
|
if trans_costs is not None:
|
757
826
|
trans_costs = TF._parse_dictionary(trans_costs)
|
@@ -762,44 +831,66 @@ class SupplyCurve:
|
|
762
831
|
max_workers = os.cpu_count()
|
763
832
|
|
764
833
|
logger.info('Computing LCOT costs for all possible connections...')
|
765
|
-
groups = trans_table.groupby(
|
834
|
+
groups = trans_table.groupby(SupplyCurveField.SC_GID)
|
766
835
|
if max_workers > 1:
|
767
|
-
loggers = [__name__,
|
768
|
-
with SpawnProcessPool(
|
769
|
-
|
836
|
+
loggers = [__name__, "reV.handlers.transmission", "reV"]
|
837
|
+
with SpawnProcessPool(
|
838
|
+
max_workers=max_workers, loggers=loggers
|
839
|
+
) as exe:
|
770
840
|
futures = []
|
771
841
|
for sc_gid, sc_table in groups:
|
772
|
-
capacity = cls._get_capacity(
|
773
|
-
|
774
|
-
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
|
779
|
-
|
842
|
+
capacity = cls._get_capacity(
|
843
|
+
sc_gid,
|
844
|
+
sc_table,
|
845
|
+
connectable=connectable,
|
846
|
+
sc_capacity_col=scc,
|
847
|
+
)
|
848
|
+
futures.append(
|
849
|
+
exe.submit(
|
850
|
+
TC.feature_costs,
|
851
|
+
sc_table,
|
852
|
+
capacity=capacity,
|
853
|
+
avail_cap_frac=avail_cap_frac,
|
854
|
+
line_limited=line_limited,
|
855
|
+
**trans_costs,
|
856
|
+
)
|
857
|
+
)
|
780
858
|
|
781
859
|
cost = [future.result() for future in futures]
|
782
860
|
else:
|
783
861
|
cost = []
|
784
862
|
for sc_gid, sc_table in groups:
|
785
|
-
capacity = cls._get_capacity(
|
786
|
-
|
787
|
-
|
788
|
-
|
789
|
-
|
790
|
-
|
791
|
-
|
792
|
-
|
793
|
-
|
794
|
-
|
795
|
-
|
863
|
+
capacity = cls._get_capacity(
|
864
|
+
sc_gid,
|
865
|
+
sc_table,
|
866
|
+
connectable=connectable,
|
867
|
+
sc_capacity_col=scc,
|
868
|
+
)
|
869
|
+
cost.append(
|
870
|
+
TC.feature_costs(
|
871
|
+
sc_table,
|
872
|
+
capacity=capacity,
|
873
|
+
avail_cap_frac=avail_cap_frac,
|
874
|
+
line_limited=line_limited,
|
875
|
+
**trans_costs,
|
876
|
+
)
|
877
|
+
)
|
878
|
+
|
879
|
+
cost = np.hstack(cost).astype("float32")
|
880
|
+
logger.info("LCOT cost calculation is complete.")
|
796
881
|
|
797
882
|
return cost
|
798
883
|
|
799
|
-
def compute_total_lcoe(
|
800
|
-
|
801
|
-
|
802
|
-
|
884
|
+
def compute_total_lcoe(
|
885
|
+
self,
|
886
|
+
fcr,
|
887
|
+
transmission_costs=None,
|
888
|
+
avail_cap_frac=1,
|
889
|
+
line_limited=False,
|
890
|
+
connectable=True,
|
891
|
+
max_workers=None,
|
892
|
+
consider_friction=True,
|
893
|
+
):
|
803
894
|
"""
|
804
895
|
Compute LCOT and total LCOE for all sc point to transmission feature
|
805
896
|
connections
|
@@ -828,45 +919,50 @@ class SupplyCurve:
|
|
828
919
|
Flag to consider friction layer on LCOE when "mean_lcoe_friction"
|
829
920
|
is in the sc points input, by default True
|
830
921
|
"""
|
831
|
-
if
|
922
|
+
if "trans_cap_cost" not in self._trans_table:
|
832
923
|
scc = self._sc_capacity_col
|
833
|
-
cost = self._compute_trans_cap_cost(
|
834
|
-
|
835
|
-
|
836
|
-
|
837
|
-
|
838
|
-
|
839
|
-
|
840
|
-
|
924
|
+
cost = self._compute_trans_cap_cost(
|
925
|
+
self._trans_table,
|
926
|
+
trans_costs=transmission_costs,
|
927
|
+
avail_cap_frac=avail_cap_frac,
|
928
|
+
line_limited=line_limited,
|
929
|
+
connectable=connectable,
|
930
|
+
max_workers=max_workers,
|
931
|
+
sc_capacity_col=scc,
|
932
|
+
)
|
933
|
+
self._trans_table["trans_cap_cost_per_mw"] = cost # $/MW
|
841
934
|
else:
|
842
|
-
cost = self._trans_table[
|
935
|
+
cost = self._trans_table["trans_cap_cost"].values.copy() # $
|
843
936
|
cost /= self._trans_table[self._sc_capacity_col] # $/MW
|
844
|
-
self._trans_table[
|
937
|
+
self._trans_table["trans_cap_cost_per_mw"] = cost
|
845
938
|
|
846
939
|
cost *= self._trans_table[self._sc_capacity_col]
|
847
|
-
|
940
|
+
# align with "mean_cf"
|
941
|
+
cost /= self._trans_table[SupplyCurveField.CAPACITY]
|
848
942
|
|
849
943
|
if 'reinforcement_cost_per_mw' in self._trans_table:
|
850
944
|
logger.info("'reinforcement_cost_per_mw' column found in "
|
851
945
|
"transmission table. Adding reinforcement costs "
|
852
946
|
"to total LCOE.")
|
853
|
-
cf_mean_arr = self._trans_table[
|
947
|
+
cf_mean_arr = self._trans_table[SupplyCurveField.MEAN_CF].values
|
854
948
|
lcot = (cost * fcr) / (cf_mean_arr * 8760)
|
855
|
-
lcoe = lcot + self._trans_table[
|
949
|
+
lcoe = lcot + self._trans_table[SupplyCurveField.MEAN_LCOE]
|
856
950
|
self._trans_table['lcot_no_reinforcement'] = lcot
|
857
951
|
self._trans_table['lcoe_no_reinforcement'] = lcoe
|
858
952
|
r_cost = (self._trans_table['reinforcement_cost_per_mw']
|
859
953
|
.values.copy())
|
860
954
|
r_cost *= self._trans_table[self._sc_capacity_col]
|
861
|
-
|
955
|
+
# align with "mean_cf"
|
956
|
+
r_cost /= self._trans_table[SupplyCurveField.CAPACITY]
|
862
957
|
cost += r_cost # $/MW
|
863
958
|
|
864
|
-
cf_mean_arr = self._trans_table[
|
959
|
+
cf_mean_arr = self._trans_table[SupplyCurveField.MEAN_CF].values
|
865
960
|
lcot = (cost * fcr) / (cf_mean_arr * 8760)
|
866
961
|
|
867
962
|
self._trans_table['lcot'] = lcot
|
868
|
-
self._trans_table['total_lcoe'] = (
|
869
|
-
|
963
|
+
self._trans_table['total_lcoe'] = (
|
964
|
+
self._trans_table['lcot']
|
965
|
+
+ self._trans_table[SupplyCurveField.MEAN_LCOE])
|
870
966
|
|
871
967
|
if consider_friction:
|
872
968
|
self._calculate_total_lcoe_friction()
|
@@ -875,15 +971,19 @@ class SupplyCurve:
|
|
875
971
|
"""Look for site mean LCOE with friction in the trans table and if
|
876
972
|
found make a total LCOE column with friction."""
|
877
973
|
|
878
|
-
if
|
879
|
-
lcoe_friction = (
|
880
|
-
|
881
|
-
|
974
|
+
if SupplyCurveField.MEAN_LCOE_FRICTION in self._trans_table:
|
975
|
+
lcoe_friction = (
|
976
|
+
self._trans_table['lcot']
|
977
|
+
+ self._trans_table[SupplyCurveField.MEAN_LCOE_FRICTION])
|
978
|
+
self._trans_table[SupplyCurveField.TOTAL_LCOE_FRICTION] = (
|
979
|
+
lcoe_friction
|
980
|
+
)
|
882
981
|
logger.info('Found mean LCOE with friction. Adding key '
|
883
982
|
'"total_lcoe_friction" to trans table.')
|
884
983
|
|
885
|
-
def _exclude_noncompetitive_wind_farms(
|
886
|
-
|
984
|
+
def _exclude_noncompetitive_wind_farms(
|
985
|
+
self, comp_wind_dirs, sc_gid, downwind=False
|
986
|
+
):
|
887
987
|
"""
|
888
988
|
Exclude non-competitive wind farms for given sc_gid
|
889
989
|
|
@@ -905,18 +1005,20 @@ class SupplyCurve:
|
|
905
1005
|
gid = comp_wind_dirs.check_sc_gid(sc_gid)
|
906
1006
|
if gid is not None:
|
907
1007
|
if comp_wind_dirs.mask[gid]:
|
908
|
-
exclude_gids = comp_wind_dirs[
|
1008
|
+
exclude_gids = comp_wind_dirs["upwind", gid]
|
909
1009
|
if downwind:
|
910
|
-
exclude_gids = np.append(
|
911
|
-
|
1010
|
+
exclude_gids = np.append(
|
1011
|
+
exclude_gids, comp_wind_dirs["downwind", gid]
|
1012
|
+
)
|
912
1013
|
for n in exclude_gids:
|
913
1014
|
check = comp_wind_dirs.exclude_sc_point_gid(n)
|
914
1015
|
if check:
|
915
|
-
sc_gids = comp_wind_dirs[
|
1016
|
+
sc_gids = comp_wind_dirs[SupplyCurveField.SC_GID, n]
|
916
1017
|
for sc_id in sc_gids:
|
917
1018
|
if self._mask[sc_id]:
|
918
|
-
logger.debug(
|
919
|
-
|
1019
|
+
logger.debug(
|
1020
|
+
"Excluding sc_gid {}".format(sc_id)
|
1021
|
+
)
|
920
1022
|
self._mask[sc_id] = False
|
921
1023
|
|
922
1024
|
return comp_wind_dirs
|
@@ -945,8 +1047,11 @@ class SupplyCurve:
|
|
945
1047
|
missing = [s for s in sum_labels if s not in table]
|
946
1048
|
|
947
1049
|
if any(missing):
|
948
|
-
logger.info(
|
949
|
-
|
1050
|
+
logger.info(
|
1051
|
+
'Could not make sum column "{}", missing: {}'.format(
|
1052
|
+
new_label, missing
|
1053
|
+
)
|
1054
|
+
)
|
950
1055
|
else:
|
951
1056
|
sum_arr = np.zeros(len(table))
|
952
1057
|
for s in sum_labels:
|
@@ -958,13 +1063,25 @@ class SupplyCurve:
|
|
958
1063
|
|
959
1064
|
return table
|
960
1065
|
|
961
|
-
def _full_sort(
|
962
|
-
|
963
|
-
|
964
|
-
|
965
|
-
|
966
|
-
|
967
|
-
|
1066
|
+
def _full_sort(
|
1067
|
+
self,
|
1068
|
+
trans_table,
|
1069
|
+
trans_costs=None,
|
1070
|
+
avail_cap_frac=1,
|
1071
|
+
comp_wind_dirs=None,
|
1072
|
+
total_lcoe_fric=None,
|
1073
|
+
sort_on="total_lcoe",
|
1074
|
+
columns=(
|
1075
|
+
"trans_gid",
|
1076
|
+
"trans_capacity",
|
1077
|
+
"trans_type",
|
1078
|
+
"trans_cap_cost_per_mw",
|
1079
|
+
"dist_km",
|
1080
|
+
"lcot",
|
1081
|
+
"total_lcoe",
|
1082
|
+
),
|
1083
|
+
downwind=False,
|
1084
|
+
):
|
968
1085
|
"""
|
969
1086
|
Internal method to handle full supply curve sorting
|
970
1087
|
|
@@ -1002,9 +1119,11 @@ class SupplyCurve:
|
|
1002
1119
|
Updated sc_points table with transmission connections, LCOT
|
1003
1120
|
and LCOE+LCOT based on full supply curve connections
|
1004
1121
|
"""
|
1005
|
-
trans_features = self._create_handler(
|
1006
|
-
|
1007
|
-
|
1122
|
+
trans_features = self._create_handler(
|
1123
|
+
self._trans_table,
|
1124
|
+
trans_costs=trans_costs,
|
1125
|
+
avail_cap_frac=avail_cap_frac,
|
1126
|
+
)
|
1008
1127
|
init_list = [np.nan] * int(1 + np.max(self._sc_gids))
|
1009
1128
|
columns = list(columns)
|
1010
1129
|
if sort_on not in columns:
|
@@ -1012,22 +1131,25 @@ class SupplyCurve:
|
|
1012
1131
|
|
1013
1132
|
conn_lists = {k: deepcopy(init_list) for k in columns}
|
1014
1133
|
|
1015
|
-
trans_sc_gids = trans_table[
|
1134
|
+
trans_sc_gids = trans_table[SupplyCurveField.SC_GID].values.astype(int)
|
1016
1135
|
|
1017
1136
|
# syntax is final_key: source_key (source from trans_table)
|
1018
1137
|
all_cols = {k: k for k in columns}
|
1019
|
-
essentials = {
|
1020
|
-
|
1021
|
-
|
1022
|
-
|
1023
|
-
|
1024
|
-
|
1025
|
-
|
1026
|
-
|
1138
|
+
essentials = {
|
1139
|
+
"trans_gid": "trans_gid",
|
1140
|
+
"trans_capacity": "avail_cap",
|
1141
|
+
"trans_type": "category",
|
1142
|
+
"dist_km": "dist_km",
|
1143
|
+
"trans_cap_cost_per_mw": "trans_cap_cost_per_mw",
|
1144
|
+
"lcot": "lcot",
|
1145
|
+
"total_lcoe": "total_lcoe",
|
1146
|
+
}
|
1027
1147
|
all_cols.update(essentials)
|
1028
1148
|
|
1029
|
-
arrays = {
|
1030
|
-
|
1149
|
+
arrays = {
|
1150
|
+
final_key: trans_table[source_key].values
|
1151
|
+
for final_key, source_key in all_cols.items()
|
1152
|
+
}
|
1031
1153
|
|
1032
1154
|
sc_capacities = trans_table[self._sc_capacity_col].values
|
1033
1155
|
|
@@ -1036,52 +1158,62 @@ class SupplyCurve:
|
|
1036
1158
|
for i in range(len(trans_table)):
|
1037
1159
|
sc_gid = trans_sc_gids[i]
|
1038
1160
|
if self._mask[sc_gid]:
|
1039
|
-
connect = trans_features.connect(
|
1040
|
-
|
1161
|
+
connect = trans_features.connect(
|
1162
|
+
arrays["trans_gid"][i], sc_capacities[i]
|
1163
|
+
)
|
1041
1164
|
if connect:
|
1042
1165
|
connected += 1
|
1043
|
-
logger.debug(
|
1166
|
+
logger.debug("Connecting sc gid {}".format(sc_gid))
|
1044
1167
|
self._mask[sc_gid] = False
|
1045
1168
|
|
1046
1169
|
for col_name, data_arr in arrays.items():
|
1047
1170
|
conn_lists[col_name][sc_gid] = data_arr[i]
|
1048
1171
|
|
1049
1172
|
if total_lcoe_fric is not None:
|
1050
|
-
|
1051
|
-
|
1173
|
+
col_name = SupplyCurveField.TOTAL_LCOE_FRICTION
|
1174
|
+
conn_lists[col_name][sc_gid] = total_lcoe_fric[i]
|
1052
1175
|
|
1053
1176
|
current_prog = connected // (len(self) / 100)
|
1054
1177
|
if current_prog > progress:
|
1055
1178
|
progress = current_prog
|
1056
|
-
logger.info(
|
1057
|
-
|
1179
|
+
logger.info(
|
1180
|
+
"{} % of supply curve points connected".format(
|
1181
|
+
progress
|
1182
|
+
)
|
1183
|
+
)
|
1058
1184
|
|
1059
1185
|
if comp_wind_dirs is not None:
|
1060
|
-
comp_wind_dirs =
|
1186
|
+
comp_wind_dirs = (
|
1061
1187
|
self._exclude_noncompetitive_wind_farms(
|
1062
|
-
comp_wind_dirs, sc_gid, downwind=downwind
|
1188
|
+
comp_wind_dirs, sc_gid, downwind=downwind
|
1189
|
+
)
|
1190
|
+
)
|
1063
1191
|
|
1064
1192
|
index = range(0, int(1 + np.max(self._sc_gids)))
|
1065
1193
|
connections = pd.DataFrame(conn_lists, index=index)
|
1066
|
-
connections.index.name =
|
1194
|
+
connections.index.name = SupplyCurveField.SC_GID
|
1067
1195
|
connections = connections.dropna(subset=[sort_on])
|
1068
1196
|
connections = connections[columns].reset_index()
|
1069
1197
|
|
1070
|
-
sc_gids = self._sc_points[
|
1071
|
-
connected = connections[
|
1198
|
+
sc_gids = self._sc_points[SupplyCurveField.SC_GID].values
|
1199
|
+
connected = connections[SupplyCurveField.SC_GID].values
|
1072
1200
|
logger.debug('Connected gids {} out of total supply curve gids {}'
|
1073
1201
|
.format(len(connected), len(sc_gids)))
|
1074
1202
|
unconnected = ~np.isin(sc_gids, connected)
|
1075
1203
|
unconnected = sc_gids[unconnected].tolist()
|
1076
1204
|
|
1077
1205
|
if unconnected:
|
1078
|
-
msg = (
|
1079
|
-
|
1080
|
-
|
1206
|
+
msg = (
|
1207
|
+
"{} supply curve points were not connected to tranmission! "
|
1208
|
+
"Unconnected sc_gid's: {}".format(
|
1209
|
+
len(unconnected), unconnected
|
1210
|
+
)
|
1211
|
+
)
|
1081
1212
|
logger.warning(msg)
|
1082
1213
|
warn(msg)
|
1083
1214
|
|
1084
|
-
supply_curve = self._sc_points.merge(
|
1215
|
+
supply_curve = self._sc_points.merge(
|
1216
|
+
connections, on=SupplyCurveField.SC_GID)
|
1085
1217
|
|
1086
1218
|
return supply_curve.reset_index(drop=True)
|
1087
1219
|
|
@@ -1090,42 +1222,61 @@ class SupplyCurve:
|
|
1090
1222
|
Add the transmission connection feature capacity to the trans table if
|
1091
1223
|
needed
|
1092
1224
|
"""
|
1093
|
-
if
|
1094
|
-
kwargs = {
|
1225
|
+
if "avail_cap" not in self._trans_table:
|
1226
|
+
kwargs = {"avail_cap_frac": avail_cap_frac}
|
1095
1227
|
fc = TF.feature_capacity(self._trans_table, **kwargs)
|
1096
|
-
self._trans_table = self._trans_table.merge(fc, on=
|
1228
|
+
self._trans_table = self._trans_table.merge(fc, on="trans_gid")
|
1097
1229
|
|
1098
1230
|
def _adjust_output_columns(self, columns, consider_friction):
|
1099
|
-
"""Add extra output columns, if needed.
|
1231
|
+
"""Add extra output columns, if needed."""
|
1100
1232
|
# These are essentially should-be-defaults that are not
|
1101
1233
|
# backwards-compatible, so have to explicitly check for them
|
1102
1234
|
extra_cols = ['ba_str', 'poi_lat', 'poi_lon', 'reinforcement_poi_lat',
|
1103
|
-
'reinforcement_poi_lon',
|
1235
|
+
'reinforcement_poi_lon', SupplyCurveField.EOS_MULT,
|
1236
|
+
SupplyCurveField.REG_MULT,
|
1104
1237
|
'reinforcement_cost_per_mw', 'reinforcement_dist_km',
|
1105
|
-
'n_parallel_trans',
|
1238
|
+
'n_parallel_trans', SupplyCurveField.TOTAL_LCOE_FRICTION]
|
1106
1239
|
if not consider_friction:
|
1107
|
-
extra_cols -= {
|
1240
|
+
extra_cols -= {SupplyCurveField.TOTAL_LCOE_FRICTION}
|
1108
1241
|
|
1109
|
-
extra_cols = [
|
1110
|
-
|
1242
|
+
extra_cols = [
|
1243
|
+
col
|
1244
|
+
for col in extra_cols
|
1245
|
+
if col in self._trans_table and col not in columns
|
1246
|
+
]
|
1111
1247
|
|
1112
1248
|
return columns + extra_cols
|
1113
1249
|
|
1114
1250
|
def _determine_sort_on(self, sort_on):
|
1115
1251
|
"""Determine the `sort_on` column from user input and trans table"""
|
1116
|
-
if
|
1252
|
+
if "reinforcement_cost_per_mw" in self._trans_table:
|
1117
1253
|
sort_on = sort_on or "lcoe_no_reinforcement"
|
1118
|
-
return sort_on or
|
1119
|
-
|
1120
|
-
def full_sort(
|
1121
|
-
|
1122
|
-
|
1123
|
-
|
1124
|
-
|
1125
|
-
|
1126
|
-
|
1127
|
-
|
1128
|
-
|
1254
|
+
return sort_on or "total_lcoe"
|
1255
|
+
|
1256
|
+
def full_sort(
|
1257
|
+
self,
|
1258
|
+
fcr,
|
1259
|
+
transmission_costs=None,
|
1260
|
+
avail_cap_frac=1,
|
1261
|
+
line_limited=False,
|
1262
|
+
connectable=True,
|
1263
|
+
max_workers=None,
|
1264
|
+
consider_friction=True,
|
1265
|
+
sort_on=None,
|
1266
|
+
columns=(
|
1267
|
+
"trans_gid",
|
1268
|
+
"trans_capacity",
|
1269
|
+
"trans_type",
|
1270
|
+
"trans_cap_cost_per_mw",
|
1271
|
+
"dist_km",
|
1272
|
+
"lcot",
|
1273
|
+
"total_lcoe",
|
1274
|
+
),
|
1275
|
+
wind_dirs=None,
|
1276
|
+
n_dirs=2,
|
1277
|
+
downwind=False,
|
1278
|
+
offshore_compete=False,
|
1279
|
+
):
|
1129
1280
|
"""
|
1130
1281
|
run full supply curve sorting
|
1131
1282
|
|
@@ -1180,14 +1331,17 @@ class SupplyCurve:
|
|
1180
1331
|
Updated sc_points table with transmission connections, LCOT
|
1181
1332
|
and LCOE+LCOT based on full supply curve connections
|
1182
1333
|
"""
|
1183
|
-
logger.info(
|
1334
|
+
logger.info("Starting full competitive supply curve sort.")
|
1184
1335
|
self._check_substation_conns(self._trans_table)
|
1185
|
-
self.compute_total_lcoe(
|
1186
|
-
|
1187
|
-
|
1188
|
-
|
1189
|
-
|
1190
|
-
|
1336
|
+
self.compute_total_lcoe(
|
1337
|
+
fcr,
|
1338
|
+
transmission_costs=transmission_costs,
|
1339
|
+
avail_cap_frac=avail_cap_frac,
|
1340
|
+
line_limited=line_limited,
|
1341
|
+
connectable=connectable,
|
1342
|
+
max_workers=max_workers,
|
1343
|
+
consider_friction=consider_friction,
|
1344
|
+
)
|
1191
1345
|
self._check_feature_capacity(avail_cap_frac=avail_cap_frac)
|
1192
1346
|
|
1193
1347
|
if isinstance(columns, tuple):
|
@@ -1197,12 +1351,14 @@ class SupplyCurve:
|
|
1197
1351
|
sort_on = self._determine_sort_on(sort_on)
|
1198
1352
|
|
1199
1353
|
trans_table = self._trans_table.copy()
|
1200
|
-
pos = trans_table[
|
1201
|
-
trans_table = trans_table.loc[~pos].sort_values([sort_on,
|
1354
|
+
pos = trans_table["lcot"].isnull()
|
1355
|
+
trans_table = trans_table.loc[~pos].sort_values([sort_on, "trans_gid"])
|
1202
1356
|
|
1203
1357
|
total_lcoe_fric = None
|
1204
|
-
|
1205
|
-
|
1358
|
+
col_in_table = SupplyCurveField.MEAN_LCOE_FRICTION in trans_table
|
1359
|
+
if consider_friction and col_in_table:
|
1360
|
+
total_lcoe_fric = \
|
1361
|
+
trans_table[SupplyCurveField.TOTAL_LCOE_FRICTION].values
|
1206
1362
|
|
1207
1363
|
comp_wind_dirs = None
|
1208
1364
|
if wind_dirs is not None:
|
@@ -1216,28 +1372,47 @@ class SupplyCurve:
|
|
1216
1372
|
|
1217
1373
|
msg += " windfarms"
|
1218
1374
|
logger.info(msg)
|
1219
|
-
comp_wind_dirs = CompetitiveWindFarms(
|
1220
|
-
|
1221
|
-
|
1222
|
-
|
1223
|
-
|
1224
|
-
|
1225
|
-
|
1226
|
-
|
1227
|
-
|
1228
|
-
|
1229
|
-
|
1230
|
-
|
1375
|
+
comp_wind_dirs = CompetitiveWindFarms(
|
1376
|
+
wind_dirs,
|
1377
|
+
self._sc_points,
|
1378
|
+
n_dirs=n_dirs,
|
1379
|
+
offshore=offshore_compete,
|
1380
|
+
)
|
1381
|
+
|
1382
|
+
supply_curve = self._full_sort(
|
1383
|
+
trans_table,
|
1384
|
+
trans_costs=transmission_costs,
|
1385
|
+
avail_cap_frac=avail_cap_frac,
|
1386
|
+
comp_wind_dirs=comp_wind_dirs,
|
1387
|
+
total_lcoe_fric=total_lcoe_fric,
|
1388
|
+
sort_on=sort_on,
|
1389
|
+
columns=columns,
|
1390
|
+
downwind=downwind,
|
1391
|
+
)
|
1231
1392
|
|
1232
1393
|
return supply_curve
|
1233
1394
|
|
1234
|
-
def simple_sort(
|
1235
|
-
|
1236
|
-
|
1237
|
-
|
1238
|
-
|
1239
|
-
|
1240
|
-
|
1395
|
+
def simple_sort(
|
1396
|
+
self,
|
1397
|
+
fcr,
|
1398
|
+
transmission_costs=None,
|
1399
|
+
avail_cap_frac=1,
|
1400
|
+
max_workers=None,
|
1401
|
+
consider_friction=True,
|
1402
|
+
sort_on=None,
|
1403
|
+
columns=(
|
1404
|
+
"trans_gid",
|
1405
|
+
"trans_type",
|
1406
|
+
"lcot",
|
1407
|
+
"total_lcoe",
|
1408
|
+
"dist_km",
|
1409
|
+
"trans_cap_cost_per_mw",
|
1410
|
+
),
|
1411
|
+
wind_dirs=None,
|
1412
|
+
n_dirs=2,
|
1413
|
+
downwind=False,
|
1414
|
+
offshore_compete=False,
|
1415
|
+
):
|
1241
1416
|
"""
|
1242
1417
|
Run simple supply curve sorting that does not take into account
|
1243
1418
|
available capacity
|
@@ -1292,12 +1467,15 @@ class SupplyCurve:
|
|
1292
1467
|
Updated sc_points table with transmission connections, LCOT
|
1293
1468
|
and LCOE+LCOT based on simple supply curve connections
|
1294
1469
|
"""
|
1295
|
-
logger.info(
|
1296
|
-
self.compute_total_lcoe(
|
1297
|
-
|
1298
|
-
|
1299
|
-
|
1300
|
-
|
1470
|
+
logger.info("Starting simple supply curve sort (no capacity limits).")
|
1471
|
+
self.compute_total_lcoe(
|
1472
|
+
fcr,
|
1473
|
+
transmission_costs=transmission_costs,
|
1474
|
+
avail_cap_frac=avail_cap_frac,
|
1475
|
+
connectable=False,
|
1476
|
+
max_workers=max_workers,
|
1477
|
+
consider_friction=consider_friction,
|
1478
|
+
)
|
1301
1479
|
trans_table = self._trans_table.copy()
|
1302
1480
|
|
1303
1481
|
if isinstance(columns, tuple):
|
@@ -1307,32 +1485,49 @@ class SupplyCurve:
|
|
1307
1485
|
sort_on = self._determine_sort_on(sort_on)
|
1308
1486
|
|
1309
1487
|
connections = trans_table.sort_values([sort_on, 'trans_gid'])
|
1310
|
-
connections = connections.groupby(
|
1488
|
+
connections = connections.groupby(SupplyCurveField.SC_GID).first()
|
1311
1489
|
rename = {'trans_gid': 'trans_gid',
|
1312
1490
|
'category': 'trans_type'}
|
1313
1491
|
connections = connections.rename(columns=rename)
|
1314
1492
|
connections = connections[columns].reset_index()
|
1315
1493
|
|
1316
|
-
supply_curve = self._sc_points.merge(connections,
|
1494
|
+
supply_curve = self._sc_points.merge(connections,
|
1495
|
+
on=SupplyCurveField.SC_GID)
|
1317
1496
|
if wind_dirs is not None:
|
1318
|
-
supply_curve =
|
1319
|
-
|
1320
|
-
|
1321
|
-
|
1322
|
-
|
1323
|
-
|
1324
|
-
|
1497
|
+
supply_curve = CompetitiveWindFarms.run(
|
1498
|
+
wind_dirs,
|
1499
|
+
supply_curve,
|
1500
|
+
n_dirs=n_dirs,
|
1501
|
+
offshore=offshore_compete,
|
1502
|
+
sort_on=sort_on,
|
1503
|
+
downwind=downwind,
|
1504
|
+
)
|
1325
1505
|
|
1326
1506
|
supply_curve = supply_curve.reset_index(drop=True)
|
1327
1507
|
|
1328
1508
|
return supply_curve
|
1329
1509
|
|
1330
|
-
def run(
|
1331
|
-
|
1332
|
-
|
1333
|
-
|
1334
|
-
|
1335
|
-
|
1510
|
+
def run(
|
1511
|
+
self,
|
1512
|
+
out_fpath,
|
1513
|
+
fixed_charge_rate,
|
1514
|
+
simple=True,
|
1515
|
+
avail_cap_frac=1,
|
1516
|
+
line_limited=False,
|
1517
|
+
transmission_costs=None,
|
1518
|
+
consider_friction=True,
|
1519
|
+
sort_on=None,
|
1520
|
+
columns=(
|
1521
|
+
"trans_gid",
|
1522
|
+
"trans_type",
|
1523
|
+
"trans_cap_cost_per_mw",
|
1524
|
+
"dist_km",
|
1525
|
+
"lcot",
|
1526
|
+
"total_lcoe",
|
1527
|
+
),
|
1528
|
+
max_workers=None,
|
1529
|
+
competition=None,
|
1530
|
+
):
|
1336
1531
|
"""Run Supply Curve Transmission calculations.
|
1337
1532
|
|
1338
1533
|
Run full supply curve taking into account available capacity of
|
@@ -1433,12 +1628,14 @@ class SupplyCurve:
|
|
1433
1628
|
str
|
1434
1629
|
Path to output supply curve.
|
1435
1630
|
"""
|
1436
|
-
kwargs = {
|
1437
|
-
|
1438
|
-
|
1439
|
-
|
1440
|
-
|
1441
|
-
|
1631
|
+
kwargs = {
|
1632
|
+
"fcr": fixed_charge_rate,
|
1633
|
+
"transmission_costs": transmission_costs,
|
1634
|
+
"consider_friction": consider_friction,
|
1635
|
+
"sort_on": sort_on,
|
1636
|
+
"columns": columns,
|
1637
|
+
"max_workers": max_workers,
|
1638
|
+
}
|
1442
1639
|
kwargs.update(competition or {})
|
1443
1640
|
|
1444
1641
|
if simple:
|
@@ -1457,7 +1654,7 @@ class SupplyCurve:
|
|
1457
1654
|
def _format_sc_out_fpath(out_fpath):
|
1458
1655
|
"""Add CSV file ending and replace underscore, if necessary."""
|
1459
1656
|
if not out_fpath.endswith(".csv"):
|
1460
|
-
out_fpath =
|
1657
|
+
out_fpath = "{}.csv".format(out_fpath)
|
1461
1658
|
|
1462
1659
|
project_dir, out_fn = os.path.split(out_fpath)
|
1463
1660
|
out_fn = out_fn.replace("supply_curve", "supply-curve")
|