NREL-reV 0.8.6__py3-none-any.whl → 0.8.9__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. {NREL_reV-0.8.6.dist-info → NREL_reV-0.8.9.dist-info}/METADATA +12 -10
  2. {NREL_reV-0.8.6.dist-info → NREL_reV-0.8.9.dist-info}/RECORD +38 -38
  3. {NREL_reV-0.8.6.dist-info → NREL_reV-0.8.9.dist-info}/WHEEL +1 -1
  4. reV/SAM/SAM.py +182 -133
  5. reV/SAM/econ.py +18 -14
  6. reV/SAM/generation.py +640 -414
  7. reV/SAM/windbos.py +93 -79
  8. reV/bespoke/bespoke.py +690 -445
  9. reV/bespoke/place_turbines.py +6 -6
  10. reV/config/project_points.py +220 -140
  11. reV/econ/econ.py +165 -113
  12. reV/econ/economies_of_scale.py +57 -34
  13. reV/generation/base.py +310 -183
  14. reV/generation/generation.py +309 -191
  15. reV/handlers/exclusions.py +16 -15
  16. reV/handlers/multi_year.py +12 -9
  17. reV/handlers/outputs.py +6 -5
  18. reV/hybrids/hybrid_methods.py +28 -30
  19. reV/hybrids/hybrids.py +304 -188
  20. reV/nrwal/nrwal.py +262 -168
  21. reV/qa_qc/cli_qa_qc.py +14 -10
  22. reV/qa_qc/qa_qc.py +217 -119
  23. reV/qa_qc/summary.py +228 -146
  24. reV/rep_profiles/rep_profiles.py +349 -230
  25. reV/supply_curve/aggregation.py +349 -188
  26. reV/supply_curve/competitive_wind_farms.py +90 -48
  27. reV/supply_curve/exclusions.py +138 -85
  28. reV/supply_curve/extent.py +75 -50
  29. reV/supply_curve/points.py +620 -295
  30. reV/supply_curve/sc_aggregation.py +396 -226
  31. reV/supply_curve/supply_curve.py +505 -308
  32. reV/supply_curve/tech_mapping.py +144 -82
  33. reV/utilities/__init__.py +199 -16
  34. reV/utilities/pytest_utils.py +8 -4
  35. reV/version.py +1 -1
  36. {NREL_reV-0.8.6.dist-info → NREL_reV-0.8.9.dist-info}/LICENSE +0 -0
  37. {NREL_reV-0.8.6.dist-info → NREL_reV-0.8.9.dist-info}/entry_points.txt +0 -0
  38. {NREL_reV-0.8.6.dist-info → NREL_reV-0.8.9.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
 
@@ -48,7 +52,8 @@ class AbstractSupplyCurvePoint(ABC):
48
52
  self._gid = gid
49
53
  self._resolution = resolution
50
54
  self._rows, self._cols = self._parse_slices(
51
- gid, resolution, exclusion_shape)
55
+ gid, resolution, exclusion_shape
56
+ )
52
57
 
53
58
  @staticmethod
54
59
  def _ordered_unique(seq):
@@ -98,7 +103,7 @@ class AbstractSupplyCurvePoint(ABC):
98
103
 
99
104
  @property
100
105
  def gid(self):
101
- """supply curve point gid"""
106
+ """Supply curve point gid"""
102
107
  return self._gid
103
108
 
104
109
  @property
@@ -172,8 +177,10 @@ class AbstractSupplyCurvePoint(ABC):
172
177
  row = loc[0][0]
173
178
  col = loc[1][0]
174
179
  except IndexError as exc:
175
- msg = ('Gid {} out of bounds for extent shape {} and '
176
- 'resolution {}.'.format(gid, shape, resolution))
180
+ msg = (
181
+ "Gid {} out of bounds for extent shape {} and "
182
+ "resolution {}.".format(gid, shape, resolution)
183
+ )
177
184
  raise IndexError(msg) from exc
178
185
 
179
186
  if row + 1 != nrows:
@@ -192,9 +199,18 @@ class AbstractSupplyCurvePoint(ABC):
192
199
  class SupplyCurvePoint(AbstractSupplyCurvePoint):
193
200
  """Generic single SC point based on exclusions, resolution, and techmap"""
194
201
 
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):
202
+ def __init__(
203
+ self,
204
+ gid,
205
+ excl,
206
+ tm_dset,
207
+ excl_dict=None,
208
+ inclusion_mask=None,
209
+ resolution=64,
210
+ excl_area=None,
211
+ exclusion_shape=None,
212
+ close=True,
213
+ ):
198
214
  """
199
215
  Parameters
200
216
  ----------
@@ -245,8 +261,10 @@ class SupplyCurvePoint(AbstractSupplyCurvePoint):
245
261
  self._incl_mask = inclusion_mask
246
262
  self._incl_mask_flat = None
247
263
  if inclusion_mask is not None:
248
- msg = ('Bad inclusion mask input shape of {} with stated '
249
- 'resolution of {}'.format(inclusion_mask.shape, resolution))
264
+ msg = (
265
+ "Bad inclusion mask input shape of {} with stated "
266
+ "resolution of {}".format(inclusion_mask.shape, resolution)
267
+ )
250
268
  assert len(inclusion_mask.shape) == 2, msg
251
269
  assert inclusion_mask.shape[0] <= resolution, msg
252
270
  assert inclusion_mask.shape[1] <= resolution, msg
@@ -282,11 +300,12 @@ class SupplyCurvePoint(AbstractSupplyCurvePoint):
282
300
  excl_fpath = excl.excl_h5.h5_file
283
301
  exclusions = excl
284
302
  else:
285
- raise SupplyCurveInputError('SupplyCurvePoints needs an '
286
- 'exclusions file path, or '
287
- 'ExclusionMask handler but '
288
- 'received: {}'
289
- .format(type(excl)))
303
+ raise SupplyCurveInputError(
304
+ "SupplyCurvePoints needs an "
305
+ "exclusions file path, or "
306
+ "ExclusionMask handler but "
307
+ "received: {}".format(type(excl))
308
+ )
290
309
 
291
310
  return excl_fpath, exclusions
292
311
 
@@ -312,9 +331,12 @@ class SupplyCurvePoint(AbstractSupplyCurvePoint):
312
331
  res_gids = res_gids.astype(np.int32).flatten()
313
332
 
314
333
  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))
334
+ emsg = (
335
+ "Supply curve point gid {} has no viable exclusion points "
336
+ 'based on exclusions file: "{}"'.format(
337
+ self._gid, self._excl_fpath
338
+ )
339
+ )
318
340
  raise EmptySupplyCurvePointError(emsg)
319
341
 
320
342
  return res_gids
@@ -343,8 +365,9 @@ class SupplyCurvePoint(AbstractSupplyCurvePoint):
343
365
  ExclusionMask h5 handler object.
344
366
  """
345
367
  if self._excls is None:
346
- self._excls = ExclusionMaskFromDict(self._excl_fpath,
347
- layers_dict=self._excl_dict)
368
+ self._excls = ExclusionMaskFromDict(
369
+ self._excl_fpath, layers_dict=self._excl_dict
370
+ )
348
371
 
349
372
  return self._excls
350
373
 
