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.
Files changed (38) hide show
  1. {NREL_reV-0.8.7.dist-info → NREL_reV-0.8.9.dist-info}/METADATA +12 -10
  2. {NREL_reV-0.8.7.dist-info → NREL_reV-0.8.9.dist-info}/RECORD +38 -38
  3. {NREL_reV-0.8.7.dist-info → NREL_reV-0.8.9.dist-info}/WHEEL +1 -1
  4. reV/SAM/SAM.py +182 -133
  5. reV/SAM/econ.py +18 -14
  6. reV/SAM/generation.py +608 -419
  7. reV/SAM/windbos.py +93 -79
  8. reV/bespoke/bespoke.py +690 -445
  9. reV/bespoke/place_turbines.py +6 -6
  10. reV/config/project_points.py +220 -140
  11. reV/econ/econ.py +165 -113
  12. reV/econ/economies_of_scale.py +57 -34
  13. reV/generation/base.py +310 -183
  14. reV/generation/generation.py +298 -190
  15. reV/handlers/exclusions.py +16 -15
  16. reV/handlers/multi_year.py +12 -9
  17. reV/handlers/outputs.py +6 -5
  18. reV/hybrids/hybrid_methods.py +28 -30
  19. reV/hybrids/hybrids.py +304 -188
  20. reV/nrwal/nrwal.py +262 -168
  21. reV/qa_qc/cli_qa_qc.py +14 -10
  22. reV/qa_qc/qa_qc.py +217 -119
  23. reV/qa_qc/summary.py +228 -146
  24. reV/rep_profiles/rep_profiles.py +349 -230
  25. reV/supply_curve/aggregation.py +349 -188
  26. reV/supply_curve/competitive_wind_farms.py +90 -48
  27. reV/supply_curve/exclusions.py +138 -85
  28. reV/supply_curve/extent.py +75 -50
  29. reV/supply_curve/points.py +536 -309
  30. reV/supply_curve/sc_aggregation.py +366 -225
  31. reV/supply_curve/supply_curve.py +505 -308
  32. reV/supply_curve/tech_mapping.py +144 -82
  33. reV/utilities/__init__.py +199 -16
  34. reV/utilities/pytest_utils.py +8 -4
  35. reV/version.py +1 -1
  36. {NREL_reV-0.8.7.dist-info → NREL_reV-0.8.9.dist-info}/LICENSE +0 -0
  37. {NREL_reV-0.8.7.dist-info → NREL_reV-0.8.9.dist-info}/entry_points.txt +0 -0
  38. {NREL_reV-0.8.7.dist-info → NREL_reV-0.8.9.dist-info}/top_level.txt +0 -0
