NREL-reV 0.8.7__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.
Files changed (43) hide show
  1. {NREL_reV-0.8.7.dist-info → NREL_reV-0.9.0.dist-info}/METADATA +13 -10
  2. {NREL_reV-0.8.7.dist-info → NREL_reV-0.9.0.dist-info}/RECORD +43 -43
  3. {NREL_reV-0.8.7.dist-info → NREL_reV-0.9.0.dist-info}/WHEEL +1 -1
  4. reV/SAM/SAM.py +217 -133
  5. reV/SAM/econ.py +18 -14
  6. reV/SAM/generation.py +611 -422
  7. reV/SAM/windbos.py +93 -79
  8. reV/bespoke/bespoke.py +681 -377
  9. reV/bespoke/cli_bespoke.py +2 -0
  10. reV/bespoke/place_turbines.py +187 -43
  11. reV/config/output_request.py +2 -1
  12. reV/config/project_points.py +218 -140
  13. reV/econ/econ.py +166 -114
  14. reV/econ/economies_of_scale.py +91 -45
  15. reV/generation/base.py +331 -184
  16. reV/generation/generation.py +326 -200
  17. reV/generation/output_attributes/lcoe_fcr_inputs.json +38 -3
  18. reV/handlers/__init__.py +0 -1
  19. reV/handlers/exclusions.py +16 -15
  20. reV/handlers/multi_year.py +57 -26
  21. reV/handlers/outputs.py +6 -5
  22. reV/handlers/transmission.py +44 -27
  23. reV/hybrids/hybrid_methods.py +30 -30
  24. reV/hybrids/hybrids.py +305 -189
  25. reV/nrwal/nrwal.py +262 -168
  26. reV/qa_qc/cli_qa_qc.py +14 -10
  27. reV/qa_qc/qa_qc.py +217 -119
  28. reV/qa_qc/summary.py +228 -146
  29. reV/rep_profiles/rep_profiles.py +349 -230
  30. reV/supply_curve/aggregation.py +349 -188
  31. reV/supply_curve/competitive_wind_farms.py +90 -48
  32. reV/supply_curve/exclusions.py +138 -85
  33. reV/supply_curve/extent.py +75 -50
  34. reV/supply_curve/points.py +735 -390
  35. reV/supply_curve/sc_aggregation.py +357 -248
  36. reV/supply_curve/supply_curve.py +604 -347
  37. reV/supply_curve/tech_mapping.py +144 -82
  38. reV/utilities/__init__.py +274 -16
  39. reV/utilities/pytest_utils.py +8 -4
  40. reV/version.py +1 -1
  41. {NREL_reV-0.8.7.dist-info → NREL_reV-0.9.0.dist-info}/LICENSE +0 -0
  42. {NREL_reV-0.8.7.dist-info → NREL_reV-0.9.0.dist-info}/entry_points.txt +0 -0
  43. {NREL_reV-0.8.7.dist-info → NREL_reV-0.9.0.dist-info}/top_level.txt +0 -0
@@ -2,26 +2,30 @@
2
2
  """
3
3
  reV supply curve points frameworks.
4
4
  """
5
- from abc import ABC
5
+
6
6
  import logging
7
+ from abc import ABC
8
+ from warnings import warn
9
+
7
10
  import numpy as np
8
11
  import pandas as pd
9
- from warnings import warn
12
+ from rex.multi_time_resource import MultiTimeResource
13
+ from rex.resource import BaseResource, Resource
14
+ from rex.utilities.utilities import jsonify_dict
10
15
 
11
16
  from reV.econ.economies_of_scale import EconomiesOfScale
12
17
  from reV.econ.utilities import lcoe_fcr
13
- from reV.handlers.exclusions import ExclusionLayers
18
+ from reV.handlers.exclusions import LATITUDE, LONGITUDE, ExclusionLayers
14
19
  from reV.supply_curve.exclusions import ExclusionMask, ExclusionMaskFromDict
15
- from reV.utilities.exceptions import (SupplyCurveInputError,
16
- EmptySupplyCurvePointError,
17
- InputWarning,
18
- FileInputError,
19
- DataShapeError,
20
- OutputWarning)
21
-
22
- from rex.resource import Resource, BaseResource
23
- from rex.multi_time_resource import MultiTimeResource
24
- from rex.utilities.utilities import jsonify_dict
20
+ from reV.utilities import ResourceMetaField, SupplyCurveField
21
+ from reV.utilities.exceptions import (
22
+ DataShapeError,
23
+ EmptySupplyCurvePointError,
24
+ FileInputError,
25
+ InputWarning,
26
+ OutputWarning,
27
+ SupplyCurveInputError,
28
+ )
25
29
 
26
30
  logger = logging.getLogger(__name__)
27
31
 
@@ -47,8 +51,9 @@ class AbstractSupplyCurvePoint(ABC):
47
51
 
48
52
  self._gid = gid
49
53
  self._resolution = resolution
50
- self._rows, self._cols = self._parse_slices(
51
- gid, resolution, exclusion_shape)
54
+ self._rows = self._cols = self._sc_row_ind = self._sc_col_ind = None
55
+ self._parse_sc_row_col_ind(resolution, exclusion_shape)
56
+ self._parse_slices(resolution, exclusion_shape)
52
57
 
53
58
  @staticmethod
54
59
  def _ordered_unique(seq):
@@ -69,36 +74,37 @@ class AbstractSupplyCurvePoint(ABC):
69
74
 
70
75
  return [x for x in seq if not (x in seen or seen.add(x))]
71
76
 
72
- def _parse_slices(self, gid, resolution, exclusion_shape):
73
- """Parse inputs for the definition of this SC point.
77
+ def _parse_sc_row_col_ind(self, resolution, exclusion_shape):
78
+ """Parse SC row and column index.
74
79
 
75
80
  Parameters
76
81
  ----------
77
- gid : int | None
78
- gid for supply curve point to analyze.
79
82
  resolution : int | None
80
83
  SC resolution, must be input in combination with gid.
81
84
  exclusion_shape : tuple
82
- Shape of the exclusions extent (rows, cols). Inputing this will
83
- speed things up considerably.
84
-
85
- Returns
86
- -------
87
- rows : slice
88
- Row slice to index the high-res layer (exclusions) for the gid in
89
- the agg layer (supply curve).
90
- cols : slice
91
- Col slice to index the high-res layer (exclusions) for the gid in
92
- the agg layer (supply curve).
85
+ Shape of the exclusions extent (rows, cols).
93
86
  """
87
+ n_sc_cols = int(np.ceil(exclusion_shape[1] / resolution))
94
88
 
95
- rows, cols = self.get_agg_slices(gid, exclusion_shape, resolution)
89
+ self._sc_row_ind = self._gid // n_sc_cols
90
+ self._sc_col_ind = self._gid % n_sc_cols
96
91
 
97
- return rows, cols
92
+ def _parse_slices(self, resolution, exclusion_shape):
93
+ """Parse row and column resource/generation grid slices.
94
+
95
+ Parameters
96
+ ----------
97
+ resolution : int | None
98
+ SC resolution, must be input in combination with gid.
99
+ exclusion_shape : tuple
100
+ Shape of the exclusions extent (rows, cols).
101
+ """
102
+ inds = self.get_agg_slices(self._gid, exclusion_shape, resolution)
103
+ self._rows, self._cols = inds
98
104
 
99
105
  @property
100
106
  def gid(self):
101
- """supply curve point gid"""
107
+ """Supply curve point gid"""
102
108
  return self._gid
103
109
 
104
110
  @property
@@ -112,6 +118,16 @@ class AbstractSupplyCurvePoint(ABC):
112
118
  """
113
119
  return self._gid
114
120
 
121
+ @property
122
+ def sc_row_ind(self):
123
+ """int: Supply curve row index"""
124
+ return self._sc_row_ind
125
+
126
+ @property
127
+ def sc_col_ind(self):
128
+ """int: Supply curve column index"""
129
+ return self._sc_col_ind
130
+
115
131
  @property
116
132
  def resolution(self):
117
133
  """Get the supply curve grid aggregation resolution"""
@@ -172,8 +188,10 @@ class AbstractSupplyCurvePoint(ABC):
172
188
  row = loc[0][0]
173
189
  col = loc[1][0]
174
190
  except IndexError as exc:
175
- msg = ('Gid {} out of bounds for extent shape {} and '
176
- 'resolution {}.'.format(gid, shape, resolution))
191
+ msg = (
192
+ "Gid {} out of bounds for extent shape {} and "
193
+ "resolution {}.".format(gid, shape, resolution)
194
+ )
177
195
  raise IndexError(msg) from exc
178
196
 
179
197
  if row + 1 != nrows:
@@ -192,9 +210,18 @@ class AbstractSupplyCurvePoint(ABC):
192
210
  class SupplyCurvePoint(AbstractSupplyCurvePoint):
193
211
  """Generic single SC point based on exclusions, resolution, and techmap"""
194
212
 