@@ -359,8 +382,8 @@ class SupplyCurvePoint(AbstractSupplyCurvePoint):
359
382
  """
360
383
 
361
384
  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]
385
+ lats = self.exclusions.excl_h5[LATITUDE, self.rows, self.cols]
386
+ lons = self.exclusions.excl_h5[LONGITUDE, self.rows, self.cols]
364
387
  self._centroid = (lats.mean(), lons.mean())
365
388
 
366
389
  return self._centroid
@@ -438,8 +461,12 @@ class SupplyCurvePoint(AbstractSupplyCurvePoint):
438
461
  self._incl_mask[out_of_extent] = 0.0
439
462
 
440
463
  if self._incl_mask.max() > 1:
441
- w = ('Exclusions data max value is > 1: {}'
442
- .format(self._incl_mask.max()), InputWarning)
464
+ w = (
465
+ "Exclusions data max value is > 1: {}".format(
466
+ self._incl_mask.max()
467
+ ),
468
+ InputWarning,
469
+ )
443
470
  logger.warning(w)
444
471
  warn(w)
445
472
 
@@ -505,8 +532,9 @@ class SupplyCurvePoint(AbstractSupplyCurvePoint):
505
532
  """
506
533
 
507
534
  if all(self.include_mask_flat[self.bool_mask] == 0):
508
- msg = ('Supply curve point gid {} is completely excluded!'
509
- .format(self._gid))
535
+ msg = "Supply curve point gid {} is completely excluded!".format(
536
+ self._gid
537
+ )
510
538
  raise EmptySupplyCurvePointError(msg)
511
539
 
512
540
  def exclusion_weighted_mean(self, arr, drop_nan=True):
@@ -530,18 +558,18 @@ class SupplyCurvePoint(AbstractSupplyCurvePoint):
530
558
  """
531
559
 
532
560
  if len(arr.shape) == 2:
533
- x = arr[:, self._gids[self.bool_mask]].astype('float32')
561
+ x = arr[:, self._gids[self.bool_mask]].astype("float32")
534
562
  incl = self.include_mask_flat[self.bool_mask]
535
563
  x *= incl
536
564
  mean = x.sum(axis=1) / incl.sum()
537
565
 
538
566
  else:
539
- x = arr[self._gids[self.bool_mask]].astype('float32')
567
+ x = arr[self._gids[self.bool_mask]].astype("float32")
540
568
  incl = self.include_mask_flat[self.bool_mask]
541
569
 
542
570
  if np.isnan(x).all():
543
571
  return np.nan
544
- elif drop_nan and np.isnan(x).any():
572
+ if drop_nan and np.isnan(x).any():
545
573
  nan_mask = np.isnan(x)
546
574
  x = x[~nan_mask]
547
575
  incl = incl[~nan_mask]
@@ -600,10 +628,10 @@ class SupplyCurvePoint(AbstractSupplyCurvePoint):
600
628
  Sum of arr masked by the binary exclusions
601
629
  """
602
630
  if len(arr.shape) == 2:
603
- x = arr[:, self._gids[self.bool_mask]].astype('float32')
631
+ x = arr[:, self._gids[self.bool_mask]].astype("float32")
604
632
  ax = 1
605
633
  else:
606
- x = arr[self._gids[self.bool_mask]].astype('float32')
634
+ x = arr[self._gids[self.bool_mask]].astype("float32")
607
635
  ax = 0
608
636
 
609
637
  x *= self.include_mask_flat[self.bool_mask]
@@ -612,8 +640,17 @@ class SupplyCurvePoint(AbstractSupplyCurvePoint):
612
640
  return agg
613
641
 
614
642
  @classmethod
615
- def sc_mean(cls, gid, excl, tm_dset, data, excl_dict=None, resolution=64,
616
- exclusion_shape=None, close=True):
643
+ def sc_mean(
644
+ cls,
645
+ gid,
646
+ excl,
647
+ tm_dset,
648
+ data,
649
+ excl_dict=None,
650
+ resolution=64,
651
+ exclusion_shape=None,
652
+ close=True,
653
+ ):
617
654
  """
618
655
  Compute exclusions weight mean for the sc point from data
619
656
 
@@ -649,16 +686,29 @@ class SupplyCurvePoint(AbstractSupplyCurvePoint):
649
686
  ndarray
650
687
  Exclusions weighted means of data for supply curve point
651
688
  """
652
- kwargs = {"excl_dict": excl_dict, "resolution": resolution,
653
- "exclusion_shape": exclusion_shape, "close": close}
689
+ kwargs = {
690
+ "excl_dict": excl_dict,
691
+ "resolution": resolution,
692
+ "exclusion_shape": exclusion_shape,
693
+ "close": close,
694
+ }
654
695
  with cls(gid, excl, tm_dset, **kwargs) as point:
655
696
  means = point.exclusion_weighted_mean(data)
656
697
 
657
698
  return means
658
699
 
659
700
  @classmethod
660
- def sc_sum(cls, gid, excl, tm_dset, data, excl_dict=None, resolution=64,
661
- exclusion_shape=None, close=True):
701
+ def sc_sum(
702
+ cls,
703
+ gid,
704
+ excl,
705
+ tm_dset,
706
+ data,
707
+ excl_dict=None,
708
+ resolution=64,
709
+ exclusion_shape=None,
710
+ close=True,
711
+ ):
662
712
  """
663
713
  Compute the aggregate (sum) of data for the sc point
664
714
 
@@ -694,8 +744,12 @@ class SupplyCurvePoint(AbstractSupplyCurvePoint):
694
744
  ndarray
695
745
  Sum / aggregation of data for supply curve point
696
746
  """
697
- kwargs = {"excl_dict": excl_dict, "resolution": resolution,
698
- "exclusion_shape": exclusion_shape, "close": close}
747
+ kwargs = {
748
+ "excl_dict": excl_dict,
749
+ "resolution": resolution,
750
+ "exclusion_shape": exclusion_shape,
751
+ "close": close,
752
+ }
699
753
  with cls(gid, excl, tm_dset, **kwargs) as point:
700
754
  agg = point.aggregate(data)
701
755
 
@@ -718,9 +772,8 @@ class SupplyCurvePoint(AbstractSupplyCurvePoint):
718
772
  """
719
773
  if not data.size:
720
774
  return None
721
- else:
722
- # pd series is more flexible with non-numeric than stats mode
723
- return pd.Series(data).mode().values[0]
775
+ # pd series is more flexible with non-numeric than stats mode
776
+ return pd.Series(data).mode().values[0]
724
777
 
725
778
  @staticmethod
726
779
  def _categorize(data, incl_mult):
@@ -743,8 +796,10 @@ class SupplyCurvePoint(AbstractSupplyCurvePoint):
743
796
  total inclusions
744
797
  """
745
798
 
746
- data = {category: float(incl_mult[(data == category)].sum())
747
- for category in np.unique(data)}
799
+ data = {
800
+ category: float(incl_mult[(data == category)].sum())
801
+ for category in np.unique(data)
802
+ }
748
803
  data = jsonify_dict(data)
749
804
 
750
805
  return data
@@ -770,18 +825,22 @@ class SupplyCurvePoint(AbstractSupplyCurvePoint):
770
825
  data : float | int | str | None
771
826
  Result of applying method to data.
