NREL-reV 0.8.9__py3-none-any.whl → 0.9.0__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.
@@ -7,6 +7,7 @@ reV supply curve module
7
7
  import json
8
8
  import logging
9
9
  import os
10
+ from itertools import chain
10
11
  from copy import deepcopy
11
12
  from warnings import warn
12
13
 
@@ -24,11 +25,38 @@ from reV.utilities.exceptions import SupplyCurveError, SupplyCurveInputError
24
25
  logger = logging.getLogger(__name__)
25
26
 
26
27
 
28
+ # map is column name to relative order in which it should appear in output file
29
+ _REQUIRED_COMPUTE_AND_OUTPUT_COLS = {
30
+ SupplyCurveField.TRANS_GID: 0,
31
+ SupplyCurveField.TRANS_TYPE: 1,
32
+ SupplyCurveField.N_PARALLEL_TRANS: 2,
33
+ SupplyCurveField.DIST_SPUR_KM: 3,
34
+ SupplyCurveField.TOTAL_TRANS_CAP_COST_PER_MW: 10,
35
+ SupplyCurveField.LCOT: 11,
36
+ SupplyCurveField.TOTAL_LCOE: 12,
37
+ }
38
+ _REQUIRED_OUTPUT_COLS = {SupplyCurveField.DIST_EXPORT_KM: 4,
39
+ SupplyCurveField.REINFORCEMENT_DIST_KM: 5,
40
+ SupplyCurveField.TIE_LINE_COST_PER_MW: 6,
41
+ SupplyCurveField.CONNECTION_COST_PER_MW: 7,
42
+ SupplyCurveField.EXPORT_COST_PER_MW: 8,
43
+ SupplyCurveField.REINFORCEMENT_COST_PER_MW: 9,
44
+ SupplyCurveField.POI_LAT: 13,
45
+ SupplyCurveField.POI_LON: 14,
46
+ SupplyCurveField.REINFORCEMENT_POI_LAT: 15,
47
+ SupplyCurveField.REINFORCEMENT_POI_LON: 16}
48
+ DEFAULT_COLUMNS = tuple(str(field)
49
+ for field in chain(_REQUIRED_COMPUTE_AND_OUTPUT_COLS,
50
+ _REQUIRED_OUTPUT_COLS))
51
+ """Default output columns from supply chain computation (not ordered)"""
52
+
53
+
27
54
  class SupplyCurve:
28
55
  """SupplyCurve"""
29
56
 
30
57
  def __init__(self, sc_points, trans_table, sc_features=None,
31
- sc_capacity_col=SupplyCurveField.CAPACITY):
58
+ # str() to fix docs
59
+ sc_capacity_col=str(SupplyCurveField.CAPACITY_AC_MW)):
32
60
  """ReV LCOT calculation and SupplyCurve sorting class.
33
61
 
34
62
  ``reV`` supply curve computes the transmission costs associated
@@ -282,15 +310,19 @@ class SupplyCurve:
282
310
  # also xformer_cost_p_mw -> xformer_cost_per_mw (not sure why there
283
311
  # would be a *_p_mw but here we are...)
284
312
  rename_map = {
285
- "trans_line_gid": "trans_gid",
313
+ "trans_line_gid": SupplyCurveField.TRANS_GID,
286
314
  "trans_gids": "trans_line_gids",
287
315
  "xformer_cost_p_mw": "xformer_cost_per_mw",
288
316
  }
289
317
  trans_table = trans_table.rename(columns=rename_map)
290
318
 
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
319
+ contains_dist_in_miles = "dist_mi" in trans_table
320
+ missing_km_dist = SupplyCurveField.DIST_SPUR_KM not in trans_table
321
+ if contains_dist_in_miles and missing_km_dist:
322
+ trans_table = trans_table.rename(
323
+ columns={"dist_mi": SupplyCurveField.DIST_SPUR_KM}
324
+ )
325
+ trans_table[SupplyCurveField.DIST_SPUR_KM] *= 1.60934
294
326
 
295
327
  drop_cols = [SupplyCurveField.SC_GID, 'cap_left',
296
328
  SupplyCurveField.SC_POINT_GID]
@@ -302,7 +334,7 @@ class SupplyCurve:
302
334
 
303
335
  @staticmethod
304
336
  def _map_trans_capacity(trans_sc_table,
305
- sc_capacity_col=SupplyCurveField.CAPACITY):
337
+ sc_capacity_col=SupplyCurveField.CAPACITY_AC_MW):
306
338
  """