195
- def __init__(self, gid, excl, tm_dset, excl_dict=None, inclusion_mask=None,
196
- resolution=64, excl_area=None, exclusion_shape=None,
197
- close=True):
213
+ def __init__(
214
+ self,
215
+ gid,
216
+ excl,
217
+ tm_dset,
218
+ excl_dict=None,
219
+ inclusion_mask=None,
220
+ resolution=64,
221
+ excl_area=None,
222
+ exclusion_shape=None,
223
+ close=True,
224
+ ):
198
225
  """
199
226
  Parameters
200
227
  ----------
@@ -245,8 +272,10 @@ class SupplyCurvePoint(AbstractSupplyCurvePoint):
245
272
  self._incl_mask = inclusion_mask
246
273
  self._incl_mask_flat = None
247
274
  if inclusion_mask is not None:
248
- msg = ('Bad inclusion mask input shape of {} with stated '
249
- 'resolution of {}'.format(inclusion_mask.shape, resolution))
275
+ msg = (
276
+ "Bad inclusion mask input shape of {} with stated "
277
+ "resolution of {}".format(inclusion_mask.shape, resolution)
278
+ )
250
279
  assert len(inclusion_mask.shape) == 2, msg
251
280
  assert inclusion_mask.shape[0] <= resolution, msg
252
281
  assert inclusion_mask.shape[1] <= resolution, msg
@@ -282,11 +311,12 @@ class SupplyCurvePoint(AbstractSupplyCurvePoint):
282
311
  excl_fpath = excl.excl_h5.h5_file
283
312
  exclusions = excl
284
313
  else:
285
- raise SupplyCurveInputError('SupplyCurvePoints needs an '
286
- 'exclusions file path, or '
287
- 'ExclusionMask handler but '
288
- 'received: {}'
289
- .format(type(excl)))
314
+ raise SupplyCurveInputError(
315
+ "SupplyCurvePoints needs an "
316
+ "exclusions file path, or "
317
+ "ExclusionMask handler but "
318
+ "received: {}".format(type(excl))
319
+ )
290
320
 
291
321
  return excl_fpath, exclusions
292
322
 
@@ -312,9 +342,12 @@ class SupplyCurvePoint(AbstractSupplyCurvePoint):
312
342
  res_gids = res_gids.astype(np.int32).flatten()
313
343
 
314
344
  if (res_gids != -1).sum() == 0:
315
- emsg = ('Supply curve point gid {} has no viable exclusion points '
316
- 'based on exclusions file: "{}"'
317
- .format(self._gid, self._excl_fpath))
345
+ emsg = (
346
+ "Supply curve point gid {} has no viable exclusion points "
347
+ 'based on exclusions file: "{}"'.format(
348
+ self._gid, self._excl_fpath
349
+ )
350
+ )
318
351
  raise EmptySupplyCurvePointError(emsg)
319
352
 
320
353
  return res_gids
@@ -343,8 +376,9 @@ class SupplyCurvePoint(AbstractSupplyCurvePoint):
343
376
  ExclusionMask h5 handler object.
344
377
  """
345
378
  if self._excls is None:
346
- self._excls = ExclusionMaskFromDict(self._excl_fpath,
347
- layers_dict=self._excl_dict)
379
+ self._excls = ExclusionMaskFromDict(
380
+ self._excl_fpath, layers_dict=self._excl_dict
381
+ )
348
382
 
349
383
  return self._excls
350
384
 
@@ -359,8 +393,8 @@ class SupplyCurvePoint(AbstractSupplyCurvePoint):
359
393
  """
360
394
 
361
395
  if self._centroid is None:
362
- lats = self.exclusions.excl_h5['latitude', self.rows, self.cols]
363
- lons = self.exclusions.excl_h5['longitude', self.rows, self.cols]
396
+ lats = self.exclusions.excl_h5[LATITUDE, self.rows, self.cols]
397
+ lons = self.exclusions.excl_h5[LONGITUDE, self.rows, self.cols]
364
398
  self._centroid = (lats.mean(), lons.mean())
365
399
 
366
400
  return self._centroid
@@ -438,8 +472,12 @@ class SupplyCurvePoint(AbstractSupplyCurvePoint):
438
472
  self._incl_mask[out_of_extent] = 0.0
439
473
 
440
474
  if self._incl_mask.max() > 1:
441
- w = ('Exclusions data max value is > 1: {}'
442
- .format(self._incl_mask.max()), InputWarning)
475
+ w = (
476
+ "Exclusions data max value is > 1: {}".format(
477
+ self._incl_mask.max()
478
+ ),
479
+ InputWarning,
480
+ )
443
481
  logger.warning(w)
444
482
  warn(w)
445
483
 
@@ -505,8 +543,9 @@ class SupplyCurvePoint(AbstractSupplyCurvePoint):
505
543
  """
506
544
 
507
545
  if all(self.include_mask_flat[self.bool_mask] == 0):
508
- msg = ('Supply curve point gid {} is completely excluded!'
509
- .format(self._gid))
546
+ msg = "Supply curve point gid {} is completely excluded!".format(
547
+ self._gid
548
+ )
510
549
  raise EmptySupplyCurvePointError(msg)
511
550
 
512
551
  def exclusion_weighted_mean(self, arr, drop_nan=True):
@@ -530,18 +569,18 @@ class SupplyCurvePoint(AbstractSupplyCurvePoint):
530
569
  """
531
570
 
532
571
  if len(arr.shape) == 2:
533
- x = arr[:, self._gids[self.bool_mask]].astype('float32')
572
+ x = arr[:, self._gids[self.bool_mask]].astype("float32")
534
573
  incl = self.include_mask_flat[self.bool_mask]
535
574
  x *= incl
536
575
  mean = x.sum(axis=1) / incl.sum()
537
576
 
538
577
  else:
539
- x = arr[self._gids[self.bool_mask]].astype('float32')
578
+ x = arr[self._gids[self.bool_mask]].astype("float32")
540
579
  incl = self.include_mask_flat[self.bool_mask]
541
580
 
542
581
  if np.isnan(x).all():
543
582
  return np.nan
544
- elif drop_nan and np.isnan(x).any():
583
+ if drop_nan and np.isnan(x).any():
545
584
  nan_mask = np.isnan(x)
546
585
  x = x[~nan_mask]
547
586
  incl = incl[~nan_mask]
@@ -600,10 +639,10 @@ class SupplyCurvePoint(AbstractSupplyCurvePoint):
600
639
  Sum of arr masked by the binary exclusions
601
640
  """
602
641
  if len(arr.shape) == 2:
603
- x = arr[:, self._gids[self.bool_mask]].astype('float32')
642
+ x = arr[:, self._gids[self.bool_mask]].astype("float32")
604
643
  ax = 1
605
644
  else:
606
- x = arr[self._gids[self.bool_mask]].astype('float32')
645
+ x = arr[self._gids[self.bool_mask]].astype("float32")
607
646
  ax = 0
608
647
 
609
648
  x *= self.include_mask_flat[self.bool_mask]
@@ -612,8 +651,17 @@ class SupplyCurvePoint(AbstractSupplyCurvePoint):
612
651
  return agg
613
652
 
614
653
  @classmethod
615
- def sc_mean(cls, gid, excl, tm_dset, data, excl_dict=None, resolution=64,
616
- exclusion_shape=None, close=True):
654
+ def sc_mean(
655
+ cls,
656
+ gid,
657
+ excl,
658
+ tm_dset,
659
+ data,
660
+ excl_dict=None,
661
+ resolution=64,
662
+ exclusion_shape=None,
663
+ close=True,
664
+ ):
617
665
  """
618
666
  Compute exclusions weight mean for the sc point from data
619
667
 
@@ -649,16 +697,29 @@ class SupplyCurvePoint(AbstractSupplyCurvePoint):
649
697
  ndarray
650
698
  Exclusions weighted means of data for supply curve point
651
699
  """
652
- kwargs = {"excl_dict": excl_dict, "resolution": resolution,
653
- "exclusion_shape": exclusion_shape, "close": close}
700
+ kwargs = {
701
+ "excl_dict": excl_dict,
702
+ "resolution": resolution,
703
+ "exclusion_shape": exclusion_shape,
704
+ "close": close,
705
+ }
654
706
  with cls(gid, excl, tm_dset, **kwargs) as point:
655
707
  means = point.exclusion_weighted_mean(data)
656
708
 
657
709
  return means
658
710
 
659
711
  @classmethod
660
- def sc_sum(cls, gid, excl, tm_dset, data, excl_dict=None, resolution=64,
661
- exclusion_shape=None, close=True):
712
+ def sc_sum(
713
+ cls,
714
+ gid,
715
+ excl,
716
+ tm_dset,
717
+ data,
718
+ excl_dict=None,
719
+ resolution=64,
720
+ exclusion_shape=None,
721
+ close=True,
722
+ ):
662
723
  """
663
724
  Compute the aggregate (sum) of data for the sc point
664
725
 
@@ -694,8 +755,12 @@ class SupplyCurvePoint(AbstractSupplyCurvePoint):
694
755
  ndarray
695
756
  Sum / aggregation of data for supply curve point
696
757
  """
697
- kwargs = {"excl_dict": excl_dict, "resolution": resolution,
698
- "exclusion_shape": exclusion_shape, "close": close}
758
+ kwargs = {
759
+ "excl_dict": excl_dict,
760
+ "resolution": resolution,
761
+ "exclusion_shape": exclusion_shape,
762
+ "close": close,
763
+ }
699
764
  with cls(gid, excl, tm_dset, **kwargs) as point:
700
765
  agg = point.aggregate(data)
701
766
 
@@ -718,9 +783,8 @@ class SupplyCurvePoint(AbstractSupplyCurvePoint):
718
783
  """
719
784
  if not data.size:
720
785
  return None
721
- else:
722
- # pd series is more flexible with non-numeric than stats mode
723
- return pd.Series(data).mode().values[0]
786
+ # pd series is more flexible with non-numeric than stats mode
787
+ return pd.Series(data).mode().values[0]
724
788
 
725
789
  @staticmethod
726
790
  def _categorize(data, incl_mult):
@@ -743,8 +807,10 @@ class SupplyCurvePoint(AbstractSupplyCurvePoint):
743
807
  total inclusions
744
808
  """
745
809
 
746
- data = {category: float(incl_mult[(data == category)].sum())
747
- for category in np.unique(data)}
810
+ data = {
811
+ category: float(incl_mult[(data == category)].sum())
812
+ for category in np.unique(data)
813
+ }
748
814
  data = jsonify_dict(data)
749
815
 
750
816
  return data
@@ -770,18 +836,22 @@ class SupplyCurvePoint(AbstractSupplyCurvePoint):
770
836
  data : float | int | str | None
771
837
  Result of applying method to data.