772
827
  """
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}
828
+ method_func = {
829
+ "mode": cls._mode,
830
+ "mean": np.mean,
831
+ "max": np.max,
832
+ "min": np.min,
833
+ "sum": np.sum,
834
+ "category": cls._categorize,
835
+ }
779
836
 
780
837
  if data is not None:
781
838
  method = method.lower()
782
839
  if method not in method_func:
783
- e = ('Cannot recognize data layer agg method: '
784
- '"{}". Can only {}'.format(method, list(method_func)))
840
+ e = (
841
+ "Cannot recognize data layer agg method: "
842
+ '"{}". Can only {}'.format(method, list(method_func))
843
+ )
785
844
  logger.error(e)
786
845
  raise ValueError(e)
787
846
 
@@ -789,14 +848,16 @@ class SupplyCurvePoint(AbstractSupplyCurvePoint):
789
848
  data = data.flatten()
790
849
 
791
850
  if data.shape != incl_mult.shape:
792
- e = ('Cannot aggregate data with shape that doesnt '
793
- 'match excl mult!')
851
+ e = (
852
+ "Cannot aggregate data with shape that doesnt "
853
+ "match excl mult!"
854
+ )
794
855
  logger.error(e)
795
856
  raise DataShapeError(e)
796
857
 
797
- if method == 'category':
798
- data = method_func['category'](data, incl_mult)
799
- elif method in ['mean', 'sum']:
858
+ if method == "category":
859
+ data = method_func["category"](data, incl_mult)
860
+ elif method in ["mean", "sum"]:
800
861
  data = data * incl_mult
801
862
  data = method_func[method](data)
802
863
  else:
@@ -828,32 +889,36 @@ class SupplyCurvePoint(AbstractSupplyCurvePoint):
828
889
 
829
890
  if data_layers is not None:
830
891
  for name, attrs in data_layers.items():
831
- excl_fp = attrs.get('fpath', self._excl_fpath)
892
+ excl_fp = attrs.get("fpath", self._excl_fpath)
832
893
  if excl_fp != self._excl_fpath:
833
- fh = ExclusionLayers(attrs['fpath'])
894
+ fh = ExclusionLayers(attrs["fpath"])
834
895
  else:
835
896
  fh = self.exclusions.excl_h5
836
897
 
837
- raw = fh[attrs['dset'], self.rows, self.cols]
838
- nodata = fh.get_nodata_value(attrs['dset'])
898
+ raw = fh[attrs["dset"], self.rows, self.cols]
899
+ nodata = fh.get_nodata_value(attrs["dset"])
839
900
 
840
901
  data = raw.flatten()[self.bool_mask]
841
902
  incl_mult = self.include_mask_flat[self.bool_mask].copy()
842
903
 
843
904
  if nodata is not None:
844
- valid_data_mask = (data != nodata)
905
+ valid_data_mask = data != nodata
845
906
  data = data[valid_data_mask]
846
907
  incl_mult = incl_mult[valid_data_mask]
847
908
 
848
909
  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))
910
+ m = (
911
+ 'Data layer "{}" has no valid data for '
912
+ "SC point gid {} because of exclusions "
913
+ "and/or nodata values in the data layer.".format(
914
+ name, self._gid
915
+ )
916
+ )
853
917
  logger.debug(m)
854
918
 
855
- data = self._agg_data_layer_method(data, incl_mult,
856
- attrs['method'])
919
+ data = self._agg_data_layer_method(
920
+ data, incl_mult, attrs["method"]
921
+ )
857
922
  summary[name] = data
858
923
 
859
924
  if excl_fp != self._excl_fpath:
@@ -865,10 +930,21 @@ class SupplyCurvePoint(AbstractSupplyCurvePoint):
865
930
  class AggregationSupplyCurvePoint(SupplyCurvePoint):
866
931
  """Generic single SC point to aggregate data from an h5 file."""
867
932
 
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):
933
+ def __init__(
934
+ self,
935
+ gid,
936
+ excl,
937
+ agg_h5,
938
+ tm_dset,
939
+ excl_dict=None,
940
+ inclusion_mask=None,
941
+ resolution=64,
942
+ excl_area=None,
943
+ exclusion_shape=None,
944
+ close=True,
945
+ gen_index=None,
946
+ apply_exclusions=True,
947
+ ):
872
948
  """
873
949
  Parameters
874
950
  ----------
@@ -911,13 +987,17 @@ class AggregationSupplyCurvePoint(SupplyCurvePoint):
911
987
  Flag to apply exclusions to the resource / generation gid's on
912
988
  initialization.
913
989
  """
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)
990
+ super().__init__(
991
+ gid,
992
+ excl,
993
+ tm_dset,
994
+ excl_dict=excl_dict,
995
+ inclusion_mask=inclusion_mask,
996
+ resolution=resolution,
997
+ excl_area=excl_area,
998
+ exclusion_shape=exclusion_shape,
999
+ close=close,
1000
+ )
921
1001
 
922
1002
  self._h5_fpath, self._h5 = self._parse_h5_file(agg_h5)
923
1003
 
@@ -927,9 +1007,12 @@ class AggregationSupplyCurvePoint(SupplyCurvePoint):
927
1007
  self._h5_gids = self._gids
928
1008
 
929
1009
  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))
1010
+ emsg = (
1011
+ "Supply curve point gid {} has no viable exclusion "
1012
+ 'points based on exclusions file: "{}"'.format(
1013
+ self._gid, self._excl_fpath
1014
+ )
1015
+ )
933
1016
  raise EmptySupplyCurvePointError(emsg)
934
1017
 
935
1018
  if apply_exclusions:
@@ -962,11 +1045,12 @@ class AggregationSupplyCurvePoint(SupplyCurvePoint):
962
1045
  elif issubclass(h5.__class__, MultiTimeResource):
963
1046
  h5_fpath = h5.h5_files
964
1047
  else:
965
- raise SupplyCurveInputError('SupplyCurvePoints needs a '
966
- '.h5 file path, or '
967
- 'Resource handler but '
968
- 'received: {}'
969
- .format(type(h5)))
1048
+ raise SupplyCurveInputError(
1049
+ "SupplyCurvePoints needs a "
1050
+ ".h5 file path, or "
1051
+ "Resource handler but "
1052
+ "received: {}".format(type(h5))
1053
+ )
970
1054
 
971
1055
  return h5_fpath, h5
972
1056
 
@@ -982,8 +1066,9 @@ class AggregationSupplyCurvePoint(SupplyCurvePoint):
982
1066
  self._h5_gids[exclude] = -1
983
1067
 
984
1068
  if (self._gids != -1).sum() == 0:
985
- msg = ('Supply curve point gid {} is completely excluded!'
986
- .format(self._gid))
1069
+ msg = "Supply curve point gid {} is completely excluded!".format(
1070
+ self._gid
1071
+ )
987
1072
  raise EmptySupplyCurvePointError(msg)
988
1073
 
989
1074
  def close(self):
@@ -1032,7 +1117,7 @@ class AggregationSupplyCurvePoint(SupplyCurvePoint):
1032
1117
  _h5 : Resource
1033
1118
  Resource h5 handler object.
1034
1119
  """
1035
- if self._h5 is None and '*' in self._h5_fpath:
1120
+ if self._h5 is None and "*" in self._h5_fpath:
1036
1121
  self._h5 = MultiTimeResource(self._h5_fpath)
1037
1122
  elif self._h5 is None:
1038
1123
  self._h5 = Resource(self._h5_fpath)
@@ -1043,15 +1128,22 @@ class AggregationSupplyCurvePoint(SupplyCurvePoint):
1043
1128
  def country(self):
1044
1129
  """Get the SC point country based on the resource meta data."""
1045
1130
  country = None
1046
- if 'country' in self.h5.meta and self.county is not None:
1131
+ county_not_none = self.county is not None
1132
+ if ResourceMetaField.COUNTRY in self.h5.meta and county_not_none:
1047
1133
  # make sure country and county are coincident
1048
- counties = self.h5.meta.loc[self.h5_gid_set, 'county'].values
1134
+ counties = self.h5.meta.loc[
1135
+ self.h5_gid_set, ResourceMetaField.COUNTY
1136
+ ].values
1049
1137
  iloc = np.where(counties == self.county)[0][0]