307
339
  Map SC gids to transmission features based on capacity. For any SC
308
340
  gids with capacity > the maximum transmission feature capacity, map
@@ -331,7 +363,7 @@ class SupplyCurve:
331
363
 
332
364
  nx = trans_sc_table[sc_capacity_col] / trans_sc_table["max_cap"]
333
365
  nx = np.ceil(nx).astype(int)
334
- trans_sc_table["n_parallel_trans"] = nx
366
+ trans_sc_table[SupplyCurveField.N_PARALLEL_TRANS] = nx
335
367
 
336
368
  if (nx > 1).any():
337
369
  mask = nx > 1
@@ -409,11 +441,12 @@ class SupplyCurve:
409
441
  """
410
442
  features = features.rename(
411
443
  columns={
412
- "trans_line_gid": "trans_gid",
444
+ "trans_line_gid": SupplyCurveField.TRANS_GID,
413
445
  "trans_gids": "trans_line_gids",
414
446
  }
415
447
  )
416
- mask = features["category"].str.lower() == "substation"
448
+ mask = (features[SupplyCurveField.TRANS_TYPE].str.casefold()
449
+ == "substation")
417
450
 
418
451
  if not any(mask):
419
452
  return []
@@ -424,7 +457,7 @@ class SupplyCurve:
424
457
 
425
458
  line_gids = np.unique(np.concatenate(line_gids.values))
426
459
 
427
- test = np.isin(line_gids, features["trans_gid"].values)
460
+ test = np.isin(line_gids, features[SupplyCurveField.TRANS_GID].values)
428
461
 
429
462
  return line_gids[~test].tolist()
430
463
 
@@ -513,10 +546,11 @@ class SupplyCurve:
513
546
  @classmethod
514
547
  def _merge_sc_trans_tables(cls, sc_points, trans_table,
515
548
  sc_cols=(SupplyCurveField.SC_GID,
516
- SupplyCurveField.CAPACITY,
517
- SupplyCurveField.MEAN_CF,
549
+ SupplyCurveField.CAPACITY_AC_MW,
550
+ SupplyCurveField.MEAN_CF_AC,
518
551
  SupplyCurveField.MEAN_LCOE),
519
- sc_capacity_col=SupplyCurveField.CAPACITY):
552
+ sc_capacity_col=SupplyCurveField.CAPACITY_AC_MW
553
+ ):
520
554
  """
521
555
  Merge the supply curve table with the transmission features table.
522
556
 
@@ -583,11 +617,13 @@ class SupplyCurve:
583
617
  if isinstance(sc_cols, tuple):
584
618
  sc_cols = list(sc_cols)
585
619
 
586
- if SupplyCurveField.MEAN_LCOE_FRICTION in sc_points:
587
- sc_cols.append(SupplyCurveField.MEAN_LCOE_FRICTION)
588
-
589
- if "transmission_multiplier" in sc_points:
590
- sc_cols.append("transmission_multiplier")
620
+ extra_cols = [SupplyCurveField.CAPACITY_DC_MW,
621
+ SupplyCurveField.MEAN_CF_DC,
622
+ SupplyCurveField.MEAN_LCOE_FRICTION,
623
+ "transmission_multiplier"]
624
+ for col in extra_cols:
625
+ if col in sc_points:
626
+ sc_cols.append(col)
591
627
 
592
628
  sc_cols += merge_cols
593
629
  sc_points = sc_points[sc_cols].copy()
@@ -600,10 +636,10 @@ class SupplyCurve:
600
636
  @classmethod