772
838
  """
773
- method_func = {'mode': cls._mode,
774
- 'mean': np.mean,
775
- 'max': np.max,
776
- 'min': np.min,
777
- 'sum': np.sum,
778
- 'category': cls._categorize}
839
+ method_func = {
840
+ "mode": cls._mode,
841
+ "mean": np.mean,
842
+ "max": np.max,
843
+ "min": np.min,
844
+ "sum": np.sum,
845
+ "category": cls._categorize,
846
+ }
779
847
 
780
848
  if data is not None:
781
849
  method = method.lower()
782
850
  if method not in method_func:
783
- e = ('Cannot recognize data layer agg method: '
784
- '"{}". Can only {}'.format(method, list(method_func)))
851
+ e = (
852
+ "Cannot recognize data layer agg method: "
853
+ '"{}". Can only {}'.format(method, list(method_func))
854
+ )
785
855
  logger.error(e)
786
856
  raise ValueError(e)
787
857
 
@@ -789,14 +859,16 @@ class SupplyCurvePoint(AbstractSupplyCurvePoint):
789
859
  data = data.flatten()
790
860
 
791
861
  if data.shape != incl_mult.shape:
792
- e = ('Cannot aggregate data with shape that doesnt '
793
- 'match excl mult!')
862
+ e = (
863
+ "Cannot aggregate data with shape that doesnt "
864
+ "match excl mult!"
865
+ )
794
866
  logger.error(e)
795
867
  raise DataShapeError(e)
796
868
 
797
- if method == 'category':
798
- data = method_func['category'](data, incl_mult)
799
- elif method in ['mean', 'sum']:
869
+ if method == "category":
870
+ data = method_func["category"](data, incl_mult)
871
+ elif method in ["mean", "sum"]:
800
872
  data = data * incl_mult
801
873
  data = method_func[method](data)
802
874
  else:
@@ -828,32 +900,36 @@ class SupplyCurvePoint(AbstractSupplyCurvePoint):
828
900
 
829
901
  if data_layers is not None:
830
902
  for name, attrs in data_layers.items():
831
- excl_fp = attrs.get('fpath', self._excl_fpath)
903
+ excl_fp = attrs.get("fpath", self._excl_fpath)
832
904
  if excl_fp != self._excl_fpath:
833
- fh = ExclusionLayers(attrs['fpath'])
905
+ fh = ExclusionLayers(attrs["fpath"])
834
906
  else:
835
907
  fh = self.exclusions.excl_h5
836
908
 
837
- raw = fh[attrs['dset'], self.rows, self.cols]
838
- nodata = fh.get_nodata_value(attrs['dset'])
909
+ raw = fh[attrs["dset"], self.rows, self.cols]
910
+ nodata = fh.get_nodata_value(attrs["dset"])
839
911
 
840
912
  data = raw.flatten()[self.bool_mask]
841
913
  incl_mult = self.include_mask_flat[self.bool_mask].copy()
842
914
 
843
915
  if nodata is not None:
844
- valid_data_mask = (data != nodata)
916
+ valid_data_mask = data != nodata
845
917
  data = data[valid_data_mask]
846
918
  incl_mult = incl_mult[valid_data_mask]
847
919
 
848
920
  if not data.size:
849
- m = ('Data layer "{}" has no valid data for '
850
- 'SC point gid {} because of exclusions '
851
- 'and/or nodata values in the data layer.'
852
- .format(name, self._gid))
921
+ m = (
922
+ 'Data layer "{}" has no valid data for '
923
+ "SC point gid {} because of exclusions "
924
+ "and/or nodata values in the data layer.".format(
925
+ name, self._gid
926
+ )
927
+ )
853
928
  logger.debug(m)
854
929
 
855
- data = self._agg_data_layer_method(data, incl_mult,
856
- attrs['method'])
930
+ data = self._agg_data_layer_method(
931
+ data, incl_mult, attrs["method"]
932
+ )
857
933
  summary[name] = data
858
934
 
859
935
  if excl_fp != self._excl_fpath:
@@ -865,10 +941,21 @@ class SupplyCurvePoint(AbstractSupplyCurvePoint):
865
941
  class AggregationSupplyCurvePoint(SupplyCurvePoint):
866
942
  """Generic single SC point to aggregate data from an h5 file."""
867
943
 
868
- def __init__(self, gid, excl, agg_h5, tm_dset,
869
- excl_dict=None, inclusion_mask=None,
870
- resolution=64, excl_area=None, exclusion_shape=None,
871
- close=True, gen_index=None, apply_exclusions=True):
944
+ def __init__(
945
+ self,
946
+ gid,
947
+ excl,
948
+ agg_h5,
949
+ tm_dset,
950
+ excl_dict=None,
951
+ inclusion_mask=None,
952
+ resolution=64,
953
+ excl_area=None,
954
+ exclusion_shape=None,
955
+ close=True,
956
+ gen_index=None,
957
+ apply_exclusions=True,
958
+ ):
872
959
  """
873
960
  Parameters
874
961
  ----------
@@ -911,13 +998,17 @@ class AggregationSupplyCurvePoint(SupplyCurvePoint):
911
998
  Flag to apply exclusions to the resource / generation gid's on
912
999
  initialization.
913
1000
  """
914
- super().__init__(gid, excl, tm_dset,
915
- excl_dict=excl_dict,
916
- inclusion_mask=inclusion_mask,
917
- resolution=resolution,
918
- excl_area=excl_area,
919
- exclusion_shape=exclusion_shape,
920
- close=close)
1001
+ super().__init__(
1002
+ gid,
1003
+ excl,
1004
+ tm_dset,
1005
+ excl_dict=excl_dict,
1006
+ inclusion_mask=inclusion_mask,
1007
+ resolution=resolution,
1008
+ excl_area=excl_area,
1009
+ exclusion_shape=exclusion_shape,
1010
+ close=close,
1011
+ )
921
1012
 
922
1013
  self._h5_fpath, self._h5 = self._parse_h5_file(agg_h5)
923
1014
 
@@ -927,9 +1018,12 @@ class AggregationSupplyCurvePoint(SupplyCurvePoint):
927
1018
  self._h5_gids = self._gids
928
1019
 
929
1020
  if (self._h5_gids != -1).sum() == 0:
930
- emsg = ('Supply curve point gid {} has no viable exclusion '
931
- 'points based on exclusions file: "{}"'
932
- .format(self._gid, self._excl_fpath))
1021
+ emsg = (
1022
+ "Supply curve point gid {} has no viable exclusion "
1023
+ 'points based on exclusions file: "{}"'.format(
1024
+ self._gid, self._excl_fpath
1025
+ )
1026
+ )
933
1027
  raise EmptySupplyCurvePointError(emsg)
934
1028
 
935
1029
  if apply_exclusions:
@@ -962,11 +1056,12 @@ class AggregationSupplyCurvePoint(SupplyCurvePoint):
962
1056
  elif issubclass(h5.__class__, MultiTimeResource):
963
1057
  h5_fpath = h5.h5_files
964
1058
  else:
965
- raise SupplyCurveInputError('SupplyCurvePoints needs a '
966
- '.h5 file path, or '
967
- 'Resource handler but '
968
- 'received: {}'
969
- .format(type(h5)))
1059
+ raise SupplyCurveInputError(
1060
+ "SupplyCurvePoints needs a "
1061
+ ".h5 file path, or "
1062
+ "Resource handler but "
1063
+ "received: {}".format(type(h5))
1064
+ )
970
1065
 
971
1066
  return h5_fpath, h5
972
1067
 
@@ -982,8 +1077,9 @@ class AggregationSupplyCurvePoint(SupplyCurvePoint):
982
1077
  self._h5_gids[exclude] = -1
983
1078
 
984
1079
  if (self._gids != -1).sum() == 0:
985
- msg = ('Supply curve point gid {} is completely excluded!'
986
- .format(self._gid))
1080
+ msg = "Supply curve point gid {} is completely excluded!".format(
1081
+ self._gid
1082
+ )
987
1083
  raise EmptySupplyCurvePointError(msg)
988
1084
 
989
1085
  def close(self):
@@ -1032,7 +1128,7 @@ class AggregationSupplyCurvePoint(SupplyCurvePoint):
1032
1128
  _h5 : Resource
1033
1129
  Resource h5 handler object.
1034
1130
  """
1035
- if self._h5 is None and '*' in self._h5_fpath:
1131
+ if self._h5 is None and "*" in self._h5_fpath:
1036
1132
  self._h5 = MultiTimeResource(self._h5_fpath)
1037
1133
  elif self._h5 is None:
1038
1134
  self._h5 = Resource(self._h5_fpath)
@@ -1043,15 +1139,22 @@ class AggregationSupplyCurvePoint(SupplyCurvePoint):
1043
1139
  def country(self):
1044
1140
  """Get the SC point country based on the resource meta data."""
1045
1141
  country = None
1046
- if 'country' in self.h5.meta and self.county is not None:
1142
+ county_not_none = self.county is not None
1143
+ if ResourceMetaField.COUNTRY in self.h5.meta and county_not_none:
1047
1144
  # make sure country and county are coincident
1048
- counties = self.h5.meta.loc[self.h5_gid_set, 'county'].values
1145
+ counties = self.h5.meta.loc[
1146
+ self.h5_gid_set, ResourceMetaField.COUNTY
1147
+ ].values
1049
1148
  iloc = np.where(counties == self.county)[0][0]
1050
- country = self.h5.meta.loc[self.h5_gid_set, 'country'].values
1149
+ country = self.h5.meta.loc[
1150
+ self.h5_gid_set, ResourceMetaField.COUNTRY
1151
+ ].values
1051
1152
  country = country[iloc]
1052
1153
 
1053
- elif 'country' in self.h5.meta:
1054
- country = self.h5.meta.loc[self.h5_gid_set, 'country'].mode()
1154
+ elif ResourceMetaField.COUNTRY in self.h5.meta:
1155
+ country = self.h5.meta.loc[
1156
+ self.h5_gid_set, ResourceMetaField.COUNTRY
1157
+ ].mode()
1055
1158
  country = country.values[0]
1056
1159
 
1057
1160
  return country
@@ -1060,15 +1163,21 @@ class AggregationSupplyCurvePoint(SupplyCurvePoint):
1060
1163
  def state(self):
1061
1164
  """Get the SC point state based on the resource meta data."""
1062
1165
  state = None
1063
- if 'state' in self.h5.meta and self.county is not None:
1166
+ if ResourceMetaField.STATE in self.h5.meta and self.county is not None:
1064
1167
  # make sure state and county are coincident
1065
- counties = self.h5.meta.loc[self.h5_gid_set, 'county'].values
1168
+ counties = self.h5.meta.loc[
1169
+ self.h5_gid_set, ResourceMetaField.COUNTY
1170
+ ].values
1066
1171
  iloc = np.where(counties == self.county)[0][0]