@@ -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
- import pandas as pd
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.exceptions import SupplyCurveInputError, SupplyCurveError
19
- from reV.utilities import log_versions
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='capacity'):
33
- """reV LCOT calculation and SupplyCurve sorting class.
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('Supply curve points input: {}'.format(sc_points))
131
- logger.info('Transmission table input: {}'.format(trans_table))
132
- logger.info('Supply curve capacity column: {}'.format(sc_capacity_col))
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(sc_points,
136
- sc_features=sc_features)
137
- self._trans_table = self._map_tables(self._sc_points, trans_table,
138
- sc_capacity_col=sc_capacity_col)
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('.h5'):
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 = 'sc_gid'
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('Supply curve points table imported with columns: {}'
189
- .format(sc_points.columns.values.tolist()))
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
- merge_cols = [c for c in sc_features
194
- if c in sc_points]
195
- sc_points = sc_points.merge(sc_features, on=merge_cols, how='left')
196
- logger.debug('Adding Supply Curve Features table with columns: {}'
197
- .format(sc_features.columns.values.tolist()))
198
-
199
- if 'transmission_multiplier' in sc_points:
200
- col = 'transmission_multiplier'
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('Final supply curve points table has columns: {}'
204
- .format(sc_points.columns.values.tolist()))
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('sc_')]
226
- trans_columns = [c for c in trans_columns if c.startswith('sc_')]
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 ['row', 'col']:
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 = ('Did not find a unique set of sc row and column ids to '
236
- 'merge on: {}'.format(merge_cols))
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 = {'trans_line_gid': 'trans_gid',
270
- 'trans_gids': 'trans_line_gids',
271
- 'xformer_cost_p_mw': 'xformer_cost_per_mw'}
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 'dist_mi' in trans_table and 'dist_km' not in trans_table:
275
- trans_table = trans_table.rename(columns={'dist_mi': 'dist_km'})
276
- trans_table['dist_km'] *= 1.60934
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 = ['sc_gid', 'cap_left', 'sc_point_gid']
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, sc_capacity_col='capacity'):
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['max_cap']
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['n_parallel_trans'] = nx
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 = (trans_sc_table.loc[mask, 'tie_line_cost']
320
- * nx[mask])
321
-
322
- xformer_cost = (trans_sc_table.loc[mask, 'xformer_cost_per_mw']
323
- * trans_sc_table.loc[mask, 'max_cap'] * nx[mask])
324
-
325
- conn_cost = (xformer_cost
326
- + trans_sc_table.loc[mask, 'sub_upgrade_cost']
327
- + trans_sc_table.loc[mask, 'new_sub_cost'])
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, 'tie_line_cost'] = tie_line_cost
332
- trans_sc_table.loc[mask, 'xformer_cost'] = xformer_cost
333
- trans_sc_table.loc[mask, 'connection_cost'] = conn_cost
334
- trans_sc_table.loc[mask, 'trans_cap_cost'] = trans_cap_cost
335
-
336
- msg = ("{} SC points have a capacity that exceeds the maximum "
337
- "transmission feature capacity and will be connected with "
338
- "multiple parallel transmission features."
339
- .format((nx > 1).sum()))
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(columns={'trans_line_gid': 'trans_gid',
383
- 'trans_gids': 'trans_line_gids'})
384
- mask = features['category'].str.lower() == 'substation'
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 = (features.loc[mask, 'trans_line_gids']
390
- .apply(cls._parse_trans_line_gids))
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['trans_gid'].values)
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, sc_cols='sc_gid'):
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 'sc_gid'
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 = ('The following sc_gid (keys) were connected to substations '
422
- 'but were not connected to the respective transmission line'
423
- ' gids (values) which is required for full SC sort: {}'
424
- .format(missing))
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['sc_gid'].unique())
441
- trans_sc_gids = set(trans_table['sc_gid'].unique())
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 = ("There are {} Supply Curve points with missing "
445
- "transmission mappings. Supply curve points with no "
446
- "transmission features will not be connected! "
447
- "Missing sc_gid's: {}"
448
- .format(len(missing), missing))
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 = ('Merging of sc points table and transmission features '
454
- 'table failed with {} original sc gids and {} transmission '
455
- 'sc gids after table merge.'
456
- .format(len(sc_gids), len(trans_sc_gids)))
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('There are {} original SC gids and {} sc gids in the '
461
- 'merged transmission table.'
462
- .format(len(sc_gids), len(trans_sc_gids)))
463
- logger.debug('Transmission Table created with columns: {}'
464
- .format(trans_table.columns.values.tolist()))
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=('sc_gid', 'capacity', 'mean_cf',
469
- 'mean_lcoe'),
470
- sc_capacity_col='capacity'):
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 ('sc_gid', 'capacity', 'mean_cf', 'mean_lcoe')
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(cls._merge_sc_trans_tables(
509
- sc_points, table, sc_cols=sc_cols,
510
- sc_capacity_col=sc_capacity_col))
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(sc_points.columns,
517
- trans_table.columns)
518
- logger.info('Merging SC table and Trans Table with '
519
- '{} mapping: {}'
520
- .format('sc_table_col: trans_table_col', merge_cols))
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 'mean_lcoe_friction' in sc_points:
528
- sc_cols.append('mean_lcoe_friction')
586
+ if SupplyCurveField.MEAN_LCOE_FRICTION in sc_points:
587
+ sc_cols.append(SupplyCurveField.MEAN_LCOE_FRICTION)
529
588
 
530
- if 'transmission_multiplier' in sc_points:
531
- sc_cols.append('transmission_multiplier')
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(sc_points, on=merge_cols,
536
- how='inner')
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=('sc_gid', 'capacity', 'mean_cf', 'mean_lcoe'),
543
- sc_capacity_col='capacity'):
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 ('sc_gid', 'capacity', 'mean_cf', 'mean_lcoe')
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(sc_points, trans_table,
577
- sc_cols=sc_cols,
578
- sc_capacity_col=scc)
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 'max_cap' in trans_sc_table:
581
- trans_sc_table = cls._map_trans_capacity(trans_sc_table,
582
- sc_capacity_col=scc)
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
- ['sc_gid', 'trans_gid']).reset_index(drop=True)
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(trans_table, avail_cap_frac=avail_cap_frac,
623
- **kwargs)
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='sc_gid'):
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='capacity'):
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 = ('Each supply curve point should only have '
690
- 'a single capacity, but {} has {}'
691
- .format(sc_gid, capacity))
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='capacity'):
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('Supply curve table must have '
753
- 'supply curve point capacity column'
754
- '({}) to compute lcot'.format(scc))
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('sc_gid')
834
+ groups = trans_table.groupby(SupplyCurveField.SC_GID)
766
835
  if max_workers > 1:
767
- loggers = [__name__, 'reV.handlers.transmission', 'reV']
768
- with SpawnProcessPool(max_workers=max_workers,
769
- loggers=loggers) as exe:
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(sc_gid, sc_table,
773
- connectable=connectable,
774
- sc_capacity_col=scc)
775
- futures.append(exe.submit(TC.feature_costs, sc_table,
776
- capacity=capacity,
777
- avail_cap_frac=avail_cap_frac,
778
- line_limited=line_limited,
779
- **trans_costs))
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(sc_gid, sc_table,
786
- connectable=connectable,
787
- sc_capacity_col=scc)
788
- cost.append(TC.feature_costs(sc_table,
789
- capacity=capacity,
790
- avail_cap_frac=avail_cap_frac,
791
- line_limited=line_limited,
792
- **trans_costs))
793
-
794
- cost = np.hstack(cost).astype('float32')
795
- logger.info('LCOT cost calculation is complete.')
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(self, fcr, transmission_costs=None,
800
- avail_cap_frac=1, line_limited=False,
801
- connectable=True, max_workers=None,
802
- consider_friction=True):
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 'trans_cap_cost' not in self._trans_table:
922
+ if "trans_cap_cost" not in self._trans_table:
832
923
  scc = self._sc_capacity_col
833
- cost = self._compute_trans_cap_cost(self._trans_table,
834
- trans_costs=transmission_costs,
835
- avail_cap_frac=avail_cap_frac,
836
- line_limited=line_limited,
837
- connectable=connectable,
838
- max_workers=max_workers,
839
- sc_capacity_col=scc)
840
- self._trans_table['trans_cap_cost_per_mw'] = cost # $/MW
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['trans_cap_cost'].values.copy() # $
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['trans_cap_cost_per_mw'] = cost
937
+ self._trans_table["trans_cap_cost_per_mw"] = cost
845
938
 
846
939
  cost *= self._trans_table[self._sc_capacity_col]
847
- cost /= self._trans_table['capacity'] # align with "mean_cf"
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['mean_cf'].values
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['mean_lcoe']
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
- r_cost /= self._trans_table['capacity'] # align with "mean_cf"
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['mean_cf'].values
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'] = (self._trans_table['lcot']
869
- + self._trans_table['mean_lcoe'])
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 'mean_lcoe_friction' in self._trans_table:
879
- lcoe_friction = (self._trans_table['lcot']
880
- + self._trans_table['mean_lcoe_friction'])
881
- self._trans_table['total_lcoe_friction'] = lcoe_friction
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(self, comp_wind_dirs, sc_gid,
886
- downwind=False):
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['upwind', gid]
1008
+ exclude_gids = comp_wind_dirs["upwind", gid]
909
1009
  if downwind:
910
- exclude_gids = np.append(exclude_gids,
911
- comp_wind_dirs['downwind', gid])
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['sc_gid', n]
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('Excluding sc_gid {}'
919
- .format(sc_id))
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('Could not make sum column "{}", missing: {}'
949
- .format(new_label, missing))
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(self, trans_table, trans_costs=None,
962
- avail_cap_frac=1, comp_wind_dirs=None,
963
- total_lcoe_fric=None, sort_on='total_lcoe',
964
- columns=('trans_gid', 'trans_capacity', 'trans_type',
965
- 'trans_cap_cost_per_mw', 'dist_km', 'lcot',
966
- 'total_lcoe'),
967
- downwind=False):
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(self._trans_table,
1006
- trans_costs=trans_costs,
1007
- avail_cap_frac=avail_cap_frac)
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['sc_gid'].values.astype(int)
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 = {'trans_gid': 'trans_gid',
1020
- 'trans_capacity': 'avail_cap',
1021
- 'trans_type': 'category',
1022
- 'dist_km': 'dist_km',
1023
- 'trans_cap_cost_per_mw': 'trans_cap_cost_per_mw',
1024
- 'lcot': 'lcot',
1025
- 'total_lcoe': 'total_lcoe',
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 = {final_key: trans_table[source_key].values
1030
- for final_key, source_key in all_cols.items()}
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(arrays['trans_gid'][i],
1040
- sc_capacities[i])
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('Connecting sc gid {}'.format(sc_gid))
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
- conn_lists['total_lcoe_friction'][sc_gid] = \
1051
- total_lcoe_fric[i]
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('{} % of supply curve points connected'
1057
- .format(progress))
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 = 'sc_gid'
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['sc_gid'].values
1071
- connected = connections['sc_gid'].values
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 = ("{} supply curve points were not connected to tranmission! "
1079
- "Unconnected sc_gid's: {}"
1080
- .format(len(unconnected), unconnected))
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(connections, on='sc_gid')
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 'avail_cap' not in self._trans_table:
1094
- kwargs = {'avail_cap_frac': avail_cap_frac}
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='trans_gid')
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', 'eos_mult', 'reg_mult',
1235
+ 'reinforcement_poi_lon', SupplyCurveField.EOS_MULT,
1236
+ SupplyCurveField.REG_MULT,
1104
1237
  'reinforcement_cost_per_mw', 'reinforcement_dist_km',
1105
- 'n_parallel_trans', 'total_lcoe_friction']
1238
+ 'n_parallel_trans', SupplyCurveField.TOTAL_LCOE_FRICTION]
1106
1239
  if not consider_friction:
1107
- extra_cols -= {'total_lcoe_friction'}
1240
+ extra_cols -= {SupplyCurveField.TOTAL_LCOE_FRICTION}
1108
1241
 
1109
- extra_cols = [col for col in extra_cols
1110
- if col in self._trans_table and col not in columns]
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 'reinforcement_cost_per_mw' in self._trans_table:
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 'total_lcoe'
1119
-
1120
- def full_sort(self, fcr, transmission_costs=None,
1121
- avail_cap_frac=1, line_limited=False,
1122
- connectable=True, max_workers=None,
1123
- consider_friction=True, sort_on=None,
1124
- columns=('trans_gid', 'trans_capacity', 'trans_type',
1125
- 'trans_cap_cost_per_mw', 'dist_km', 'lcot',
1126
- 'total_lcoe'),
1127
- wind_dirs=None, n_dirs=2, downwind=False,
1128
- offshore_compete=False):
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('Starting full competitive supply curve sort.')
1334
+ logger.info("Starting full competitive supply curve sort.")
1184
1335
  self._check_substation_conns(self._trans_table)
1185
- self.compute_total_lcoe(fcr, transmission_costs=transmission_costs,
1186
- avail_cap_frac=avail_cap_frac,
1187
- line_limited=line_limited,
1188
- connectable=connectable,
1189
- max_workers=max_workers,
1190
- consider_friction=consider_friction)
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['lcot'].isnull()
1201
- trans_table = trans_table.loc[~pos].sort_values([sort_on, 'trans_gid'])
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
- if consider_friction and 'mean_lcoe_friction' in trans_table:
1205
- total_lcoe_fric = trans_table['total_lcoe_friction'].values
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(wind_dirs,
1220
- self._sc_points,
1221
- n_dirs=n_dirs,
1222
- offshore=offshore_compete)
1223
-
1224
- supply_curve = self._full_sort(trans_table,
1225
- trans_costs=transmission_costs,
1226
- avail_cap_frac=avail_cap_frac,
1227
- comp_wind_dirs=comp_wind_dirs,
1228
- total_lcoe_fric=total_lcoe_fric,
1229
- sort_on=sort_on, columns=columns,
1230
- downwind=downwind)
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(self, fcr, transmission_costs=None,
1235
- avail_cap_frac=1, max_workers=None,
1236
- consider_friction=True, sort_on=None,
1237
- columns=('trans_gid', 'trans_type', 'lcot', 'total_lcoe',
1238
- 'dist_km', 'trans_cap_cost_per_mw'),
1239
- wind_dirs=None, n_dirs=2, downwind=False,
1240
- offshore_compete=False):
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('Starting simple supply curve sort (no capacity limits).')
1296
- self.compute_total_lcoe(fcr, transmission_costs=transmission_costs,
1297
- avail_cap_frac=avail_cap_frac,
1298
- connectable=False,
1299
- max_workers=max_workers,
1300
- consider_friction=consider_friction)
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('sc_gid').first()
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, on='sc_gid')
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
- CompetitiveWindFarms.run(wind_dirs,
1320
- supply_curve,
1321
- n_dirs=n_dirs,
1322
- offshore=offshore_compete,
1323
- sort_on=sort_on,
1324
- downwind=downwind)
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(self, out_fpath, fixed_charge_rate, simple=True, avail_cap_frac=1,
1331
- line_limited=False, transmission_costs=None,
1332
- consider_friction=True, sort_on=None,
1333
- columns=('trans_gid', 'trans_type', 'trans_cap_cost_per_mw',
1334
- 'dist_km', 'lcot', 'total_lcoe'),
1335
- max_workers=None, competition=None):
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 = {"fcr": fixed_charge_rate,
1437
- "transmission_costs": transmission_costs,
1438
- "consider_friction": consider_friction,
1439
- "sort_on": sort_on,
1440
- "columns": columns,
1441
- "max_workers": max_workers}
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 = '{}.csv'.format(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")