601
637
  def _map_tables(cls, sc_points, trans_table,
602
638
  sc_cols=(SupplyCurveField.SC_GID,
603
- SupplyCurveField.CAPACITY,
604
- SupplyCurveField.MEAN_CF,
639
+ SupplyCurveField.CAPACITY_AC_MW,
640
+ SupplyCurveField.MEAN_CF_AC,
605
641
  SupplyCurveField.MEAN_LCOE),
606
- sc_capacity_col=SupplyCurveField.CAPACITY):
642
+ sc_capacity_col=SupplyCurveField.CAPACITY_AC_MW):
607
643
  """
608
644
  Map supply curve points to transmission features
609
645
 
@@ -618,8 +654,9 @@ class SupplyCurve:
618
654
  sc_cols : tuple | list, optional
619
655
  List of column from sc_points to transfer into the trans table,
620
656
  If the `sc_capacity_col` is not included, it will get added.
621
- by default (SupplyCurveField.SC_GID, SupplyCurveField.CAPACITY,
622
- SupplyCurveField.MEAN_CF, SupplyCurveField.MEAN_LCOE)
657
+ by default (SupplyCurveField.SC_GID,
658
+ SupplyCurveField.CAPACITY_AC_MW, SupplyCurveField.MEAN_CF_AC,
659
+ SupplyCurveField.MEAN_LCOE)
623
660
  sc_capacity_col : str, optional
624
661
  Name of capacity column in `trans_sc_table`. The values in
625
662
  this column determine the size of transmission lines built.
@@ -646,9 +683,9 @@ class SupplyCurve:
646
683
  trans_sc_table, sc_capacity_col=scc
647
684
  )
648
685
 
649
- trans_sc_table = \
650
- trans_sc_table.sort_values(
651
- [SupplyCurveField.SC_GID, 'trans_gid']).reset_index(drop=True)
686
+ sort_cols = [SupplyCurveField.SC_GID, SupplyCurveField.TRANS_GID]
687
+ trans_sc_table = trans_sc_table.sort_values(sort_cols)
688
+ trans_sc_table = trans_sc_table.reset_index(drop=True)
652
689
 
653
690
  cls._check_sc_trans_table(sc_points, trans_sc_table)
654
691
 
@@ -718,7 +755,7 @@ class SupplyCurve:
718
755
 
719
756
  @staticmethod
720
757
  def _get_capacity(sc_gid, sc_table, connectable=True,
721
- sc_capacity_col=SupplyCurveField.CAPACITY):
758
+ sc_capacity_col=SupplyCurveField.CAPACITY_AC_MW):
722
759
  """
723
760
  Get capacity of supply curve point
724
761
 
@@ -767,7 +804,8 @@ class SupplyCurve:
767
804
  def _compute_trans_cap_cost(cls, trans_table, trans_costs=None,
768
805
  avail_cap_frac=1, max_workers=None,
769
806
  connectable=True, line_limited=False,
770
- sc_capacity_col=SupplyCurveField.CAPACITY):
807
+ sc_capacity_col=(
808
+ SupplyCurveField.CAPACITY_AC_MW)):
771
809
  """
772
810
  Compute levelized cost of transmission for all combinations of
773
811
  supply curve points and tranmission features in trans_table
@@ -919,7 +957,10 @@ class SupplyCurve:
919
957
  Flag to consider friction layer on LCOE when "mean_lcoe_friction"
920
958
  is in the sc points input, by default True
921
959
  """
922
- if "trans_cap_cost" not in self._trans_table:
960
+ tcc_per_mw_col = SupplyCurveField.TOTAL_TRANS_CAP_COST_PER_MW
961
+ if tcc_per_mw_col in self._trans_table:
962
+ cost = self._trans_table[tcc_per_mw_col].values.copy()
963
+ elif "trans_cap_cost" not in self._trans_table:
923
964
  scc = self._sc_capacity_col
924
965
  cost = self._compute_trans_cap_cost(
925
966
  self._trans_table,
@@ -930,39 +971,51 @@ class SupplyCurve:
930
971
  max_workers=max_workers,
931
972
  sc_capacity_col=scc,
932
973
  )
933
- self._trans_table["trans_cap_cost_per_mw"] = cost # $/MW
974
+ self._trans_table[tcc_per_mw_col] = cost # $/MW
934
975
  else:
935
976
  cost = self._trans_table["trans_cap_cost"].values.copy() # $