1050
- country = self.h5.meta.loc[self.h5_gid_set, 'country'].values
1138
+ country = self.h5.meta.loc[
1139
+ self.h5_gid_set, ResourceMetaField.COUNTRY
1140
+ ].values
1051
1141
  country = country[iloc]
1052
1142
 
1053
- elif 'country' in self.h5.meta:
1054
- country = self.h5.meta.loc[self.h5_gid_set, 'country'].mode()
1143
+ elif ResourceMetaField.COUNTRY in self.h5.meta:
1144
+ country = self.h5.meta.loc[
1145
+ self.h5_gid_set, ResourceMetaField.COUNTRY
1146
+ ].mode()
1055
1147
  country = country.values[0]
1056
1148
 
1057
1149
  return country
@@ -1060,15 +1152,21 @@ class AggregationSupplyCurvePoint(SupplyCurvePoint):
1060
1152
  def state(self):
1061
1153
  """Get the SC point state based on the resource meta data."""
1062
1154
  state = None
1063
- if 'state' in self.h5.meta and self.county is not None:
1155
+ if ResourceMetaField.STATE in self.h5.meta and self.county is not None:
1064
1156
  # make sure state and county are coincident
1065
- counties = self.h5.meta.loc[self.h5_gid_set, 'county'].values
1157
+ counties = self.h5.meta.loc[
1158
+ self.h5_gid_set, ResourceMetaField.COUNTY
1159
+ ].values
1066
1160
  iloc = np.where(counties == self.county)[0][0]
1067
- state = self.h5.meta.loc[self.h5_gid_set, 'state'].values
1161
+ state = self.h5.meta.loc[
1162
+ self.h5_gid_set, ResourceMetaField.STATE
1163
+ ].values
1068
1164
  state = state[iloc]
1069
1165
 
1070
- elif 'state' in self.h5.meta:
1071
- state = self.h5.meta.loc[self.h5_gid_set, 'state'].mode()
1166
+ elif ResourceMetaField.STATE in self.h5.meta:
1167
+ state = self.h5.meta.loc[
1168
+ self.h5_gid_set, ResourceMetaField.STATE
1169
+ ].mode()
1072
1170
  state = state.values[0]
1073
1171
 
1074
1172
  return state
@@ -1077,8 +1175,10 @@ class AggregationSupplyCurvePoint(SupplyCurvePoint):
1077
1175
  def county(self):
1078
1176
  """Get the SC point county based on the resource meta data."""
1079
1177
  county = None
1080
- if 'county' in self.h5.meta:
1081
- county = self.h5.meta.loc[self.h5_gid_set, 'county'].mode()
1178
+ if ResourceMetaField.COUNTY in self.h5.meta:
1179
+ county = self.h5.meta.loc[
1180
+ self.h5_gid_set, ResourceMetaField.COUNTY
1181
+ ].mode()
1082
1182
  county = county.values[0]
1083
1183
 
1084
1184
  return county
@@ -1087,8 +1187,10 @@ class AggregationSupplyCurvePoint(SupplyCurvePoint):
1087
1187
  def elevation(self):
1088
1188
  """Get the SC point elevation based on the resource meta data."""
1089
1189
  elevation = None
1090
- if 'elevation' in self.h5.meta:
1091
- elevation = self.h5.meta.loc[self.h5_gid_set, 'elevation'].mean()
1190
+ if ResourceMetaField.ELEVATION in self.h5.meta:
1191
+ elevation = self.h5.meta.loc[
1192
+ self.h5_gid_set, ResourceMetaField.ELEVATION
1193
+ ].mean()
1092
1194
 
1093
1195
  return elevation
1094
1196
 
@@ -1096,15 +1198,22 @@ class AggregationSupplyCurvePoint(SupplyCurvePoint):
1096
1198
  def timezone(self):
1097
1199
  """Get the SC point timezone based on the resource meta data."""
1098
1200
  timezone = None
1099
- if 'timezone' in self.h5.meta and self.county is not None:
1201
+ county_not_none = self.county is not None
1202
+ if ResourceMetaField.TIMEZONE in self.h5.meta and county_not_none:
1100
1203
  # make sure timezone flag and county are coincident
1101
- counties = self.h5.meta.loc[self.h5_gid_set, 'county'].values
1204
+ counties = self.h5.meta.loc[
1205
+ self.h5_gid_set, ResourceMetaField.COUNTY
1206
+ ].values
1102
1207
  iloc = np.where(counties == self.county)[0][0]
1103
- timezone = self.h5.meta.loc[self.h5_gid_set, 'timezone'].values
1208
+ timezone = self.h5.meta.loc[
1209
+ self.h5_gid_set, ResourceMetaField.TIMEZONE
1210
+ ].values
1104
1211
  timezone = timezone[iloc]
1105
1212
 
1106
- elif 'timezone' in self.h5.meta:
1107
- timezone = self.h5.meta.loc[self.h5_gid_set, 'timezone'].mode()
1213
+ elif ResourceMetaField.TIMEZONE in self.h5.meta:
1214
+ timezone = self.h5.meta.loc[
1215
+ self.h5_gid_set, ResourceMetaField.TIMEZONE
1216
+ ].mode()
1108
1217
  timezone = timezone.values[0]
1109
1218
 
1110
1219
  return timezone
@@ -1114,15 +1223,22 @@ class AggregationSupplyCurvePoint(SupplyCurvePoint):
1114
1223
  """Get the SC point offshore flag based on the resource meta data
1115
1224
  (if offshore column is present)."""
1116
1225
  offshore = None
1117
- if 'offshore' in self.h5.meta and self.county is not None:
1226
+ county_not_none = self.county is not None
1227
+ if ResourceMetaField.OFFSHORE in self.h5.meta and county_not_none:
1118
1228
  # make sure offshore flag and county are coincident
1119
- counties = self.h5.meta.loc[self.h5_gid_set, 'county'].values
1229
+ counties = self.h5.meta.loc[
1230
+ self.h5_gid_set, ResourceMetaField.COUNTY
1231
+ ].values
1120
1232
  iloc = np.where(counties == self.county)[0][0]
1121
- offshore = self.h5.meta.loc[self.h5_gid_set, 'offshore'].values
1233
+ offshore = self.h5.meta.loc[
1234
+ self.h5_gid_set, ResourceMetaField.OFFSHORE
1235
+ ].values
1122
1236
  offshore = offshore[iloc]
1123
1237
 
1124
- elif 'offshore' in self.h5.meta:
1125
- offshore = self.h5.meta.loc[self.h5_gid_set, 'offshore'].mode()
1238
+ elif ResourceMetaField.OFFSHORE in self.h5.meta:
1239
+ offshore = self.h5.meta.loc[
1240
+ self.h5_gid_set, ResourceMetaField.OFFSHORE
1241
+ ].mode()
1126
1242
  offshore = offshore.values[0]
1127
1243
 
1128
1244
  return offshore
@@ -1138,8 +1254,10 @@ class AggregationSupplyCurvePoint(SupplyCurvePoint):
1138
1254
  -------
1139
1255
  gid_counts : list