1067
- state = self.h5.meta.loc[self.h5_gid_set, 'state'].values
1172
+ state = self.h5.meta.loc[
1173
+ self.h5_gid_set, ResourceMetaField.STATE
1174
+ ].values
1068
1175
  state = state[iloc]
1069
1176
 
1070
- elif 'state' in self.h5.meta:
1071
- state = self.h5.meta.loc[self.h5_gid_set, 'state'].mode()
1177
+ elif ResourceMetaField.STATE in self.h5.meta:
1178
+ state = self.h5.meta.loc[
1179
+ self.h5_gid_set, ResourceMetaField.STATE
1180
+ ].mode()
1072
1181
  state = state.values[0]
1073
1182
 
1074
1183
  return state
@@ -1077,8 +1186,10 @@ class AggregationSupplyCurvePoint(SupplyCurvePoint):
1077
1186
  def county(self):
1078
1187
  """Get the SC point county based on the resource meta data."""
1079
1188
  county = None
1080
- if 'county' in self.h5.meta:
1081
- county = self.h5.meta.loc[self.h5_gid_set, 'county'].mode()
1189
+ if ResourceMetaField.COUNTY in self.h5.meta:
1190
+ county = self.h5.meta.loc[
1191
+ self.h5_gid_set, ResourceMetaField.COUNTY
1192
+ ].mode()
1082
1193
  county = county.values[0]
1083
1194
 
1084
1195
  return county
@@ -1087,8 +1198,10 @@ class AggregationSupplyCurvePoint(SupplyCurvePoint):
1087
1198
  def elevation(self):
1088
1199
  """Get the SC point elevation based on the resource meta data."""
1089
1200
  elevation = None
1090
- if 'elevation' in self.h5.meta:
1091
- elevation = self.h5.meta.loc[self.h5_gid_set, 'elevation'].mean()
1201
+ if ResourceMetaField.ELEVATION in self.h5.meta:
1202
+ elevation = self.h5.meta.loc[
1203
+ self.h5_gid_set, ResourceMetaField.ELEVATION
1204
+ ].mean()
1092
1205
 
1093
1206
  return elevation
1094
1207
 
@@ -1096,15 +1209,22 @@ class AggregationSupplyCurvePoint(SupplyCurvePoint):
1096
1209
  def timezone(self):
1097
1210
  """Get the SC point timezone based on the resource meta data."""
1098
1211
  timezone = None
1099
- if 'timezone' in self.h5.meta and self.county is not None:
1212
+ county_not_none = self.county is not None
1213
+ if ResourceMetaField.TIMEZONE in self.h5.meta and county_not_none:
1100
1214
  # make sure timezone flag and county are coincident
1101
- counties = self.h5.meta.loc[self.h5_gid_set, 'county'].values
1215
+ counties = self.h5.meta.loc[
1216
+ self.h5_gid_set, ResourceMetaField.COUNTY
1217
+ ].values
1102
1218
  iloc = np.where(counties == self.county)[0][0]
1103
- timezone = self.h5.meta.loc[self.h5_gid_set, 'timezone'].values
1219
+ timezone = self.h5.meta.loc[
1220
+ self.h5_gid_set, ResourceMetaField.TIMEZONE
1221
+ ].values
1104
1222
  timezone = timezone[iloc]
1105
1223
 
1106
- elif 'timezone' in self.h5.meta:
1107
- timezone = self.h5.meta.loc[self.h5_gid_set, 'timezone'].mode()
1224
+ elif ResourceMetaField.TIMEZONE in self.h5.meta:
1225
+ timezone = self.h5.meta.loc[
1226
+ self.h5_gid_set, ResourceMetaField.TIMEZONE
1227
+ ].mode()
1108
1228
  timezone = timezone.values[0]
1109
1229
 
1110
1230
  return timezone
@@ -1114,15 +1234,22 @@ class AggregationSupplyCurvePoint(SupplyCurvePoint):
1114
1234
  """Get the SC point offshore flag based on the resource meta data
1115
1235
  (if offshore column is present)."""
1116
1236
  offshore = None
1117
- if 'offshore' in self.h5.meta and self.county is not None:
1237
+ county_not_none = self.county is not None
1238
+ if ResourceMetaField.OFFSHORE in self.h5.meta and county_not_none:
1118
1239
  # make sure offshore flag and county are coincident
1119
- counties = self.h5.meta.loc[self.h5_gid_set, 'county'].values
1240
+ counties = self.h5.meta.loc[
1241
+ self.h5_gid_set, ResourceMetaField.COUNTY
1242
+ ].values
1120
1243
  iloc = np.where(counties == self.county)[0][0]
1121
- offshore = self.h5.meta.loc[self.h5_gid_set, 'offshore'].values
1244
+ offshore = self.h5.meta.loc[
1245
+ self.h5_gid_set, ResourceMetaField.OFFSHORE
1246
+ ].values
1122
1247
  offshore = offshore[iloc]
1123
1248
 
1124
- elif 'offshore' in self.h5.meta:
1125
- offshore = self.h5.meta.loc[self.h5_gid_set, 'offshore'].mode()
1249
+ elif ResourceMetaField.OFFSHORE in self.h5.meta:
1250
+ offshore = self.h5.meta.loc[
1251
+ self.h5_gid_set, ResourceMetaField.OFFSHORE
1252
+ ].mode()
1126
1253
  offshore = offshore.values[0]
1127
1254
 
1128
1255
  return offshore
@@ -1138,8 +1265,10 @@ class AggregationSupplyCurvePoint(SupplyCurvePoint):
1138
1265
  -------
1139
1266
  gid_counts : list
1140
1267
  """
1141
- gid_counts = [self.include_mask_flat[(self._h5_gids == gid)].sum()
1142
- for gid in self.h5_gid_set]
1268
+ gid_counts = [
1269
+ self.include_mask_flat[(self._h5_gids == gid)].sum()
1270
+ for gid in self.h5_gid_set
1271
+ ]
1143
1272
 
1144
1273
  return gid_counts
1145
1274
 
@@ -1153,28 +1282,41 @@ class AggregationSupplyCurvePoint(SupplyCurvePoint):
1153
1282
  pandas.Series
1154
1283
  List of supply curve point's meta data
1155
1284
  """
1156
- meta = {'sc_point_gid': self.sc_point_gid,
1157
- 'source_gids': self.h5_gid_set,
1158
- 'gid_counts': self.gid_counts,
1159
- 'n_gids': self.n_gids,
1160
- 'area_sq_km': self.area,
1161
- 'latitude': self.latitude,
1162
- 'longitude': self.longitude,
1163
- 'country': self.country,
1164
- 'state': self.state,
1165
- 'county': self.county,
1166
- 'elevation': self.elevation,
1167
- 'timezone': self.timezone,
1168
- }
1285
+ meta = {
1286
+ SupplyCurveField.SC_POINT_GID: self.sc_point_gid,
1287
+ SupplyCurveField.SOURCE_GIDS: self.h5_gid_set,
1288
+ SupplyCurveField.GID_COUNTS: self.gid_counts,
1289
+ SupplyCurveField.N_GIDS: self.n_gids,
1290
+ SupplyCurveField.AREA_SQ_KM: self.area,
1291
+ SupplyCurveField.LATITUDE: self.latitude,
1292
+ SupplyCurveField.LONGITUDE: self.longitude,
1293
+ SupplyCurveField.COUNTRY: self.country,
1294
+ SupplyCurveField.STATE: self.state,
1295
+ SupplyCurveField.COUNTY: self.county,
1296
+ SupplyCurveField.ELEVATION: self.elevation,
1297
+ SupplyCurveField.TIMEZONE: self.timezone,
1298
+ }
1169
1299
  meta = pd.Series(meta)
1170
1300
 
1171
1301
  return meta
1172
1302
 
1173
1303
  @classmethod
1174
- def run(cls, gid, excl, agg_h5, tm_dset, *agg_dset, agg_method='mean',
1175
- excl_dict=None, inclusion_mask=None,
1176
- resolution=64, excl_area=None,
1177
- exclusion_shape=None, close=True, gen_index=None):
1304
+ def run(
1305
+ cls,
1306
+ gid,
1307
+ excl,
1308
+ agg_h5,
1309
+ tm_dset,
1310
+ *agg_dset,
1311
+ agg_method="mean",
1312
+ excl_dict=None,
1313
+ inclusion_mask=None,
1314
+ resolution=64,
1315
+ excl_area=None,
1316
+ exclusion_shape=None,
1317
+ close=True,
1318
+ gen_index=None,
1319
+ ):
1178
1320
  """
1179
1321
  Compute exclusions weight mean for the sc point from data
1180
1322
 
@@ -1228,30 +1370,34 @@ class AggregationSupplyCurvePoint(SupplyCurvePoint):
1228
1370
  Given datasets and meta data aggregated to supply curve points
1229
1371
  """
1230
1372
  if isinstance(agg_dset, str):
1231
- agg_dset = (agg_dset, )
1232
-
1233
- kwargs = {"excl_dict": excl_dict,
1234
- "inclusion_mask": inclusion_mask,
1235
- "resolution": resolution,
1236
- "excl_area": excl_area,
1237
- "exclusion_shape": exclusion_shape,
1238
- "close": close,
1239
- "gen_index": gen_index}
1373
+ agg_dset = (agg_dset,)
1374
+
1375
+ kwargs = {
1376
+ "excl_dict": excl_dict,
1377
+ "inclusion_mask": inclusion_mask,
1378
+ "resolution": resolution,
1379
+ "excl_area": excl_area,
1380
+ "exclusion_shape": exclusion_shape,
1381
+ "close": close,
1382
+ "gen_index": gen_index,
1383
+ }
1240
1384
 
1241
1385
  with cls(gid, excl, agg_h5, tm_dset, **kwargs) as point:
1242
- if agg_method.lower().startswith('mean'):
1386
+ if agg_method.lower().startswith("mean"):
1243
1387
  agg_method = point.exclusion_weighted_mean
1244
- elif agg_method.lower().startswith(('sum', 'agg')):
1388
+ elif agg_method.lower().startswith(("sum", "agg")):
1245
1389
  agg_method = point.aggregate
1246
- elif 'wind_dir' in agg_method.lower():
1390
+ elif "wind_dir" in agg_method.lower():
1247
1391
  agg_method = point.mean_wind_dirs
