NREL-reV 0.8.9__py3-none-any.whl → 0.9.2__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,22 +1116,22 @@ class SupplyCurve:
1063
1116
 
1064
1117
  return table
1065
1118
 
1066
- def _full_sort(
1119
+ def _full_sort( # noqa: C901
1067
1120
  self,
1068
1121
  trans_table,
1069
1122
  trans_costs=None,
1070
1123
  avail_cap_frac=1,
1071
1124
  comp_wind_dirs=None,
1072
1125
  total_lcoe_fric=None,
1073
- sort_on="total_lcoe",
1126
+ sort_on=SupplyCurveField.TOTAL_LCOE,
1074
1127
  columns=(
1075
- "trans_gid",
1076
- "trans_capacity",
1077
- "trans_type",
1078
- "trans_cap_cost_per_mw",
1079
- "dist_km",
1080
- "lcot",
1081
- "total_lcoe",
1128
+ SupplyCurveField.TRANS_GID,
1129
+ SupplyCurveField.TRANS_CAPACITY,
1130
+ SupplyCurveField.TRANS_TYPE,
1131
+ SupplyCurveField.TOTAL_TRANS_CAP_COST_PER_MW,
1132
+ SupplyCurveField.DIST_SPUR_KM,
1133
+ SupplyCurveField.LCOT,
1134
+ SupplyCurveField.TOTAL_LCOE,
1082
1135
  ),
1083
1136
  downwind=False,
1084
1137
  ):
@@ -1134,22 +1187,20 @@ class SupplyCurve:
1134
1187
  trans_sc_gids = trans_table[SupplyCurveField.SC_GID].values.astype(int)
1135
1188
 
1136
1189
  # 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)
1190
+ all_cols = list(columns)
1191
+ essentials = [SupplyCurveField.TRANS_GID,
1192
+ SupplyCurveField.TRANS_CAPACITY,
1193
+ SupplyCurveField.TRANS_TYPE,
1194
+ SupplyCurveField.DIST_SPUR_KM,
1195
+ SupplyCurveField.TOTAL_TRANS_CAP_COST_PER_MW,
1196
+ SupplyCurveField.LCOT,
1197
+ SupplyCurveField.TOTAL_LCOE]
1148
1198
 
1149
- arrays = {
1150
- final_key: trans_table[source_key].values
1151
- for final_key, source_key in all_cols.items()
1152
- }
1199
+ for col in essentials:
1200
+ if col not in all_cols:
1201
+ all_cols.append(col)
1202
+
1203
+ arrays = {col: trans_table[col].values for col in all_cols}
1153
1204
 
1154
1205
  sc_capacities = trans_table[self._sc_capacity_col].values
1155
1206
 
@@ -1159,7 +1210,7 @@ class SupplyCurve:
1159
1210
  sc_gid = trans_sc_gids[i]
1160
1211
  if self._mask[sc_gid]:
1161
1212
  connect = trans_features.connect(
1162
- arrays["trans_gid"][i], sc_capacities[i]
1213
+ arrays[SupplyCurveField.TRANS_GID][i], sc_capacities[i]
1163
1214
  )
1164
1215
  if connect:
1165
1216
  connected += 1
@@ -1222,36 +1273,50 @@ class SupplyCurve:
1222
1273
  Add the transmission connection feature capacity to the trans table if
1223
1274
  needed
1224
1275
  """
1225
- if "avail_cap" not in self._trans_table:
1276
+ if SupplyCurveField.TRANS_CAPACITY not in self._trans_table:
1226
1277
  kwargs = {"avail_cap_frac": avail_cap_frac}
1227
1278
  fc = TF.feature_capacity(self._trans_table, **kwargs)
1228
- self._trans_table = self._trans_table.merge(fc, on="trans_gid")
1279
+ self._trans_table = self._trans_table.merge(
1280
+ fc, on=SupplyCurveField.TRANS_GID)
1229
1281
 
1230
1282
  def _adjust_output_columns(self, columns, consider_friction):
1231
1283
  """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
1284
+
1285
+ for col in _REQUIRED_COMPUTE_AND_OUTPUT_COLS:
1286
+ if col not in columns:
1287
+ columns.append(col)
1288
+
1289
+ for col in _REQUIRED_OUTPUT_COLS:
1290
+ if col not in self._trans_table:
1291
+ self._trans_table[col] = np.nan
1292
+ if col not in columns:
1293
+ columns.append(col)
1294
+
1295
+ missing_cols = [col for col in columns if col not in self._trans_table]
1296
+ if missing_cols:
1297
+ msg = (f"The following requested columns are not found in "
1298
+ f"transmission table: {missing_cols}.\nSkipping...")
1299
+ logger.warning(msg)
1300
+ warn(msg)
1301
+
1302
+ columns = [col for col in columns if col in self._trans_table]
1303
+
1304
+ fric_col = SupplyCurveField.TOTAL_LCOE_FRICTION
1305
+ if consider_friction and fric_col in self._trans_table:
1306
+ columns.append(fric_col)
1307
+
1308
+ return sorted(columns, key=_column_sort_key)
1249
1309
 
1250
1310
  def _determine_sort_on(self, sort_on):
1251
1311
  """Determine the `sort_on` column from user input and trans table"""
1252
- if "reinforcement_cost_per_mw" in self._trans_table:
1312
+ r_cost_col = SupplyCurveField.REINFORCEMENT_COST_PER_MW
1313
+ found_reinforcement_costs = (
1314
+ r_cost_col in self._trans_table
1315
+ and not self._trans_table[r_cost_col].isna().all()
1316
+ )
1317
+ if found_reinforcement_costs:
1253
1318
  sort_on = sort_on or "lcoe_no_reinforcement"
1254
- return sort_on or "total_lcoe"
1319
+ return sort_on or SupplyCurveField.TOTAL_LCOE
1255
1320
 
1256
1321
  def full_sort(
1257
1322
  self,
@@ -1264,13 +1329,13 @@ class SupplyCurve:
1264
1329
  consider_friction=True,
1265
1330
  sort_on=None,
1266
1331
  columns=(
1267
- "trans_gid",
1268
- "trans_capacity",
1269
- "trans_type",
1270
- "trans_cap_cost_per_mw",
1271
- "dist_km",
1272
- "lcot",
1273
- "total_lcoe",
1332
+ SupplyCurveField.TRANS_GID,
1333
+ SupplyCurveField.TRANS_CAPACITY,
1334
+ SupplyCurveField.TRANS_TYPE,
1335
+ SupplyCurveField.TOTAL_TRANS_CAP_COST_PER_MW,
1336
+ SupplyCurveField.DIST_SPUR_KM,
1337
+ SupplyCurveField.LCOT,
1338
+ SupplyCurveField.TOTAL_LCOE,
1274
1339
  ),
1275
1340
  wind_dirs=None,
1276
1341
  n_dirs=2,
@@ -1351,8 +1416,10 @@ class SupplyCurve:
1351
1416
  sort_on = self._determine_sort_on(sort_on)
1352
1417
 
1353
1418
  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"])
1419
+ pos = trans_table[SupplyCurveField.LCOT].isnull()
1420
+ trans_table = trans_table.loc[~pos].sort_values(
1421
+ [sort_on, SupplyCurveField.TRANS_GID]
1422
+ )
1356
1423
 
1357
1424
  total_lcoe_fric = None
1358
1425
  col_in_table = SupplyCurveField.MEAN_LCOE_FRICTION in trans_table
@@ -1400,14 +1467,7 @@ class SupplyCurve:
1400
1467
  max_workers=None,
1401
1468
  consider_friction=True,
1402
1469
  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
- ),
1470
+ columns=DEFAULT_COLUMNS,
1411
1471
  wind_dirs=None,
1412
1472
  n_dirs=2,
1413
1473
  downwind=False,
@@ -1446,9 +1506,8 @@ class SupplyCurve:
1446
1506
  will be built first, by default `None`, which will use
1447
1507
  total LCOE without any reinforcement costs as the sort value.
1448
1508
  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')
1509
+ Columns to preserve in output connections dataframe.
1510
+ By default, :obj:`DEFAULT_COLUMNS`.
1452
1511
  wind_dirs : pandas.DataFrame | str, optional
1453
1512
  path to .csv or reVX.wind_dirs.wind_dirs.WindDirs output with
1454
1513
  the neighboring supply curve point gids and power-rose value at
@@ -1476,19 +1535,16 @@ class SupplyCurve:
1476
1535
  max_workers=max_workers,
1477
1536
  consider_friction=consider_friction,
1478
1537
  )