1140
1256
  """
1141
- gid_counts = [self.include_mask_flat[(self._h5_gids == gid)].sum()
1142
- for gid in self.h5_gid_set]
1257
+ gid_counts = [
1258
+ self.include_mask_flat[(self._h5_gids == gid)].sum()
1259
+ for gid in self.h5_gid_set
1260
+ ]
1143
1261
 
1144
1262
  return gid_counts
1145
1263
 
@@ -1153,28 +1271,41 @@ class AggregationSupplyCurvePoint(SupplyCurvePoint):
1153
1271
  pandas.Series
1154
1272
  List of supply curve point's meta data
1155
1273
  """
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
- }
1274
+ meta = {
1275
+ SupplyCurveField.SC_POINT_GID: self.sc_point_gid,
1276
+ SupplyCurveField.SOURCE_GIDS: self.h5_gid_set,
1277
+ SupplyCurveField.GID_COUNTS: self.gid_counts,
1278
+ SupplyCurveField.N_GIDS: self.n_gids,
1279
+ SupplyCurveField.AREA_SQ_KM: self.area,
1280
+ SupplyCurveField.LATITUDE: self.latitude,
1281
+ SupplyCurveField.LONGITUDE: self.longitude,
1282
+ SupplyCurveField.COUNTRY: self.country,
1283
+ SupplyCurveField.STATE: self.state,
1284
+ SupplyCurveField.COUNTY: self.county,
1285
+ SupplyCurveField.ELEVATION: self.elevation,
1286
+ SupplyCurveField.TIMEZONE: self.timezone,
1287
+ }
1169
1288
  meta = pd.Series(meta)
1170
1289
 
1171
1290
  return meta
1172
1291
 
1173
1292
  @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):
1293
+ def run(
1294
+ cls,
1295
+ gid,
1296
+ excl,
1297
+ agg_h5,
1298
+ tm_dset,
1299
+ *agg_dset,
1300
+ agg_method="mean",
1301
+ excl_dict=None,
1302
+ inclusion_mask=None,
1303
+ resolution=64,
1304
+ excl_area=None,
1305
+ exclusion_shape=None,
1306
+ close=True,
1307
+ gen_index=None,
1308
+ ):
1178
1309
  """
1179
1310
  Compute exclusions weight mean for the sc point from data
1180
1311
 
@@ -1228,30 +1359,34 @@ class AggregationSupplyCurvePoint(SupplyCurvePoint):
1228
1359
  Given datasets and meta data aggregated to supply curve points
1229
1360
  """
1230
1361
  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}
1362
+ agg_dset = (agg_dset,)
1363
+
1364
+ kwargs = {
1365
+ "excl_dict": excl_dict,
1366
+ "inclusion_mask": inclusion_mask,
1367
+ "resolution": resolution,
1368
+ "excl_area": excl_area,
1369
+ "exclusion_shape": exclusion_shape,
1370
+ "close": close,
1371
+ "gen_index": gen_index,
1372
+ }
1240
1373
 
1241
1374
  with cls(gid, excl, agg_h5, tm_dset, **kwargs) as point:
1242
- if agg_method.lower().startswith('mean'):
1375
+ if agg_method.lower().startswith("mean"):
1243
1376
  agg_method = point.exclusion_weighted_mean
1244
- elif agg_method.lower().startswith(('sum', 'agg')):
1377
+ elif agg_method.lower().startswith(("sum", "agg")):
1245
1378
  agg_method = point.aggregate
1246
- elif 'wind_dir' in agg_method.lower():
1379
+ elif "wind_dir" in agg_method.lower():
1247
1380
  agg_method = point.mean_wind_dirs
1248
1381
  else:
1249
- msg = ('Aggregation method must be either mean, '
1250
- 'sum/aggregate, or wind_dir')
1382
+ msg = (
1383
+ "Aggregation method must be either mean, "
1384
+ "sum/aggregate, or wind_dir"
1385
+ )
1251
1386
  logger.error(msg)
1252
1387
  raise ValueError(msg)
1253
1388
 
1254
- out = {'meta': point.summary}
1389
+ out = {"meta": point.summary}
1255
1390
 
1256
1391
  for dset in agg_dset:
1257
1392
  ds = point.h5.open_dataset(dset)
@@ -1265,15 +1400,31 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
1265
1400
  respective generation and resource data."""
1266
1401
 
1267
1402
  # 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):
1403
+ POWER_DENSITY = {"pv": 36, "wind": 3}
1404
+
1405
+ def __init__(
1406
+ self,
1407
+ gid,
1408
+ excl,
1409
+ gen,
1410
+ tm_dset,
1411
+ gen_index,
1412
+ excl_dict=None,
1413
+ inclusion_mask=None,
1414
+ res_class_dset=None,
1415
+ res_class_bin=None,
1416
+ excl_area=None,
1417
+ power_density=None,
1418
+ cf_dset="cf_mean-means",
1419
+ lcoe_dset="lcoe_fcr-means",
1420
+ h5_dsets=None,
1421
+ resolution=64,
1422
+ exclusion_shape=None,
1423
+ close=False,
1424
+ friction_layer=None,
1425
+ recalc_lcoe=True,
1426
+ apply_exclusions=True,
1427
+ ):
1277
1428
  """
1278
1429
  Parameters
1279
1430
  ----------
@@ -1360,26 +1511,36 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
1360
1511
  self._friction_layer = friction_layer
1361
1512
  self._recalc_lcoe = recalc_lcoe
1362
1513
 
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)
1514
+ super().__init__(
1515
+ gid,
1516
+ excl,
1517
+ gen,
1518
+ tm_dset,
1519
+ excl_dict=excl_dict,
1520
+ inclusion_mask=inclusion_mask,
1521
+ resolution=resolution,
1522
+ excl_area=excl_area,
1523
+ exclusion_shape=exclusion_shape,
1524
+ close=close,
1525
+ apply_exclusions=False,
1526
+ )
1370
1527
 
1371
1528
  self._res_gid_set = None
1372
1529
  self._gen_gid_set = None
1373
1530
 
1374
1531
  self._gen_fpath, self._gen = self._h5_fpath, self._h5
1375
1532
 
1376
- self._gen_gids, self._res_gids = self._map_gen_gids(self._gids,
1377
- gen_index)
1533
+ self._gen_gids, self._res_gids = self._map_gen_gids(
1534
+ self._gids, gen_index
1535
+ )
1378
1536
  self._gids = self._gen_gids
1379
1537
  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))
1538
+ emsg = (
1539
+ "Supply curve point gid {} has no viable exclusion "
1540
+ 'points based on exclusions file: "{}"'.format(
1541
+ self._gid, self._excl_fpath
1542
+ )
1543
+ )
1383
1544
  raise EmptySupplyCurvePointError(emsg)
1384
1545
 
1385
1546
  if apply_exclusions:
@@ -1401,7 +1562,7 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
1401
1562
  Mean of flat_arr masked by the binary exclusions then weighted by
1402
1563
  the non-zero exclusions.
1403
1564
  """
1404
- x = flat_arr[self._gen_gids[self.bool_mask]].astype('float32')
1565
+ x = flat_arr[self._gen_gids[self.bool_mask]].astype("float32")
1405
1566
  incl = self.include_mask_flat[self.bool_mask]
1406
1567
  x *= incl
1407
1568
  mean = x.sum() / incl.sum()
@@ -1476,8 +1637,10 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
1476
1637
  gid_counts : list
1477
1638
  List of exclusion pixels in each resource/generation gid.
1478
1639
  """
1479
- gid_counts = [self.include_mask_flat[(self._res_gids == gid)].sum()
1480
- for gid in self.res_gid_set]
1640
+ gid_counts = [
1641
+ self.include_mask_flat[(self._res_gids == gid)].sum()
1642
+ for gid in self.res_gid_set
1643
+ ]
1481
1644
 
1482
1645
  return gid_counts
1483
1646
 
@@ -1495,10 +1658,9 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
1495
1658
  if isinstance(self._res_class_dset, np.ndarray):
1496
1659
  return self._res_class_dset
1497
1660
 
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]
1661
+ if self._res_data is None:
1662
+ if self._res_class_dset in self.gen.datasets:
1663
+ self._res_data = self.gen[self._res_class_dset]
1502
1664
 