1248
1392
  else:
1249
- msg = ('Aggregation method must be either mean, '
1250
- 'sum/aggregate, or wind_dir')
1393
+ msg = (
1394
+ "Aggregation method must be either mean, "
1395
+ "sum/aggregate, or wind_dir"
1396
+ )
1251
1397
  logger.error(msg)
1252
1398
  raise ValueError(msg)
1253
1399
 
1254
- out = {'meta': point.summary}
1400
+ out = {"meta": point.summary}
1255
1401
 
1256
1402
  for dset in agg_dset:
1257
1403
  ds = point.h5.open_dataset(dset)
@@ -1265,15 +1411,31 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
1265
1411
  respective generation and resource data."""
1266
1412
 
1267
1413
  # technology-dependent power density estimates in MW/km2
1268
- POWER_DENSITY = {'pv': 36, 'wind': 3}
1269
-
1270
- def __init__(self, gid, excl, gen, tm_dset, gen_index,
1271
- excl_dict=None, inclusion_mask=None,
1272
- res_class_dset=None, res_class_bin=None, excl_area=None,
1273
- power_density=None, cf_dset='cf_mean-means',
1274
- lcoe_dset='lcoe_fcr-means', h5_dsets=None, resolution=64,
1275
- exclusion_shape=None, close=False, friction_layer=None,
1276
- recalc_lcoe=True, apply_exclusions=True):
1414
+ POWER_DENSITY = {"pv": 36, "wind": 3}
1415
+
1416
+ def __init__(
1417
+ self,
1418
+ gid,
1419
+ excl,
1420
+ gen,
1421
+ tm_dset,
1422
+ gen_index,
1423
+ excl_dict=None,
1424
+ inclusion_mask=None,
1425
+ res_class_dset=None,
1426
+ res_class_bin=None,
1427
+ excl_area=None,
1428
+ power_density=None,
1429
+ cf_dset="cf_mean-means",
1430
+ lcoe_dset="lcoe_fcr-means",
1431
+ h5_dsets=None,
1432
+ resolution=64,
1433
+ exclusion_shape=None,
1434
+ close=False,
1435
+ friction_layer=None,
1436
+ recalc_lcoe=True,
1437
+ apply_exclusions=True,
1438
+ ):
1277
1439
  """
1278
1440
  Parameters
1279
1441
  ----------
@@ -1316,7 +1478,11 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
1316
1478
  (resource) "gid" and "power_density columns".
1317
1479
  cf_dset : str | np.ndarray
1318
1480
  Dataset name from gen containing capacity factor mean values.
1319
- Can be pre-extracted generation output data in np.ndarray.
1481
+ This name is used to infer AC capacity factor dataset for
1482
+ solar runs (i.e. the AC vsersion of "cf_mean-means" would
1483
+ be inferred to be "cf_mean_ac-means"). This input can also
1484
+ be pre-extracted generation output data in np.ndarray, in
1485
+ which case all DC solar outputs are set to `None`.
1320
1486
  lcoe_dset : str | np.ndarray
1321
1487
  Dataset name from gen containing LCOE mean values.
1322
1488
  Can be pre-extracted generation output data in np.ndarray.
@@ -1338,7 +1504,7 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
1338
1504
  recalc_lcoe : bool
1339
1505
  Flag to re-calculate the LCOE from the multi-year mean capacity
1340
1506
  factor and annual energy production data. This requires several
1341
- datasets to be aggregated in the h5_dsets input: system_capacity,
1507
+ datasets to be aggregated in the gen input: system_capacity,
1342
1508
  fixed_charge_rate, capital_cost, fixed_operating_cost,
1343
1509
  and variable_operating_cost.
1344
1510
  apply_exclusions : bool
@@ -1359,27 +1525,39 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
1359
1525
  self._power_density = self._power_density_ac = power_density
1360
1526
  self._friction_layer = friction_layer
1361
1527
  self._recalc_lcoe = recalc_lcoe
1362
-
1363
- super().__init__(gid, excl, gen, tm_dset,
1364
- excl_dict=excl_dict,
1365
- inclusion_mask=inclusion_mask,
1366
- resolution=resolution,
1367
- excl_area=excl_area,
1368
- exclusion_shape=exclusion_shape,
1369
- close=close, apply_exclusions=False)
1528
+ self._ssc = None
1529
+ self._slk = {}
1530
+
1531
+ super().__init__(
1532
+ gid,
1533
+ excl,
1534
+ gen,
1535
+ tm_dset,
1536
+ excl_dict=excl_dict,
1537
+ inclusion_mask=inclusion_mask,
1538
+ resolution=resolution,
1539
+ excl_area=excl_area,
1540
+ exclusion_shape=exclusion_shape,
1541
+ close=close,
1542
+ apply_exclusions=False,
1543
+ )
1370
1544
 
1371
1545
  self._res_gid_set = None
1372
1546
  self._gen_gid_set = None
1373
1547
 
1374
1548
  self._gen_fpath, self._gen = self._h5_fpath, self._h5
1375
1549
 
1376
- self._gen_gids, self._res_gids = self._map_gen_gids(self._gids,
1377
- gen_index)
1550
+ self._gen_gids, self._res_gids = self._map_gen_gids(
1551
+ self._gids, gen_index
1552
+ )
1378
1553
  self._gids = self._gen_gids
1379
1554
  if (self._gen_gids != -1).sum() == 0:
1380
- emsg = ('Supply curve point gid {} has no viable exclusion '
1381
- 'points based on exclusions file: "{}"'
1382
- .format(self._gid, self._excl_fpath))
1555
+ emsg = (
1556
+ "Supply curve point gid {} has no viable exclusion "
1557
+ 'points based on exclusions file: "{}"'.format(
1558
+ self._gid, self._excl_fpath
1559
+ )
1560
+ )
1383
1561
  raise EmptySupplyCurvePointError(emsg)
1384
1562
 
1385
1563
  if apply_exclusions:
@@ -1401,7 +1579,7 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
1401
1579
  Mean of flat_arr masked by the binary exclusions then weighted by
1402
1580
  the non-zero exclusions.
1403
1581
  """
1404
- x = flat_arr[self._gen_gids[self.bool_mask]].astype('float32')
1582
+ x = flat_arr[self._gen_gids[self.bool_mask]].astype("float32")
1405
1583
  incl = self.include_mask_flat[self.bool_mask]
1406
1584
  x *= incl
1407
1585
  mean = x.sum() / incl.sum()
@@ -1476,8 +1654,10 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
1476
1654
  gid_counts : list
1477
1655
  List of exclusion pixels in each resource/generation gid.
1478
1656
  """
1479
- gid_counts = [self.include_mask_flat[(self._res_gids == gid)].sum()
1480
- for gid in self.res_gid_set]
1657
+ gid_counts = [
1658
+ self.include_mask_flat[(self._res_gids == gid)].sum()
1659
+ for gid in self.res_gid_set
1660
+ ]
1481
1661
 
1482
1662
  return gid_counts
1483
1663
 
@@ -1495,10 +1675,9 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
1495
1675
  if isinstance(self._res_class_dset, np.ndarray):
1496
1676
  return self._res_class_dset
1497
1677
 
1498
- else:
1499
- if self._res_data is None:
1500
- if self._res_class_dset in self.gen.datasets:
1501
- self._res_data = self.gen[self._res_class_dset]
1678
+ if self._res_data is None:
1679
+ if self._res_class_dset in self.gen.datasets:
1680
+ self._res_data = self.gen[self._res_class_dset]
1502
1681
 
1503
1682
  return self._res_data
1504
1683
 
@@ -1516,13 +1695,36 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
1516
1695
  if isinstance(self._cf_dset, np.ndarray):
1517
1696
  return self._cf_dset
1518
1697
 
1519
- else:
1520
- if self._gen_data is None:
1521
- if self._cf_dset in self.gen.datasets:
1522
- self._gen_data = self.gen[self._cf_dset]
1698
+ if self._gen_data is None:
1699
+ if self._cf_dset in self.gen.datasets:
1700
+ self._gen_data = self.gen[self._cf_dset]
1523
1701
 
1524
1702
  return self._gen_data
1525
1703
 
1704
+ @property
1705
+ def gen_ac_data(self):
1706
+ """Get the generation ac capacity factor data array.
1707
+
1708
+ This output is only not `None` for solar runs where `cf_dset`
1709
+ was specified as a string.
1710
+
1711
+ Returns
1712
+ -------
1713
+ gen_ac_data : np.ndarray | None
1714
+ Multi-year-mean ac capacity factor data array for all sites
1715
+ in the generation data output file or `None` if none
1716
+ detected.
1717
+ """
1718
+
1719
+ if isinstance(self._cf_dset, np.ndarray):
1720
+ return None
1721
+
1722
+ ac_cf_dset = _infer_cf_dset_ac(self._cf_dset)
1723
+ if ac_cf_dset in self.gen.datasets:
1724
+ return self.gen[ac_cf_dset]
1725
+
1726
+ return None
1727
+
1526
1728
  @property
1527
1729
  def lcoe_data(self):