936
- cost /= self._trans_table[self._sc_capacity_col] # $/MW
937
- self._trans_table["trans_cap_cost_per_mw"] = cost
938
-
939
- cost *= self._trans_table[self._sc_capacity_col]
940
- # align with "mean_cf"
941
- cost /= self._trans_table[SupplyCurveField.CAPACITY]
942
-
943
- if 'reinforcement_cost_per_mw' in self._trans_table:
944
- logger.info("'reinforcement_cost_per_mw' column found in "
945
- "transmission table. Adding reinforcement costs "
946
- "to total LCOE.")
947
- cf_mean_arr = self._trans_table[SupplyCurveField.MEAN_CF].values
948
- lcot = (cost * fcr) / (cf_mean_arr * 8760)
949
- lcoe = lcot + self._trans_table[SupplyCurveField.MEAN_LCOE]
950
- self._trans_table['lcot_no_reinforcement'] = lcot
951
- self._trans_table['lcoe_no_reinforcement'] = lcoe
952
- r_cost = (self._trans_table['reinforcement_cost_per_mw']
953
- .values.copy())
954
- r_cost *= self._trans_table[self._sc_capacity_col]
955
- # align with "mean_cf"
956
- r_cost /= self._trans_table[SupplyCurveField.CAPACITY]
977
+ cost /= self._trans_table[SupplyCurveField.CAPACITY_AC_MW] # $/MW
978
+ self._trans_table[tcc_per_mw_col] = cost
979
+
980
+ self._trans_table[tcc_per_mw_col] = (
981
+ self._trans_table[tcc_per_mw_col].astype("float32")
982
+ )
983
+ cost = cost.astype("float32")
984
+ cf_mean_arr = self._trans_table[SupplyCurveField.MEAN_CF_AC]
985
+ cf_mean_arr = cf_mean_arr.values.astype("float32")
986
+ resource_lcoe = self._trans_table[SupplyCurveField.MEAN_LCOE]
987
+ resource_lcoe = resource_lcoe.values.astype("float32")
988
+
989
+ if 'reinforcement_cost_floored_per_mw' in self._trans_table:
990
+ logger.info("'reinforcement_cost_floored_per_mw' column found in "
991
+ "transmission table. Adding floored reinforcement "
992
+ "cost LCOE as sorting option.")
993
+ fr_cost = (self._trans_table['reinforcement_cost_floored_per_mw']
994
+ .values.copy())
995
+
996
+ lcot_fr = ((cost + fr_cost) * fcr) / (cf_mean_arr * 8760)
997
+ lcoe_fr = lcot_fr + resource_lcoe
998
+ self._trans_table['lcot_floored_reinforcement'] = lcot_fr
999
+ self._trans_table['lcoe_floored_reinforcement'] = lcoe_fr
1000
+
1001
+ if SupplyCurveField.REINFORCEMENT_COST_PER_MW in self._trans_table:
1002
+ logger.info("%s column found in transmission table. Adding "
1003
+ "reinforcement costs to total LCOE.",
1004
+ SupplyCurveField.REINFORCEMENT_COST_PER_MW)
1005
+ lcot_nr = (cost * fcr) / (cf_mean_arr * 8760)
1006
+ lcoe_nr = lcot_nr + resource_lcoe
1007
+ self._trans_table['lcot_no_reinforcement'] = lcot_nr
1008
+ self._trans_table['lcoe_no_reinforcement'] = lcoe_nr
1009
+
1010
+ col_name = SupplyCurveField.REINFORCEMENT_COST_PER_MW
1011
+ r_cost = self._trans_table[col_name].astype("float32")
1012
+ r_cost = r_cost.values.copy()
1013
+ self._trans_table[tcc_per_mw_col] += r_cost
957
1014
  cost += r_cost # $/MW
958
1015
 
959
- cf_mean_arr = self._trans_table[SupplyCurveField.MEAN_CF].values
960
1016
  lcot = (cost * fcr) / (cf_mean_arr * 8760)
961
-
962
- self._trans_table['lcot'] = lcot
963
- self._trans_table['total_lcoe'] = (
964
- self._trans_table['lcot']
965
- + self._trans_table[SupplyCurveField.MEAN_LCOE])
1017
+ self._trans_table[SupplyCurveField.LCOT] = lcot
1018
+ self._trans_table[SupplyCurveField.TOTAL_LCOE] = lcot + resource_lcoe
966
1019
 
967
1020
  if consider_friction:
968
1021
  self._calculate_total_lcoe_friction()
@@ -973,7 +1026,7 @@ class SupplyCurve:
973
1026
 
974
1027
  if SupplyCurveField.MEAN_LCOE_FRICTION in self._trans_table:
975
1028
  lcoe_friction = (
976
- self._trans_table['lcot']
1029
+ self._trans_table[SupplyCurveField.LCOT]
977
1030
  + self._trans_table[SupplyCurveField.MEAN_LCOE_FRICTION])