1503
1665
  return self._res_data
1504
1666
 
@@ -1516,10 +1678,9 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
1516
1678
  if isinstance(self._cf_dset, np.ndarray):
1517
1679
  return self._cf_dset
1518
1680
 
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]
1681
+ if self._gen_data is None:
1682
+ if self._cf_dset in self.gen.datasets:
1683
+ self._gen_data = self.gen[self._cf_dset]
1523
1684
 
1524
1685
  return self._gen_data
1525
1686
 
@@ -1537,10 +1698,9 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
1537
1698
  if isinstance(self._lcoe_dset, np.ndarray):
1538
1699
  return self._lcoe_dset
1539
1700
 
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]
1701
+ if self._lcoe_data is None:
1702
+ if self._lcoe_dset in self.gen.datasets:
1703
+ self._lcoe_data = self.gen[self._lcoe_dset]
1544
1704
 
1545
1705
  return self._lcoe_data
1546
1706
 
@@ -1578,19 +1738,31 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
1578
1738
  # year CF, but the output should be identical to the original LCOE and
1579
1739
  # so is not consequential).
1580
1740
  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
- mean_lcoe = lcoe_fcr(
1589
- self.mean_h5_dsets_data['fixed_charge_rate'],
1590
- self.mean_h5_dsets_data['capital_cost'],
1591
- self.mean_h5_dsets_data['fixed_operating_cost'],
1592
- aep,
1593
- self.mean_h5_dsets_data['variable_operating_cost'])
1741
+ required = (
1742
+ "fixed_charge_rate",
1743
+ "capital_cost",
1744
+ "fixed_operating_cost",
1745
+ "variable_operating_cost",
1746
+ "system_capacity",
1747
+ )
1748
+ if self.mean_h5_dsets_data is not None and all(
1749
+ k in self.mean_h5_dsets_data for k in required
1750
+ ):
1751
+ aep = (
1752
+ self.mean_h5_dsets_data["system_capacity"]
1753
+ * self.mean_cf
1754
+ * 8760
1755
+ )
1756
+ # Note the AEP computation uses the SAM config
1757
+ # `system_capacity`, so no need to scale `capital_cost`
1758
+ # or `fixed_operating_cost` by anything
1759
+ mean_lcoe = lcoe_fcr(
1760
+ self.mean_h5_dsets_data["fixed_charge_rate"],
1761
+ self.mean_h5_dsets_data["capital_cost"],
1762
+ self.mean_h5_dsets_data["fixed_operating_cost"],
1763
+ aep,
1764
+ self.mean_h5_dsets_data["variable_operating_cost"],
1765
+ )
1594
1766
 
1595
1767
  # alternative if lcoe was not able to be re-calculated from
1596
1768
  # multi year mean CF
@@ -1676,26 +1848,31 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
1676
1848
  """
1677
1849
 
1678
1850
  if self._power_density is None:
1679
- tech = self.gen.meta['reV_tech'][0]
1851
+ tech = self.gen.meta["reV_tech"][0]
1680
1852
  if tech in self.POWER_DENSITY:
1681
1853
  self._power_density = self.POWER_DENSITY[tech]
1682
1854
  else:
1683
- warn('Could not recognize reV technology in generation meta '
1684
- 'data: "{}". Cannot lookup an appropriate power density '
1685
- 'to calculate SC point capacity.'.format(tech))
1855
+ warn(
1856
+ "Could not recognize reV technology in generation meta "
1857
+ 'data: "{}". Cannot lookup an appropriate power density '
1858
+ "to calculate SC point capacity.".format(tech)
1859
+ )
1686
1860
 
1687
1861
  elif isinstance(self._power_density, pd.DataFrame):
1688
1862
  self._pd_obj = self._power_density
1689
1863
 
1690
1864
  missing = set(self.res_gid_set) - set(self._pd_obj.index.values)
1691
1865
  if any(missing):
1692
- msg = ('Variable power density input is missing the '
1693
- 'following resource GIDs: {}'.format(missing))
1866
+ msg = (
1867
+ "Variable power density input is missing the "
1868
+ "following resource GIDs: {}".format(missing)
1869
+ )
1694
1870
  logger.error(msg)
1695
1871
  raise FileInputError(msg)
1696
1872
 
1697
- pds = self._pd_obj.loc[self._res_gids[self.bool_mask],
1698
- 'power_density'].values
1873
+ pds = self._pd_obj.loc[
1874
+ self._res_gids[self.bool_mask], "power_density"
1875
+ ].values
1699
1876
  pds = pds.astype(np.float32)
1700
1877
  pds *= self.include_mask_flat[self.bool_mask]
1701
1878
  denom = self.include_mask_flat[self.bool_mask].sum()
@@ -1721,18 +1898,20 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
1721
1898
  return None
1722
1899
 
1723
1900
  ilr = self.gen["dc_ac_ratio", self._gen_gids[self.bool_mask]]
1724
- ilr = ilr.astype('float32')
1901
+ ilr = ilr.astype("float32")
1725
1902
  weights = self.include_mask_flat[self.bool_mask]
1726
1903
  if self._power_density_ac is None:
1727
- tech = self.gen.meta['reV_tech'][0]
1904
+ tech = self.gen.meta["reV_tech"][0]
1728
1905
  if tech in self.POWER_DENSITY:
1729
1906
  power_density_ac = self.POWER_DENSITY[tech] / ilr
1730
1907
  power_density_ac *= weights
1731
1908
  power_density_ac = power_density_ac.sum() / weights.sum()
1732
1909
  else:
1733
- warn('Could not recognize reV technology in generation meta '
1734
- 'data: "{}". Cannot lookup an appropriate power density '
1735
- 'to calculate SC point capacity.'.format(tech))
1910
+ warn(
1911
+ "Could not recognize reV technology in generation meta "
1912
+ 'data: "{}". Cannot lookup an appropriate power density '
1913
+ "to calculate SC point capacity.".format(tech)
1914
+ )
1736
1915
  power_density_ac = None
1737
1916
 
1738
1917
  elif isinstance(self._power_density_ac, pd.DataFrame):
@@ -1740,13 +1919,16 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
1740
1919
 
1741
1920
  missing = set(self.res_gid_set) - set(self._pd_obj.index.values)
1742
1921
  if any(missing):
1743
- msg = ('Variable power density input is missing the '
1744
- 'following resource GIDs: {}'.format(missing))
1922
+ msg = (
1923
+ "Variable power density input is missing the "
1924
+ "following resource GIDs: {}".format(missing)
1925
+ )
1745
1926
  logger.error(msg)
1746
1927
  raise FileInputError(msg)
1747
1928
 
1748
- pds = self._pd_obj.loc[self._res_gids[self.bool_mask],
1749
- 'power_density'].values
1929
+ pds = self._pd_obj.loc[
1930
+ self._res_gids[self.bool_mask], "power_density"
1931
+ ].values
1750
1932
  power_density_ac = pds.astype(np.float32) / ilr
1751
1933
  power_density_ac *= weights
1752
1934
  power_density_ac = power_density_ac.sum() / weights.sum()
@@ -1796,6 +1978,98 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
1796
1978
 
1797
1979
  return self.area * self.power_density_ac
1798
1980
 
1981
+ @property
1982
+ def sc_point_capital_cost(self):
1983
+ """Get the capital cost for the entire SC point.
1984
+
1985
+ This method scales the capital cost based on the included-area
1986
+ capacity. The calculation requires 'capital_cost' and
1987
+ 'system_capacity' in the generation file and passed through as
1988
+ `h5_dsets`, otherwise it returns `None`.
1989
+
1990
+ Returns
1991
+ -------
1992
+ sc_point_capital_cost : float | None
1993
+ Total supply curve point capital cost ($).
1994
+ """
1995
+ if self.mean_h5_dsets_data is None:
1996
+ return None
1997
+
1998
+ required = ("capital_cost", "system_capacity")
1999
+ if not all(k in self.mean_h5_dsets_data for k in required):
2000
+ return None
2001
+
2002
+ cap_cost_per_mw = (
2003
+ self.mean_h5_dsets_data["capital_cost"]
2004
+ / self.mean_h5_dsets_data["system_capacity"]
2005
+ )
2006
+ return cap_cost_per_mw * self.capacity
2007
+
2008
+ @property
2009
+ def sc_point_fixed_operating_cost(self):
2010
+ """Get the fixed operating cost for the entire SC point.
2011
+
2012
+ This method scales the fixed operating cost based on the
2013
+ included-area capacity. The calculation requires
2014
+ 'fixed_operating_cost' and 'system_capacity' in the generation
2015
+ file and passed through as `h5_dsets`, otherwise it returns
2016
+ `None`.
2017
+
2018
+ Returns
2019
+ -------
2020
+ sc_point_fixed_operating_cost : float | None
2021
+ Total supply curve point fixed operating cost ($).
2022
+ """
2023
+ if self.mean_h5_dsets_data is None:
2024
+ return None
2025
+
2026
+ required = ("fixed_operating_cost", "system_capacity")
2027
+ if not all(k in self.mean_h5_dsets_data for k in required):
2028
+ return None
2029
+
2030
+ fixed_cost_per_mw = (
2031
+ self.mean_h5_dsets_data["fixed_operating_cost"]
2032
+ / self.mean_h5_dsets_data["system_capacity"]
2033
+ )
2034
+ return fixed_cost_per_mw * self.capacity
2035
+
2036
+ @property
2037
+ def sc_point_annual_energy(self):
2038
+ """Get the total annual energy (MWh) for the entire SC point.
2039
+
2040
+ This value is computed using the capacity of the supply curve
2041
+ point as well as the mean capacity factor. If the mean capacity
2042
+ factor is `None`, this value will also be `None`.
2043
+
2044
+ Returns
2045
+ -------
2046
+ sc_point_annual_energy : float | None
2047
+ Total annual energy (MWh) for the entire SC point.
2048
+ """
2049
+ if self.mean_cf is None:
2050
+ return None
2051
+
2052
+ return self.mean_cf * self.capacity * 8760
2053
+
2054
+ @property
2055
+ def sc_point_annual_energy_ac(self):
2056
+ """Get the total AC annual energy (MWh) for the entire SC point.
2057
+
2058
+ This value is computed using the AC capacity of the supply curve
2059
+ point as well as the mean capacity factor. If either the mean
2060
+ capacity factor or the AC capacity value is `None`, this value
2061
+ will also be `None`.
2062
+
2063
+ Returns
2064
+ -------
2065
+ sc_point_annual_energy_ac : float | None
2066
+ Total AC annual energy (MWh) for the entire SC point.
2067
+ """
2068
+ if self.mean_cf is None or self.capacity_ac is None:
2069
+ return None
2070
+
2071
+ return self.mean_cf * self.capacity_ac * 8760
2072
+
1799
2073
  @property
1800
2074
  def h5_dsets_data(self):
1801
2075
  """Get any additional/supplemental h5 dataset data to summarize.