1528
1730
  """Get the LCOE data array.
@@ -1537,10 +1739,9 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
1537
1739
  if isinstance(self._lcoe_dset, np.ndarray):
1538
1740
  return self._lcoe_dset
1539
1741
 
1540
- else:
1541
- if self._lcoe_data is None:
1542
- if self._lcoe_dset in self.gen.datasets:
1543
- self._lcoe_data = self.gen[self._lcoe_dset]
1742
+ if self._lcoe_data is None:
1743
+ if self._lcoe_dset in self.gen.datasets:
1744
+ self._lcoe_data = self.gen[self._lcoe_dset]
1544
1745
 
1545
1746
  return self._lcoe_data
1546
1747
 
@@ -1550,6 +1751,11 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
1550
1751
  factor is weighted by the exclusions (usually 0 or 1, but 0.5
1551
1752
  exclusions will weight appropriately).
1552
1753
 
1754
+ This value represents DC capacity factor for solar and AC
1755
+ capacity factor for all other technologies. This is the capacity
1756
+ factor that should be used for all cost calculations for ALL
1757
+ technologies (to align with SAM).
1758
+
1553
1759
  Returns
1554
1760
  -------
1555
1761
  mean_cf : float | None
@@ -1561,6 +1767,45 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
1561
1767
 
1562
1768
  return mean_cf
1563
1769
 
1770
+ @property
1771
+ def mean_cf_ac(self):
1772
+ """Get the mean AC capacity factor for the non-excluded data.
1773
+
1774
+ This output is only not `None` for solar runs.
1775
+
1776
+ Capacity factor is weighted by the exclusions (usually 0 or 1,
1777
+ but 0.5 exclusions will weight appropriately).
1778
+
1779
+ Returns
1780
+ -------
1781
+ mean_cf_ac : float | None
1782
+ Mean capacity factor value for the non-excluded data.
1783
+ """
1784
+ mean_cf_ac = None
1785
+ if self.gen_ac_data is not None:
1786
+ mean_cf_ac = self.exclusion_weighted_mean(self.gen_ac_data)
1787
+
1788
+ return mean_cf_ac
1789
+
1790
+ @property
1791
+ def mean_cf_dc(self):
1792
+ """Get the mean DC capacity factor for the non-excluded data.
1793
+
1794
+ This output is only not `None` for solar runs.
1795
+
1796
+ Capacity factor is weighted by the exclusions (usually 0 or 1,
1797
+ but 0.5 exclusions will weight appropriately).
1798
+
1799
+ Returns
1800
+ -------
1801
+ mean_cf_dc : float | None
1802
+ Mean capacity factor value for the non-excluded data.
1803
+ """
1804
+ if self.mean_cf_ac is not None:
1805
+ return self.mean_cf
1806
+
1807
+ return None
1808
+
1564
1809
  @property
1565
1810
  def mean_lcoe(self):
1566
1811
  """Get the mean LCOE for the non-excluded data.
@@ -1578,22 +1823,25 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
1578
1823
  # year CF, but the output should be identical to the original LCOE and
1579
1824
  # so is not consequential).
1580
1825
  if self._recalc_lcoe:
1581
- required = ('fixed_charge_rate', 'capital_cost',
1582
- 'fixed_operating_cost', 'variable_operating_cost',
1583
- 'system_capacity')
1584
- if self.mean_h5_dsets_data is not None:
1585
- if all(k in self.mean_h5_dsets_data for k in required):
1586
- aep = (self.mean_h5_dsets_data['system_capacity']
1587
- * self.mean_cf * 8760)
1588
- # Note the AEP computation uses the SAM config
1589
- # `system_capacity`, so no need to scale `capital_cost`
1590
- # or `fixed_operating_cost` by anything
1591
- mean_lcoe = lcoe_fcr(
1592
- self.mean_h5_dsets_data['fixed_charge_rate'],
1593
- self.mean_h5_dsets_data['capital_cost'],
1594
- self.mean_h5_dsets_data['fixed_operating_cost'],
1595
- aep,
1596
- self.mean_h5_dsets_data['variable_operating_cost'])
1826
+ required = ("fixed_charge_rate", "capital_cost",
1827
+ "fixed_operating_cost", "variable_operating_cost",
1828
+ "system_capacity")
1829
+ if all(self._sam_lcoe_kwargs.get(k) is not None for k in required):
1830
+ aep = (
1831
+ self._sam_lcoe_kwargs["system_capacity"]
1832
+ * self.mean_cf
1833
+ * 8760
1834
+ )
1835
+ # Note the AEP computation uses the SAM config
1836
+ # `system_capacity`, so no need to scale `capital_cost`
1837
+ # or `fixed_operating_cost` by anything
1838
+ mean_lcoe = lcoe_fcr(
1839
+ self._sam_lcoe_kwargs["fixed_charge_rate"],
1840
+ self._sam_lcoe_kwargs["capital_cost"],
1841
+ self._sam_lcoe_kwargs["fixed_operating_cost"],
1842
+ aep,
1843
+ self._sam_lcoe_kwargs["variable_operating_cost"],
1844
+ )
1597
1845
 
1598
1846
  # alternative if lcoe was not able to be re-calculated from
1599
1847
  # multi year mean CF
@@ -1679,26 +1927,31 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
1679
1927
  """
1680
1928
 
1681
1929
  if self._power_density is None:
1682
- tech = self.gen.meta['reV_tech'][0]
1930
+ tech = self.gen.meta["reV_tech"][0]
1683
1931
  if tech in self.POWER_DENSITY:
1684
1932
  self._power_density = self.POWER_DENSITY[tech]
1685
1933
  else:
1686
- warn('Could not recognize reV technology in generation meta '
1687
- 'data: "{}". Cannot lookup an appropriate power density '
1688
- 'to calculate SC point capacity.'.format(tech))
1934
+ warn(
1935
+ "Could not recognize reV technology in generation meta "
1936
+ 'data: "{}". Cannot lookup an appropriate power density '
1937
+ "to calculate SC point capacity.".format(tech)
1938
+ )
1689
1939
 
1690
1940
  elif isinstance(self._power_density, pd.DataFrame):
1691
1941
  self._pd_obj = self._power_density
1692
1942
 
1693
1943
  missing = set(self.res_gid_set) - set(self._pd_obj.index.values)
1694
1944
  if any(missing):
1695
- msg = ('Variable power density input is missing the '
1696
- 'following resource GIDs: {}'.format(missing))
1945
+ msg = (
1946
+ "Variable power density input is missing the "
1947
+ "following resource GIDs: {}".format(missing)
1948
+ )
1697
1949
  logger.error(msg)
1698
1950
  raise FileInputError(msg)
1699
1951
 
1700
- pds = self._pd_obj.loc[self._res_gids[self.bool_mask],
1701
- 'power_density'].values
1952
+ pds = self._pd_obj.loc[
1953
+ self._res_gids[self.bool_mask], "power_density"
1954
+ ].values
1702
1955
  pds = pds.astype(np.float32)
1703
1956
  pds *= self.include_mask_flat[self.bool_mask]
1704
1957
  denom = self.include_mask_flat[self.bool_mask].sum()
@@ -1724,18 +1977,20 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
1724
1977
  return None
1725
1978
 
1726
1979
  ilr = self.gen["dc_ac_ratio", self._gen_gids[self.bool_mask]]
1727
- ilr = ilr.astype('float32')
1980
+ ilr = ilr.astype("float32")
1728
1981
  weights = self.include_mask_flat[self.bool_mask]
1729
1982
  if self._power_density_ac is None:
1730
- tech = self.gen.meta['reV_tech'][0]
1983
+ tech = self.gen.meta["reV_tech"][0]
1731
1984
  if tech in self.POWER_DENSITY:
1732
1985
  power_density_ac = self.POWER_DENSITY[tech] / ilr
1733
1986
  power_density_ac *= weights
1734
1987
  power_density_ac = power_density_ac.sum() / weights.sum()
1735
1988
  else:
1736
- warn('Could not recognize reV technology in generation meta '
1737
- 'data: "{}". Cannot lookup an appropriate power density '
1738
- 'to calculate SC point capacity.'.format(tech))
1989
+ warn(
1990
+ "Could not recognize reV technology in generation meta "
1991
+ 'data: "{}". Cannot lookup an appropriate power density '
1992
+ "to calculate SC point capacity.".format(tech)
1993
+ )
1739
1994
  power_density_ac = None
1740
1995
 
1741
1996
  elif isinstance(self._power_density_ac, pd.DataFrame):
@@ -1743,13 +1998,16 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
1743
1998
 
1744
1999
  missing = set(self.res_gid_set) - set(self._pd_obj.index.values)
1745
2000
  if any(missing):
1746
- msg = ('Variable power density input is missing the '
1747
- 'following resource GIDs: {}'.format(missing))
2001
+ msg = (
2002
+ "Variable power density input is missing the "
2003
+ "following resource GIDs: {}".format(missing)
2004
+ )
1748
2005
  logger.error(msg)
1749
2006
  raise FileInputError(msg)
1750
2007
 
1751
- pds = self._pd_obj.loc[self._res_gids[self.bool_mask],
1752
- 'power_density'].values
2008
+ pds = self._pd_obj.loc[
2009
+ self._res_gids[self.bool_mask], "power_density"
2010
+ ].values
1753
2011
  power_density_ac = pds.astype(np.float32) / ilr
1754
2012
  power_density_ac *= weights
1755
2013
  power_density_ac = power_density_ac.sum() / weights.sum()
@@ -1764,6 +2022,11 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
1764
2022
  """Get the estimated capacity in MW of the supply curve point in the
1765
2023
  current resource class with the applied exclusions.
1766
2024
 
2025
+ This value represents DC capacity for solar and AC capacity for
2026
+ all other technologies. This is the capacity that should be used
2027
+ for all cost calculations for ALL technologies (to align with
2028
+ SAM).
2029
+
1767
2030
  Returns
1768
2031
  -------
1769
2032
  capacity : float
@@ -1782,7 +2045,7 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
1782
2045
  """Get the AC estimated capacity in MW of the supply curve point in the
1783
2046
  current resource class with the applied exclusions.
1784
2047
 
1785
- This values is provided only for solar inputs that have
2048
+ This value is provided only for solar inputs that have
1786
2049
  the "dc_ac_ratio" dataset in the generation file. If these
1787
2050
  conditions are not met, this value is `None`.
1788
2051
 
@@ -1800,55 +2063,26 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
1800
2063
  return self.area * self.power_density_ac
1801
2064
 
1802
2065
  @property
1803
- def sc_point_capital_cost(self):
1804
- """Get the capital cost for the entire SC point.
1805
-
1806
- This method scales the capital cost based on the included-area
1807
- capacity. The calculation requires 'capital_cost' and
1808
- 'system_capacity' in the generation file and passed through as
1809
- `h5_dsets`, otherwise it returns `None`.
1810
-
1811
- Returns
1812
- -------
1813
- sc_point_capital_cost : float | None
1814
- Total supply curve point capital cost ($).
1815
- """
1816
- if self.mean_h5_dsets_data is None:
1817
- return None
1818
-
1819
- required = ('capital_cost', 'system_capacity')
1820
- if not all(k in self.mean_h5_dsets_data for k in required):
1821
- return None
2066
+ def capacity_dc(self):
2067
+ """Get the DC estimated capacity in MW of the supply curve point
2068
+ in the current resource class with the applied exclusions.
1822
2069
 