978
1031
  self._trans_table[SupplyCurveField.TOTAL_LCOE_FRICTION] = (
979
1032
  lcoe_friction
@@ -1063,6 +1116,7 @@ class SupplyCurve:
1063
1116
 
1064
1117
  return table
1065
1118
 
1119
+ # pylint: disable=C901
1066
1120
  def _full_sort(
1067
1121
  self,
1068
1122
  trans_table,
@@ -1070,15 +1124,15 @@ class SupplyCurve:
1070
1124
  avail_cap_frac=1,
1071
1125
  comp_wind_dirs=None,
1072
1126
  total_lcoe_fric=None,
1073
- sort_on="total_lcoe",
1127
+ sort_on=SupplyCurveField.TOTAL_LCOE,
1074
1128
  columns=(
1075
- "trans_gid",
1076
- "trans_capacity",
1077
- "trans_type",
1078
- "trans_cap_cost_per_mw",
1079
- "dist_km",
1080
- "lcot",
1081
- "total_lcoe",
1129
+ SupplyCurveField.TRANS_GID,
1130
+ SupplyCurveField.TRANS_CAPACITY,
1131
+ SupplyCurveField.TRANS_TYPE,
1132
+ SupplyCurveField.TOTAL_TRANS_CAP_COST_PER_MW,
1133
+ SupplyCurveField.DIST_SPUR_KM,
1134
+ SupplyCurveField.LCOT,
1135
+ SupplyCurveField.TOTAL_LCOE,
1082
1136
  ),
1083
1137
  downwind=False,
1084
1138
  ):
@@ -1134,22 +1188,20 @@ class SupplyCurve:
1134
1188
  trans_sc_gids = trans_table[SupplyCurveField.SC_GID].values.astype(int)
1135
1189
 
1136
1190
  # syntax is final_key: source_key (source from trans_table)
1137
- all_cols = {k: k for k in columns}
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
- }
1147
- all_cols.update(essentials)
1191
+ all_cols = list(columns)
1192
+ essentials = [SupplyCurveField.TRANS_GID,
1193
+ SupplyCurveField.TRANS_CAPACITY,
1194
+ SupplyCurveField.TRANS_TYPE,
1195
+ SupplyCurveField.DIST_SPUR_KM,
1196
+ SupplyCurveField.TOTAL_TRANS_CAP_COST_PER_MW,
1197
+ SupplyCurveField.LCOT,
1198
+ SupplyCurveField.TOTAL_LCOE]
1148
1199
 
1149
- arrays = {
1150
- final_key: trans_table[source_key].values
1151
- for final_key, source_key in all_cols.items()
1152
- }
1200
+ for col in essentials:
1201
+ if col not in all_cols:
1202
+ all_cols.append(col)
1203
+
1204
+ arrays = {col: trans_table[col].values for col in all_cols}
1153
1205
 
1154
1206
  sc_capacities = trans_table[self._sc_capacity_col].values
1155
1207
 
@@ -1159,7 +1211,7 @@ class SupplyCurve:
1159
1211
  sc_gid = trans_sc_gids[i]
1160
1212
  if self._mask[sc_gid]:
1161
1213
  connect = trans_features.connect(
1162
- arrays["trans_gid"][i], sc_capacities[i]
1214
+ arrays[SupplyCurveField.TRANS_GID][i], sc_capacities[i]
1163
1215
  )
1164
1216
  if connect:
1165
1217
  connected += 1
@@ -1222,36 +1274,50 @@ class SupplyCurve:
1222
1274
  Add the transmission connection feature capacity to the trans table if
1223
1275
  needed