@@ -1818,10 +2092,13 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
1818
2092
  _h5_dsets_data = self._h5_dsets
1819
2093
 
1820
2094
  elif self._h5_dsets is not None:
1821
- e = ('Cannot recognize h5_dsets input type, should be None, '
1822
- 'a list of dataset names, or a dictionary or '
1823
- 'pre-extracted data. Received: {} {}'
1824
- .format(type(self._h5_dsets), self._h5_dsets))
2095
+ e = (
2096
+ "Cannot recognize h5_dsets input type, should be None, "
2097
+ "a list of dataset names, or a dictionary or "
2098
+ "pre-extracted data. Received: {} {}".format(
2099
+ type(self._h5_dsets), self._h5_dsets
2100
+ )
2101
+ )
1825
2102
  logger.error(e)
1826
2103
  raise TypeError(e)
1827
2104
 
@@ -1864,8 +2141,10 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
1864
2141
  self._incl_mask = self._incl_mask.flatten()
1865
2142
 
1866
2143
  if (self._gen_gids != -1).sum() == 0:
1867
- msg = ('Supply curve point gid {} is completely excluded for res '
1868
- 'bin: {}'.format(self._gid, self._res_class_bin))
2144
+ msg = (
2145
+ "Supply curve point gid {} is completely excluded for res "
2146
+ "bin: {}".format(self._gid, self._res_class_bin)
2147
+ )
1869
2148
  raise EmptySupplyCurvePointError(msg)
1870
2149
 
1871
2150
  def _resource_exclusion(self, boolean_exclude):
@@ -1883,14 +2162,16 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
1883
2162
  outside of current resource class bin.
1884
2163
  """
1885
2164
 
1886
- if (self._res_class_dset is not None
1887
- and self._res_class_bin is not None):
1888
-
2165
+ if (
2166
+ self._res_class_dset is not None
2167
+ and self._res_class_bin is not None
2168
+ ):
1889
2169
  rex = self.res_data[self._gen_gids]
1890
- rex = ((rex < np.min(self._res_class_bin))
1891
- | (rex >= np.max(self._res_class_bin)))
2170
+ rex = (rex < np.min(self._res_class_bin)) | (
2171
+ rex >= np.max(self._res_class_bin)
2172
+ )
1892
2173
 
1893
- boolean_exclude = (boolean_exclude | rex)
2174
+ boolean_exclude = boolean_exclude | rex
1894
2175
 
1895
2176
  return boolean_exclude
1896
2177
 
@@ -1910,37 +2191,50 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
1910
2191
  Dictionary of summary outputs for this sc point.
1911
2192
  """
1912
2193
 
1913
- ARGS = {'res_gids': self.res_gid_set,
1914
- 'gen_gids': self.gen_gid_set,
1915
- 'gid_counts': self.gid_counts,
1916
- 'n_gids': self.n_gids,
1917
- 'mean_cf': self.mean_cf,
1918
- 'mean_lcoe': self.mean_lcoe,
1919
- 'mean_res': self.mean_res,
1920
- 'capacity': self.capacity,
1921
- 'area_sq_km': self.area,
1922
- 'latitude': self.latitude,
1923
- 'longitude': self.longitude,
1924
- 'country': self.country,
1925
- 'state': self.state,
1926
- 'county': self.county,
1927
- 'elevation': self.elevation,
1928
- 'timezone': self.timezone,
1929
- }
1930
-
1931
- if self.capacity_ac is not None:
1932
- ARGS['capacity_ac'] = self.capacity_ac
1933
-
1934
- if self.offshore is not None:
1935
- ARGS['offshore'] = self.offshore
2194
+ ARGS = {
2195
+ SupplyCurveField.LATITUDE: self.latitude,
2196
+ SupplyCurveField.LONGITUDE: self.longitude,
2197
+ SupplyCurveField.TIMEZONE: self.timezone,
2198
+ SupplyCurveField.COUNTRY: self.country,
2199
+ SupplyCurveField.STATE: self.state,
2200
+ SupplyCurveField.COUNTY: self.county,
2201
+ SupplyCurveField.ELEVATION: self.elevation,
2202
+ SupplyCurveField.RES_GIDS: self.res_gid_set,
2203
+ SupplyCurveField.GEN_GIDS: self.gen_gid_set,
2204
+ SupplyCurveField.GID_COUNTS: self.gid_counts,
2205
+ SupplyCurveField.N_GIDS: self.n_gids,
2206
+ SupplyCurveField.MEAN_CF: self.mean_cf,
2207
+ SupplyCurveField.MEAN_LCOE: self.mean_lcoe,
2208
+ SupplyCurveField.MEAN_RES: self.mean_res,
2209
+ SupplyCurveField.CAPACITY: self.capacity,
2210
+ SupplyCurveField.AREA_SQ_KM: self.area,
2211
+ }
2212
+
2213
+ extra_atts = {
2214
+ SupplyCurveField.CAPACITY_AC: self.capacity_ac,
2215
+ SupplyCurveField.OFFSHORE: self.offshore,
2216
+ SupplyCurveField.SC_POINT_CAPITAL_COST: self.sc_point_capital_cost,
2217
+ SupplyCurveField.SC_POINT_FIXED_OPERATING_COST: (
2218
+ self.sc_point_fixed_operating_cost
2219
+ ),
2220
+ SupplyCurveField.SC_POINT_ANNUAL_ENERGY: (
2221
+ self.sc_point_annual_energy
2222
+ ),
2223
+ SupplyCurveField.SC_POINT_ANNUAL_ENERGY_AC: (
2224
+ self.sc_point_annual_energy_ac
2225
+ ),
2226
+ }
2227
+ for attr, value in extra_atts.items():
2228
+ if value is not None:
2229
+ ARGS[attr] = value
1936
2230
 