1823
- cap_cost_per_mw = (self.mean_h5_dsets_data['capital_cost']
1824
- / self.mean_h5_dsets_data['system_capacity'])
1825
- return cap_cost_per_mw * self.capacity
1826
-
1827
- @property
1828
- def sc_point_fixed_operating_cost(self):
1829
- """Get the fixed operating cost for the entire SC point.
1830
-
1831
- This method scales the fixed operating cost based on the
1832
- included-area capacity. The calculation requires
1833
- 'fixed_operating_cost' and 'system_capacity' in the generation
1834
- file and passed through as `h5_dsets`, otherwise it returns
1835
- `None`.
2070
+ This value is provided only for solar inputs that have
2071
+ the "dc_ac_ratio" dataset in the generation file. If these
2072
+ conditions are not met, this value is `None`.
1836
2073
 
1837
2074
  Returns
1838
2075
  -------
1839
- sc_point_fixed_operating_cost : float | None
1840
- Total supply curve point fixed operating cost ($).
2076
+ capacity : float | None
2077
+ Estimated AC capacity in MW of the supply curve point in the
2078
+ current resource class with the applied exclusions. Only not
2079
+ `None` for solar runs with "dc_ac_ratio" dataset in the
2080
+ generation file
1841
2081
  """
1842
- if self.mean_h5_dsets_data is None:
1843
- return None
1844
-
1845
- required = ('fixed_operating_cost', 'system_capacity')
1846
- if not all(k in self.mean_h5_dsets_data for k in required):
2082
+ if self.power_density_ac is None:
1847
2083
  return None
1848
2084
 
1849
- fixed_cost_per_mw = (self.mean_h5_dsets_data['fixed_operating_cost']
1850
- / self.mean_h5_dsets_data['system_capacity'])
1851
- return fixed_cost_per_mw * self.capacity
2085
+ return self.area * self.power_density
1852
2086
 
1853
2087
  @property
1854
2088
  def sc_point_annual_energy(self):
@@ -1868,25 +2102,6 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
1868
2102
 
1869
2103
  return self.mean_cf * self.capacity * 8760
1870
2104
 
1871
- @property
1872
- def sc_point_annual_energy_ac(self):
1873
- """Get the total AC annual energy (MWh) for the entire SC point.
1874
-
1875
- This value is computed using the AC capacity of the supply curve
1876
- point as well as the mean capacity factor. If either the mean
1877
- capacity factor or the AC capacity value is `None`, this value
1878
- will also be `None`.
1879
-
1880
- Returns
1881
- -------
1882
- sc_point_annual_energy_ac : float | None
1883
- Total AC annual energy (MWh) for the entire SC point.
1884
- """
1885
- if self.mean_cf is None or self.capacity_ac is None:
1886
- return None
1887
-
1888
- return self.mean_cf * self.capacity_ac * 8760
1889
-
1890
2105
  @property
1891
2106
  def h5_dsets_data(self):
1892
2107
  """Get any additional/supplemental h5 dataset data to summarize.
@@ -1909,15 +2124,84 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
1909
2124
  _h5_dsets_data = self._h5_dsets
1910
2125
 
1911
2126
  elif self._h5_dsets is not None:
1912
- e = ('Cannot recognize h5_dsets input type, should be None, '
1913
- 'a list of dataset names, or a dictionary or '
1914
- 'pre-extracted data. Received: {} {}'
1915
- .format(type(self._h5_dsets), self._h5_dsets))
2127
+ e = (
2128
+ "Cannot recognize h5_dsets input type, should be None, "
2129
+ "a list of dataset names, or a dictionary or "
2130
+ "pre-extracted data. Received: {} {}".format(
2131
+ type(self._h5_dsets), self._h5_dsets
2132
+ )
2133
+ )
1916
2134
  logger.error(e)
1917
2135
  raise TypeError(e)
1918
2136
 
1919
2137
  return _h5_dsets_data
1920
2138
 
2139
+ @property
2140
+ def regional_multiplier(self):
2141
+ """float: Mean regional capital cost multiplier, defaults to 1."""
2142
+ if "capital_cost_multiplier" not in self.gen.datasets:
2143
+ return 1
2144
+
2145
+ multipliers = self.gen["capital_cost_multiplier"]
2146
+ return self.exclusion_weighted_mean(multipliers)
2147
+
2148
+ @property
2149
+ def fixed_charge_rate(self):
2150
+ """float: Mean fixed_charge_rate, defaults to 0."""
2151
+ if "fixed_charge_rate" not in self.gen.datasets:
2152
+ return 0
2153
+
2154
+ return self.exclusion_weighted_mean(self.gen["fixed_charge_rate"])
2155
+
2156
+ @property
2157
+ def _sam_system_capacity(self):
2158
+ """float: Mean SAM generation system capacity input, defaults to 0. """
2159
+ if self._ssc is not None:
2160
+ return self._ssc
2161
+
2162
+ self._ssc = 0
2163
+ if "system_capacity" in self.gen.datasets:
2164
+ self._ssc = self.exclusion_weighted_mean(
2165
+ self.gen["system_capacity"]
2166
+ )
2167
+
2168
+ return self._ssc
2169
+
2170
+ @property
2171
+ def _sam_lcoe_kwargs(self):
2172
+ """dict: Mean LCOE inputs, as passed to SAM during generation."""
2173
+ if self._slk:
2174
+ return self._slk
2175
+
2176
+ self._slk = {"capital_cost": None, "fixed_operating_cost": None,
2177
+ "variable_operating_cost": None,
2178
+ "fixed_charge_rate": None, "system_capacity": None}
2179
+
2180
+ for dset in self._slk:
2181
+ if dset in self.gen.datasets:
2182
+ self._slk[dset] = self.exclusion_weighted_mean(
2183
+ self.gen[dset]
2184
+ )
2185
+
2186
+ return self._slk
2187
+
2188
+ def _compute_cost_per_ac_mw(self, dset):
2189
+ """Compute a cost per AC MW for a given input. """
2190
+ if self._sam_system_capacity <= 0:
2191
+ return 0
2192
+
2193
+ if dset not in self.gen.datasets:
2194
+ return 0
2195
+
2196
+ sam_cost = self.exclusion_weighted_mean(self.gen[dset])
2197
+ sam_cost_per_mw = sam_cost / self._sam_system_capacity
2198
+ sc_point_cost = sam_cost_per_mw * self.capacity
2199
+
2200
+ ac_cap = (self.capacity
2201
+ if self.capacity_ac is None
2202
+ else self.capacity_ac)
2203
+ return sc_point_cost / ac_cap
2204
+
1921
2205
  @property
1922
2206
  def mean_h5_dsets_data(self):
1923
2207
  """Get the mean supplemental h5 datasets data (optional)
@@ -1955,8 +2239,10 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
1955
2239
  self._incl_mask = self._incl_mask.flatten()
1956
2240
 
1957
2241
  if (self._gen_gids != -1).sum() == 0:
1958
- msg = ('Supply curve point gid {} is completely excluded for res '
1959
- 'bin: {}'.format(self._gid, self._res_class_bin))
2242
+ msg = (
2243
+ "Supply curve point gid {} is completely excluded for res "
2244
+ "bin: {}".format(self._gid, self._res_class_bin)
2245
+ )
1960
2246
  raise EmptySupplyCurvePointError(msg)
1961
2247
 
1962
2248
  def _resource_exclusion(self, boolean_exclude):
@@ -1974,14 +2260,16 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
1974
2260
  outside of current resource class bin.
1975
2261
  """
1976
2262
 
1977
- if (self._res_class_dset is not None
1978
- and self._res_class_bin is not None):
1979
-
2263
+ if (
2264
+ self._res_class_dset is not None
2265
+ and self._res_class_bin is not None
2266
+ ):
1980
2267
  rex = self.res_data[self._gen_gids]
1981
- rex = ((rex < np.min(self._res_class_bin))
1982
- | (rex >= np.max(self._res_class_bin)))
2268
+ rex = (rex < np.min(self._res_class_bin)) | (
2269
+ rex >= np.max(self._res_class_bin)
2270
+ )
1983
2271
 
1984
- boolean_exclude = (boolean_exclude | rex)
2272
+ boolean_exclude = boolean_exclude | rex
1985
2273
 
1986
2274
  return boolean_exclude
1987
2275
 
@@ -2001,39 +2289,66 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
2001
2289
  Dictionary of summary outputs for this sc point.
2002
2290
  """
2003
2291
 