1479
- trans_table = self._trans_table.copy()
1538
+ sort_on = self._determine_sort_on(sort_on)
1480
1539
 
1481
1540
  if isinstance(columns, tuple):
1482
1541
  columns = list(columns)
1483
-
1484
1542
  columns = self._adjust_output_columns(columns, consider_friction)
1485
- sort_on = self._determine_sort_on(sort_on)
1486
1543
 
1487
- connections = trans_table.sort_values([sort_on, 'trans_gid'])
1544
+ trans_table = self._trans_table.copy()
1545
+ connections = trans_table.sort_values(
1546
+ [sort_on, SupplyCurveField.TRANS_GID])
1488
1547
  connections = connections.groupby(SupplyCurveField.SC_GID).first()
1489
- rename = {'trans_gid': 'trans_gid',
1490
- 'category': 'trans_type'}
1491
- connections = connections.rename(columns=rename)
1492
1548
  connections = connections[columns].reset_index()
1493
1549
 
1494
1550
  supply_curve = self._sc_points.merge(connections,
@@ -1517,14 +1573,7 @@ class SupplyCurve:
1517
1573
  transmission_costs=None,
1518
1574
  consider_friction=True,
1519
1575
  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
- ),
1576
+ columns=DEFAULT_COLUMNS,
1528
1577
  max_workers=None,
1529
1578
  competition=None,
1530
1579
  ):
@@ -1592,8 +1641,7 @@ class SupplyCurve:
1592
1641
  By default ``None``.
1593
1642
  columns : list | tuple, optional
1594
1643
  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')``.
1644
+ By default, :obj:`DEFAULT_COLUMNS`.
1597
1645
  max_workers : int, optional
1598
1646
  Number of workers to use to compute LCOT. If > 1,
1599
1647
  computation is run in parallel. If ``None``, computation
@@ -1659,3 +1707,14 @@ def _format_sc_out_fpath(out_fpath):
1659
1707
  project_dir, out_fn = os.path.split(out_fpath)
1660
1708
  out_fn = out_fn.replace("supply_curve", "supply-curve")
1661
1709
  return os.path.join(project_dir, out_fn)
1710
+
1711
+
1712
+ def _column_sort_key(col):
1713
+ """Determine the sort order of the input column. """
1714
+ col_value = _REQUIRED_COMPUTE_AND_OUTPUT_COLS.get(col)
1715
+ if col_value is None:
1716
+ col_value = _REQUIRED_OUTPUT_COLS.get(col)
1717
+ if col_value is None:
1718
+ col_value = 1e6
1719
+
1720
+ return col_value, str(col)