1224
1276
  """
1225
- if "avail_cap" not in self._trans_table:
1277
+ if SupplyCurveField.TRANS_CAPACITY not in self._trans_table:
1226
1278
  kwargs = {"avail_cap_frac": avail_cap_frac}
1227
1279
  fc = TF.feature_capacity(self._trans_table, **kwargs)
1228
- self._trans_table = self._trans_table.merge(fc, on="trans_gid")
1280
+ self._trans_table = self._trans_table.merge(
1281
+ fc, on=SupplyCurveField.TRANS_GID)
1229
1282
 
1230
1283
  def _adjust_output_columns(self, columns, consider_friction):
1231
1284
  """Add extra output columns, if needed."""
1232
- # These are essentially should-be-defaults that are not
1233
- # backwards-compatible, so have to explicitly check for them
1234
- extra_cols = ['ba_str', 'poi_lat', 'poi_lon', 'reinforcement_poi_lat',
1235
- 'reinforcement_poi_lon', SupplyCurveField.EOS_MULT,
1236
- SupplyCurveField.REG_MULT,
1237
- 'reinforcement_cost_per_mw', 'reinforcement_dist_km',
1238
- 'n_parallel_trans', SupplyCurveField.TOTAL_LCOE_FRICTION]
1239
- if not consider_friction:
1240
- extra_cols -= {SupplyCurveField.TOTAL_LCOE_FRICTION}
1241
-
1242
- extra_cols = [
1243
- col
1244
- for col in extra_cols
1245
- if col in self._trans_table and col not in columns
1246
- ]
1247
-
1248
- return columns + extra_cols
1285
+
1286
+ for col in _REQUIRED_COMPUTE_AND_OUTPUT_COLS:
1287
+ if col not in columns:
1288
+ columns.append(col)
1289
+
1290
+ for col in _REQUIRED_OUTPUT_COLS:
1291
+ if col not in self._trans_table:
1292
+ self._trans_table[col] = None
1293
+ if col not in columns:
1294
+ columns.append(col)
1295
+
1296
+ missing_cols = [col for col in columns if col not in self._trans_table]
1297
+ if missing_cols:
1298
+ msg = (f"The following requested columns are not found in "
1299
+ f"transmission table: {missing_cols}.\nSkipping...")
1300
+ logger.warning(msg)
1301
+ warn(msg)
1302
+
1303
+ columns = [col for col in columns if col in self._trans_table]
1304
+
1305
+ fric_col = SupplyCurveField.TOTAL_LCOE_FRICTION
1306
+ if consider_friction and fric_col in self._trans_table:
1307
+ columns.append(fric_col)
1308
+
1309
+ return sorted(columns, key=_column_sort_key)
1249
1310
 
1250
1311
  def _determine_sort_on(self, sort_on):
1251
1312
  """Determine the `sort_on` column from user input and trans table"""
1252
- if "reinforcement_cost_per_mw" in self._trans_table:
1313
+ r_cost_col = SupplyCurveField.REINFORCEMENT_COST_PER_MW
1314
+ found_reinforcement_costs = (
1315
+ r_cost_col in self._trans_table
1316
+ and not self._trans_table[r_cost_col].isna().all()
1317
+ )
1318
+ if found_reinforcement_costs:
1253
1319
  sort_on = sort_on or "lcoe_no_reinforcement"
1254
- return sort_on or "total_lcoe"
1320
+ return sort_on or SupplyCurveField.TOTAL_LCOE
1255
1321
 
1256
1322
  def full_sort(
1257
1323
  self,
@@ -1264,13 +1330,13 @@ class SupplyCurve:
1264
1330
  consider_friction=True,
1265
1331
  sort_on=None,
1266
1332
  columns=(
1267
- "trans_gid",
1268
- "trans_capacity",
1269
- "trans_type",
1270
- "trans_cap_cost_per_mw",
1271
- "dist_km",
1272
- "lcot",
1273
- "total_lcoe",
1333
+ SupplyCurveField.TRANS_GID,
1334
+ SupplyCurveField.TRANS_CAPACITY,
1335
+ SupplyCurveField.TRANS_TYPE,
1336
+ SupplyCurveField.TOTAL_TRANS_CAP_COST_PER_MW,
1337
+ SupplyCurveField.DIST_SPUR_KM,
1338
+ SupplyCurveField.LCOT,
1339
+ SupplyCurveField.TOTAL_LCOE,
1274
1340
  ),
1275
1341
  wind_dirs=None,
1276
1342
  n_dirs=2,
@@ -1351,8 +1417,10 @@ class SupplyCurve:
1351
1417
  sort_on = self._determine_sort_on(sort_on)
1352
1418
 
1353
1419
  trans_table = self._trans_table.copy()
1354
- pos = trans_table["lcot"].isnull()
1355
- trans_table = trans_table.loc[~pos].sort_values([sort_on, "trans_gid"])
1420
+ pos = trans_table[SupplyCurveField.LCOT].isnull()
1421
+ trans_table = trans_table.loc[~pos].sort_values(
1422
+ [sort_on, SupplyCurveField.TRANS_GID]
1423
+ )
1356
1424
 
1357
1425
  total_lcoe_fric = None
1358
1426
  col_in_table = SupplyCurveField.MEAN_LCOE_FRICTION in trans_table
@@ -1400,14 +1468,7 @@ class SupplyCurve:
1400
1468
  max_workers=None,
1401
1469
  consider_friction=True,
1402
1470
  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
- ),
1471
+ columns=DEFAULT_COLUMNS,
1411
1472
  wind_dirs=None,
1412
1473
  n_dirs=2,
1413
1474
  downwind=False,
@@ -1446,9 +1507,8 @@ class SupplyCurve:
1446
1507
  will be built first, by default `None`, which will use
1447
1508
  total LCOE without any reinforcement costs as the sort value.
1448
1509
  columns : list | tuple, optional
1449
- Columns to preserve in output connections dataframe,
1450
- by default ('trans_gid', 'trans_capacity', 'trans_type',
1451
- 'trans_cap_cost_per_mw', 'dist_km', 'lcot', 'total_lcoe')
1510
+ Columns to preserve in output connections dataframe.
1511
+ By default, :obj:`DEFAULT_COLUMNS`.
1452
1512
  wind_dirs : pandas.DataFrame | str, optional
1453
1513
  path to .csv or reVX.wind_dirs.wind_dirs.WindDirs output with
1454
1514
  the neighboring supply curve point gids and power-rose value at
@@ -1476,19 +1536,16 @@ class SupplyCurve:
1476
1536
  max_workers=max_workers,
1477
1537
  consider_friction=consider_friction,
1478
1538
  )
1479
- trans_table = self._trans_table.copy()
1539
+ sort_on = self._determine_sort_on(sort_on)
1480
1540
 
1481
1541
  if isinstance(columns, tuple):
1482
1542
  columns = list(columns)
1483
-
1484
1543
  columns = self._adjust_output_columns(columns, consider_friction)
1485
- sort_on = self._determine_sort_on(sort_on)
1486
1544
 
1487
- connections = trans_table.sort_values([sort_on, 'trans_gid'])
1545
+ trans_table = self._trans_table.copy()
1546
+ connections = trans_table.sort_values(
1547
+ [sort_on, SupplyCurveField.TRANS_GID])
1488
1548
  connections = connections.groupby(SupplyCurveField.SC_GID).first()
1489
- rename = {'trans_gid': 'trans_gid',
1490
- 'category': 'trans_type'}
1491
- connections = connections.rename(columns=rename)
1492
1549
  connections = connections[columns].reset_index()
1493
1550
 
1494
1551
  supply_curve = self._sc_points.merge(connections,
@@ -1517,14 +1574,7 @@ class SupplyCurve:
1517
1574
  transmission_costs=None,
1518
1575
  consider_friction=True,
1519
1576
  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
- ),
1577
+ columns=DEFAULT_COLUMNS,
1528
1578
  max_workers=None,
1529
1579
  competition=None,
1530
1580
  ):
@@ -1592,8 +1642,7 @@ class SupplyCurve:
1592
1642
  By default ``None``.
1593
1643
  columns : list | tuple, optional
1594
1644
  Columns to preserve in output supply curve dataframe.
1595
- By default, ``('trans_gid', 'trans_type',
1596
- 'trans_cap_cost_per_mw', 'dist_km', 'lcot', 'total_lcoe')``.
1645
+ By default, :obj:`DEFAULT_COLUMNS`.
1597
1646
  max_workers : int, optional
1598
1647
  Number of workers to use to compute LCOT. If > 1,
1599
1648
  computation is run in parallel. If ``None``, computation
@@ -1659,3 +1708,14 @@ def _format_sc_out_fpath(out_fpath):
1659
1708
  project_dir, out_fn = os.path.split(out_fpath)
1660
1709
  out_fn = out_fn.replace("supply_curve", "supply-curve")
1661
1710
  return os.path.join(project_dir, out_fn)
1711
+
1712
+
1713
+ def _column_sort_key(col):
1714
+ """Determine the sort order of the input column. """
1715
+ col_value = _REQUIRED_COMPUTE_AND_OUTPUT_COLS.get(col)
1716
+ if col_value is None:
1717
+ col_value = _REQUIRED_OUTPUT_COLS.get(col)
1718
+ if col_value is None:
1719
+ col_value = 1e6
1720
+
1721
+ return col_value, str(col)