2004
- ARGS = {'res_gids': self.res_gid_set,
2005
- 'gen_gids': self.gen_gid_set,
2006
- 'gid_counts': self.gid_counts,
2007
- 'n_gids': self.n_gids,
2008
- 'mean_cf': self.mean_cf,
2009
- 'mean_lcoe': self.mean_lcoe,
2010
- 'mean_res': self.mean_res,
2011
- 'capacity': self.capacity,
2012
- 'area_sq_km': self.area,
2013
- 'latitude': self.latitude,
2014
- 'longitude': self.longitude,
2015
- 'country': self.country,
2016
- 'state': self.state,
2017
- 'county': self.county,
2018
- 'elevation': self.elevation,
2019
- 'timezone': self.timezone,
2020
- }
2021
-
2022
- extra_atts = ['capacity_ac', 'offshore', 'sc_point_capital_cost',
2023
- 'sc_point_fixed_operating_cost',
2024
- 'sc_point_annual_energy', 'sc_point_annual_energy_ac']
2025
- for attr in extra_atts:
2026
- value = getattr(self, attr)
2027
- if value is not None:
2028
- ARGS[attr] = value
2292
+ ARGS = {
2293
+ SupplyCurveField.LATITUDE: self.latitude,
2294
+ SupplyCurveField.LONGITUDE: self.longitude,
2295
+ SupplyCurveField.COUNTRY: self.country,
2296
+ SupplyCurveField.STATE: self.state,
2297
+ SupplyCurveField.COUNTY: self.county,
2298
+ SupplyCurveField.ELEVATION: self.elevation,
2299
+ SupplyCurveField.TIMEZONE: self.timezone,
2300
+ SupplyCurveField.SC_POINT_GID: self.sc_point_gid,
2301
+ SupplyCurveField.SC_ROW_IND: self.sc_row_ind,
2302
+ SupplyCurveField.SC_COL_IND: self.sc_col_ind,
2303
+ SupplyCurveField.RES_GIDS: self.res_gid_set,
2304
+ SupplyCurveField.GEN_GIDS: self.gen_gid_set,
2305
+ SupplyCurveField.GID_COUNTS: self.gid_counts,
2306
+ SupplyCurveField.N_GIDS: self.n_gids,
2307
+ SupplyCurveField.OFFSHORE: self.offshore,
2308
+ SupplyCurveField.MEAN_CF_AC: (
2309
+ self.mean_cf if self.mean_cf_ac is None else self.mean_cf_ac
2310
+ ),
2311
+ SupplyCurveField.MEAN_CF_DC: self.mean_cf_dc,
2312
+ SupplyCurveField.MEAN_LCOE: self.mean_lcoe,
2313
+ SupplyCurveField.MEAN_RES: self.mean_res,
2314
+ SupplyCurveField.AREA_SQ_KM: self.area,
2315
+ SupplyCurveField.CAPACITY_AC_MW: (
2316
+ self.capacity if self.capacity_ac is None else self.capacity_ac
2317
+ ),
2318
+ SupplyCurveField.CAPACITY_DC_MW: self.capacity_dc,
2319
+ SupplyCurveField.EOS_MULT: 1, # added later
2320
+ SupplyCurveField.REG_MULT: self.regional_multiplier,
2321
+ SupplyCurveField.SC_POINT_ANNUAL_ENERGY_MW: (
2322
+ self.sc_point_annual_energy
2323
+ ),
2324
+ SupplyCurveField.COST_SITE_OCC_USD_PER_AC_MW: (
2325
+ self._compute_cost_per_ac_mw("capital_cost")
2326
+ ),
2327
+ SupplyCurveField.COST_BASE_OCC_USD_PER_AC_MW: (
2328
+ self._compute_cost_per_ac_mw("base_capital_cost")
2329
+ ),
2330
+ SupplyCurveField.COST_SITE_FOC_USD_PER_AC_MW: (
2331
+ self._compute_cost_per_ac_mw("fixed_operating_cost")
2332
+ ),
2333
+ SupplyCurveField.COST_BASE_FOC_USD_PER_AC_MW: (
2334
+ self._compute_cost_per_ac_mw("base_fixed_operating_cost")
2335
+ ),
2336
+ SupplyCurveField.COST_SITE_VOC_USD_PER_AC_MW: (
2337
+ self._compute_cost_per_ac_mw("variable_operating_cost")
2338
+ ),
2339
+ SupplyCurveField.COST_BASE_VOC_USD_PER_AC_MW: (
2340
+ self._compute_cost_per_ac_mw("base_variable_operating_cost")
2341
+ ),
2342
+ SupplyCurveField.FIXED_CHARGE_RATE: self.fixed_charge_rate,
2343
+ }
2029
2344
 
2030
2345
  if self._friction_layer is not None:
2031
- ARGS['mean_friction'] = self.mean_friction
2032
- ARGS['mean_lcoe_friction'] = self.mean_lcoe_friction
2346
+ ARGS[SupplyCurveField.MEAN_FRICTION] = self.mean_friction
2347
+ ARGS[SupplyCurveField.MEAN_LCOE_FRICTION] = self.mean_lcoe_friction
2033
2348
 
2034
2349
  if self._h5_dsets is not None:
2035
2350
  for dset, data in self.mean_h5_dsets_data.items():
2036
- ARGS['mean_{}'.format(dset)] = data
2351
+ ARGS["mean_{}".format(dset)] = data
2037
2352
 
2038
2353
  if args is None:
2039
2354
  args = list(ARGS.keys())
@@ -2043,8 +2358,11 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
2043
2358
  if arg in ARGS:
2044
2359
  summary[arg] = ARGS[arg]
2045
2360
  else:
2046
- warn('Cannot find "{}" as an available SC self summary '
2047
- 'output', OutputWarning)
2361
+ warn(
2362
+ 'Cannot find "{}" as an available SC self summary '
2363
+ "output",
2364
+ OutputWarning,
2365
+ )
2048
2366
 
2049
2367
  return summary
2050
2368
 
@@ -2070,26 +2388,41 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
2070
2388
  """
2071
2389
 
2072
2390
  eos = EconomiesOfScale(cap_cost_scale, summary)
2073
- summary['raw_lcoe'] = eos.raw_lcoe
2074
- summary['mean_lcoe'] = eos.scaled_lcoe
2075
- summary['capital_cost_scalar'] = eos.capital_cost_scalar
2076
- summary['scaled_capital_cost'] = eos.scaled_capital_cost
2077
- if "sc_point_capital_cost" in summary:
2078
- scaled_costs = (summary["sc_point_capital_cost"]
2079
- * eos.capital_cost_scalar)
2080
- summary['scaled_sc_point_capital_cost'] = scaled_costs
2081
-
2391
+ summary[SupplyCurveField.RAW_LCOE] = eos.raw_lcoe
2392
+ summary[SupplyCurveField.MEAN_LCOE] = eos.scaled_lcoe
2393
+ summary[SupplyCurveField.EOS_MULT] = eos.capital_cost_scalar
2394
+ summary[SupplyCurveField.COST_SITE_OCC_USD_PER_AC_MW] = (
2395
+ summary[SupplyCurveField.COST_SITE_OCC_USD_PER_AC_MW]
2396
+ * summary[SupplyCurveField.EOS_MULT]
2397
+ )
2082
2398
  return summary
2083
2399
 
2084
2400
  @classmethod
2085
- def summarize(cls, gid, excl_fpath, gen_fpath, tm_dset, gen_index,
2086
- excl_dict=None, inclusion_mask=None,
2087
- res_class_dset=None, res_class_bin=None,
2088
- excl_area=None, power_density=None,
2089
- cf_dset='cf_mean-means', lcoe_dset='lcoe_fcr-means',
2090
- h5_dsets=None, resolution=64, exclusion_shape=None,
2091
- close=False, friction_layer=None, args=None,
2092
- data_layers=None, cap_cost_scale=None, recalc_lcoe=True):
2401
+ def summarize(
2402
+ cls,
2403
+ gid,
2404
+ excl_fpath,
2405
+ gen_fpath,
2406
+ tm_dset,
2407
+ gen_index,
2408
+ excl_dict=None,
2409
+ inclusion_mask=None,
2410
+ res_class_dset=None,
2411
+ res_class_bin=None,
2412
+ excl_area=None,
2413
+ power_density=None,
2414
+ cf_dset="cf_mean-means",
2415
+ lcoe_dset="lcoe_fcr-means",
2416
+ h5_dsets=None,
2417
+ resolution=64,
2418
+ exclusion_shape=None,
2419
+ close=False,
2420
+ friction_layer=None,
2421
+ args=None,
2422
+ data_layers=None,
2423
+ cap_cost_scale=None,
2424
+ recalc_lcoe=True,
2425
+ ):
2093
2426
  """Get a summary dictionary of a single supply curve point.
2094
2427
 
2095
2428
  Parameters
@@ -2167,7 +2500,7 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
2167
2500
  recalc_lcoe : bool
2168
2501
  Flag to re-calculate the LCOE from the multi-year mean capacity
2169
2502
  factor and annual energy production data. This requires several
2170
- datasets to be aggregated in the h5_dsets input: system_capacity,
2503
+ datasets to be aggregated in the gen input: system_capacity,
2171
2504
  fixed_charge_rate, capital_cost, fixed_operating_cost,
2172
2505
  and variable_operating_cost.
2173
2506
 
@@ -2176,24 +2509,26 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
2176
2509
  summary : dict
2177
2510
  Dictionary of summary outputs for this sc point.
2178
2511
  """
2179
- kwargs = {"excl_dict": excl_dict,
2180
- "inclusion_mask": inclusion_mask,
2181
- "res_class_dset": res_class_dset,
2182
- "res_class_bin": res_class_bin,
2183
- "excl_area": excl_area,
2184
- "power_density": power_density,
2185
- "cf_dset": cf_dset,
2186
- "lcoe_dset": lcoe_dset,
2187
- "h5_dsets": h5_dsets,
2188
- "resolution": resolution,
2189
- "exclusion_shape": exclusion_shape,
2190
- "close": close,
2191
- 'friction_layer': friction_layer,
2192
- 'recalc_lcoe': recalc_lcoe,
2193
- }
2194
-
2195
- with cls(gid, excl_fpath, gen_fpath, tm_dset, gen_index,
2196
- **kwargs) as point:
2512
+ kwargs = {
2513
+ "excl_dict": excl_dict,
2514
+ "inclusion_mask": inclusion_mask,
2515
+ "res_class_dset": res_class_dset,
2516
+ "res_class_bin": res_class_bin,
2517
+ "excl_area": excl_area,
2518
+ "power_density": power_density,
2519
+ "cf_dset": cf_dset,
2520
+ "lcoe_dset": lcoe_dset,
2521
+ "h5_dsets": h5_dsets,
2522
+ "resolution": resolution,
2523
+ "exclusion_shape": exclusion_shape,
2524
+ "close": close,
2525
+ "friction_layer": friction_layer,
2526
+ "recalc_lcoe": recalc_lcoe,
2527
+ }
2528
+
2529
+ with cls(
2530
+ gid, excl_fpath, gen_fpath, tm_dset, gen_index, **kwargs
2531
+ ) as point:
2197
2532
  summary = point.point_summary(args=args)
2198
2533
 
2199
2534
  if data_layers is not None:
@@ -2203,3 +2538,13 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
2203
2538
  summary = point.economies_of_scale(cap_cost_scale, summary)
2204
2539
 
2205
2540
  return summary
2541
+
2542
+
2543
+ def _infer_cf_dset_ac(cf_dset):
2544
+ """Infer AC dataset name from input. """
2545
+ parts = cf_dset.split("-")
2546
+ if len(parts) == 1:
2547
+ return f"{cf_dset}_ac"
2548
+
2549
+ cf_name = "-".join(parts[:-1])
2550
+ return f"{cf_name}_ac-{parts[-1]}"