1937
2231
  if self._friction_layer is not None:
1938
- ARGS['mean_friction'] = self.mean_friction
1939
- ARGS['mean_lcoe_friction'] = self.mean_lcoe_friction
2232
+ ARGS[SupplyCurveField.MEAN_FRICTION] = self.mean_friction
2233
+ ARGS[SupplyCurveField.MEAN_LCOE_FRICTION] = self.mean_lcoe_friction
1940
2234
 
1941
2235
  if self._h5_dsets is not None:
1942
2236
  for dset, data in self.mean_h5_dsets_data.items():
1943
- ARGS['mean_{}'.format(dset)] = data
2237
+ ARGS["mean_{}".format(dset)] = data
1944
2238
 
1945
2239
  if args is None:
1946
2240
  args = list(ARGS.keys())
@@ -1950,8 +2244,11 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
1950
2244
  if arg in ARGS:
1951
2245
  summary[arg] = ARGS[arg]
1952
2246
  else:
1953
- warn('Cannot find "{}" as an available SC self summary '
1954
- 'output', OutputWarning)
2247
+ warn(
2248
+ 'Cannot find "{}" as an available SC self summary '
2249
+ "output",
2250
+ OutputWarning,
2251
+ )
1955
2252
 
1956
2253
  return summary
1957
2254
 
@@ -1977,21 +2274,47 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
1977
2274
  """
1978
2275
 
1979
2276
  eos = EconomiesOfScale(cap_cost_scale, summary)
1980
- summary['raw_lcoe'] = eos.raw_lcoe
1981
- summary['mean_lcoe'] = eos.scaled_lcoe
1982
- summary['capital_cost_scalar'] = eos.capital_cost_scalar
2277
+ summary[SupplyCurveField.RAW_LCOE] = eos.raw_lcoe
2278
+ summary[SupplyCurveField.MEAN_LCOE] = eos.scaled_lcoe
2279
+ summary[SupplyCurveField.CAPITAL_COST_SCALAR] = eos.capital_cost_scalar
2280
+ summary[SupplyCurveField.SCALED_CAPITAL_COST] = eos.scaled_capital_cost
2281
+ if SupplyCurveField.SC_POINT_CAPITAL_COST in summary:
2282
+ scaled_costs = (
2283
+ summary[SupplyCurveField.SC_POINT_CAPITAL_COST]
2284
+ * eos.capital_cost_scalar
2285
+ )
2286
+ summary[SupplyCurveField.SCALED_SC_POINT_CAPITAL_COST] = (
2287
+ scaled_costs
2288
+ )
1983
2289
 
1984
2290
  return summary
1985
2291
 
1986
2292
  @classmethod
1987
- def summarize(cls, gid, excl_fpath, gen_fpath, tm_dset, gen_index,
1988
- excl_dict=None, inclusion_mask=None,
1989
- res_class_dset=None, res_class_bin=None,
1990
- excl_area=None, power_density=None,
1991
- cf_dset='cf_mean-means', lcoe_dset='lcoe_fcr-means',
1992
- h5_dsets=None, resolution=64, exclusion_shape=None,
1993
- close=False, friction_layer=None, args=None,
1994
- data_layers=None, cap_cost_scale=None, recalc_lcoe=True):
2293
+ def summarize(
2294
+ cls,
2295
+ gid,
2296
+ excl_fpath,
2297
+ gen_fpath,
2298
+ tm_dset,
2299
+ gen_index,
2300
+ excl_dict=None,
2301
+ inclusion_mask=None,
2302
+ res_class_dset=None,
2303
+ res_class_bin=None,
2304
+ excl_area=None,
2305
+ power_density=None,
2306
+ cf_dset="cf_mean-means",
2307
+ lcoe_dset="lcoe_fcr-means",
2308
+ h5_dsets=None,
2309
+ resolution=64,
2310
+ exclusion_shape=None,
2311
+ close=False,
2312
+ friction_layer=None,
2313
+ args=None,
2314
+ data_layers=None,
2315
+ cap_cost_scale=None,
2316
+ recalc_lcoe=True,
2317
+ ):
1995
2318
  """Get a summary dictionary of a single supply curve point.
1996
2319
 
1997
2320
  Parameters
@@ -2078,24 +2401,26 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
2078
2401
  summary : dict
2079
2402
  Dictionary of summary outputs for this sc point.
2080
2403
  """
2081
- kwargs = {"excl_dict": excl_dict,
2082
- "inclusion_mask": inclusion_mask,
2083
- "res_class_dset": res_class_dset,
2084
- "res_class_bin": res_class_bin,
2085
- "excl_area": excl_area,
2086
- "power_density": power_density,
2087
- "cf_dset": cf_dset,
2088
- "lcoe_dset": lcoe_dset,
2089
- "h5_dsets": h5_dsets,
2090
- "resolution": resolution,
2091
- "exclusion_shape": exclusion_shape,
2092
- "close": close,
2093
- 'friction_layer': friction_layer,
2094
- 'recalc_lcoe': recalc_lcoe,
2095
- }
2096
-
2097
- with cls(gid, excl_fpath, gen_fpath, tm_dset, gen_index,
2098
- **kwargs) as point:
2404
+ kwargs = {
2405
+ "excl_dict": excl_dict,
2406
+ "inclusion_mask": inclusion_mask,
2407
+ "res_class_dset": res_class_dset,
2408
+ "res_class_bin": res_class_bin,
2409
+ "excl_area": excl_area,
2410
+ "power_density": power_density,
2411
+ "cf_dset": cf_dset,
2412
+ "lcoe_dset": lcoe_dset,
2413
+ "h5_dsets": h5_dsets,
2414
+ "resolution": resolution,
2415
+ "exclusion_shape": exclusion_shape,
2416
+ "close": close,
2417
+ "friction_layer": friction_layer,
2418
+ "recalc_lcoe": recalc_lcoe,
2419
+ }
2420
+
2421
+ with cls(
2422
+ gid, excl_fpath, gen_fpath, tm_dset, gen_index, **kwargs
2423
+ ) as point:
2099
2424
  summary = point.point_summary(args=args)
2100
2425
 
2101
2426
  if data_layers is not None: