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.
- {NREL_reV-0.8.7.dist-info → NREL_reV-0.9.0.dist-info}/METADATA +13 -10
- {NREL_reV-0.8.7.dist-info → NREL_reV-0.9.0.dist-info}/RECORD +43 -43
- {NREL_reV-0.8.7.dist-info → NREL_reV-0.9.0.dist-info}/WHEEL +1 -1
- reV/SAM/SAM.py +217 -133
- reV/SAM/econ.py +18 -14
- reV/SAM/generation.py +611 -422
- reV/SAM/windbos.py +93 -79
- reV/bespoke/bespoke.py +681 -377
- reV/bespoke/cli_bespoke.py +2 -0
- reV/bespoke/place_turbines.py +187 -43
- reV/config/output_request.py +2 -1
- reV/config/project_points.py +218 -140
- reV/econ/econ.py +166 -114
- reV/econ/economies_of_scale.py +91 -45
- reV/generation/base.py +331 -184
- reV/generation/generation.py +326 -200
- reV/generation/output_attributes/lcoe_fcr_inputs.json +38 -3
- reV/handlers/__init__.py +0 -1
- reV/handlers/exclusions.py +16 -15
- reV/handlers/multi_year.py +57 -26
- reV/handlers/outputs.py +6 -5
- reV/handlers/transmission.py +44 -27
- reV/hybrids/hybrid_methods.py +30 -30
- reV/hybrids/hybrids.py +305 -189
- reV/nrwal/nrwal.py +262 -168
- reV/qa_qc/cli_qa_qc.py +14 -10
- reV/qa_qc/qa_qc.py +217 -119
- reV/qa_qc/summary.py +228 -146
- reV/rep_profiles/rep_profiles.py +349 -230
- reV/supply_curve/aggregation.py +349 -188
- reV/supply_curve/competitive_wind_farms.py +90 -48
- reV/supply_curve/exclusions.py +138 -85
- reV/supply_curve/extent.py +75 -50
- reV/supply_curve/points.py +735 -390
- reV/supply_curve/sc_aggregation.py +357 -248
- reV/supply_curve/supply_curve.py +604 -347
- reV/supply_curve/tech_mapping.py +144 -82
- reV/utilities/__init__.py +274 -16
- reV/utilities/pytest_utils.py +8 -4
- reV/version.py +1 -1
- {NREL_reV-0.8.7.dist-info → NREL_reV-0.9.0.dist-info}/LICENSE +0 -0
- {NREL_reV-0.8.7.dist-info → NREL_reV-0.9.0.dist-info}/entry_points.txt +0 -0
- {NREL_reV-0.8.7.dist-info → NREL_reV-0.9.0.dist-info}/top_level.txt +0 -0
reV/supply_curve/points.py
CHANGED
@@ -2,26 +2,30 @@
|
|
2
2
|
"""
|
3
3
|
reV supply curve points frameworks.
|
4
4
|
"""
|
5
|
-
|
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
|
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
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
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
|
51
|
-
|
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
|
73
|
-
"""Parse
|
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).
|
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
|
-
|
89
|
+
self._sc_row_ind = self._gid // n_sc_cols
|
90
|
+
self._sc_col_ind = self._gid % n_sc_cols
|
96
91
|
|
97
|
-
|
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
|
-
"""
|
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 = (
|
176
|
-
|
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__(
|
196
|
-
|
197
|
-
|
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 = (
|
249
|
-
|
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(
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
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 = (
|
316
|
-
|
317
|
-
|
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(
|
347
|
-
|
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[
|
363
|
-
lons = self.exclusions.excl_h5[
|
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 = (
|
442
|
-
|
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 =
|
509
|
-
|
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(
|
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(
|
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
|
-
|
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(
|
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(
|
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(
|
616
|
-
|
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 = {
|
653
|
-
|
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(
|
661
|
-
|
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 = {
|
698
|
-
|
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
|
-
|
722
|
-
|
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 = {
|
747
|
-
|
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 = {
|
774
|
-
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
|
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 = (
|
784
|
-
|
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 = (
|
793
|
-
|
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 ==
|
798
|
-
data = method_func[
|
799
|
-
elif method in [
|
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(
|
903
|
+
excl_fp = attrs.get("fpath", self._excl_fpath)
|
832
904
|
if excl_fp != self._excl_fpath:
|
833
|
-
fh = ExclusionLayers(attrs[
|
905
|
+
fh = ExclusionLayers(attrs["fpath"])
|
834
906
|
else:
|
835
907
|
fh = self.exclusions.excl_h5
|
836
908
|
|
837
|
-
raw = fh[attrs[
|
838
|
-
nodata = fh.get_nodata_value(attrs[
|
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 =
|
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 = (
|
850
|
-
|
851
|
-
|
852
|
-
|
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(
|
856
|
-
|
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__(
|
869
|
-
|
870
|
-
|
871
|
-
|
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__(
|
915
|
-
|
916
|
-
|
917
|
-
|
918
|
-
|
919
|
-
|
920
|
-
|
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 = (
|
931
|
-
|
932
|
-
|
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(
|
966
|
-
|
967
|
-
|
968
|
-
|
969
|
-
|
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 =
|
986
|
-
|
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
|
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
|
-
|
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[
|
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[
|
1149
|
+
country = self.h5.meta.loc[
|
1150
|
+
self.h5_gid_set, ResourceMetaField.COUNTRY
|
1151
|
+
].values
|
1051
1152
|
country = country[iloc]
|
1052
1153
|
|
1053
|
-
elif
|
1054
|
-
country = self.h5.meta.loc[
|
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
|
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[
|
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[
|
1172
|
+
state = self.h5.meta.loc[
|
1173
|
+
self.h5_gid_set, ResourceMetaField.STATE
|
1174
|
+
].values
|
1068
1175
|
state = state[iloc]
|
1069
1176
|
|
1070
|
-
elif
|
1071
|
-
state = self.h5.meta.loc[
|
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
|
1081
|
-
county = self.h5.meta.loc[
|
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
|
1091
|
-
elevation = self.h5.meta.loc[
|
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
|
-
|
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[
|
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[
|
1219
|
+
timezone = self.h5.meta.loc[
|
1220
|
+
self.h5_gid_set, ResourceMetaField.TIMEZONE
|
1221
|
+
].values
|
1104
1222
|
timezone = timezone[iloc]
|
1105
1223
|
|
1106
|
-
elif
|
1107
|
-
timezone = self.h5.meta.loc[
|
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
|
-
|
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[
|
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[
|
1244
|
+
offshore = self.h5.meta.loc[
|
1245
|
+
self.h5_gid_set, ResourceMetaField.OFFSHORE
|
1246
|
+
].values
|
1122
1247
|
offshore = offshore[iloc]
|
1123
1248
|
|
1124
|
-
elif
|
1125
|
-
offshore = self.h5.meta.loc[
|
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 = [
|
1142
|
-
|
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 = {
|
1157
|
-
|
1158
|
-
|
1159
|
-
|
1160
|
-
|
1161
|
-
|
1162
|
-
|
1163
|
-
|
1164
|
-
|
1165
|
-
|
1166
|
-
|
1167
|
-
|
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(
|
1175
|
-
|
1176
|
-
|
1177
|
-
|
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 = {
|
1234
|
-
|
1235
|
-
|
1236
|
-
|
1237
|
-
|
1238
|
-
|
1239
|
-
|
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(
|
1386
|
+
if agg_method.lower().startswith("mean"):
|
1243
1387
|
agg_method = point.exclusion_weighted_mean
|
1244
|
-
elif agg_method.lower().startswith((
|
1388
|
+
elif agg_method.lower().startswith(("sum", "agg")):
|
1245
1389
|
agg_method = point.aggregate
|
1246
|
-
elif
|
1390
|
+
elif "wind_dir" in agg_method.lower():
|
1247
1391
|
agg_method = point.mean_wind_dirs
|
1248
1392
|
else:
|
1249
|
-
msg = (
|
1250
|
-
|
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 = {
|
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 = {
|
1269
|
-
|
1270
|
-
def __init__(
|
1271
|
-
|
1272
|
-
|
1273
|
-
|
1274
|
-
|
1275
|
-
|
1276
|
-
|
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
|
-
|
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
|
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
|
-
|
1364
|
-
|
1365
|
-
|
1366
|
-
|
1367
|
-
|
1368
|
-
|
1369
|
-
|
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(
|
1377
|
-
|
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 = (
|
1381
|
-
|
1382
|
-
|
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(
|
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 = [
|
1480
|
-
|
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
|
-
|
1499
|
-
if self.
|
1500
|
-
|
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
|
-
|
1520
|
-
if self.
|
1521
|
-
|
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
|
-
|
1541
|
-
if self.
|
1542
|
-
|
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 = (
|
1582
|
-
|
1583
|
-
|
1584
|
-
if self.
|
1585
|
-
|
1586
|
-
|
1587
|
-
|
1588
|
-
|
1589
|
-
|
1590
|
-
|
1591
|
-
|
1592
|
-
|
1593
|
-
|
1594
|
-
|
1595
|
-
|
1596
|
-
|
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[
|
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(
|
1687
|
-
|
1688
|
-
|
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 = (
|
1696
|
-
|
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[
|
1701
|
-
|
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(
|
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[
|
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(
|
1737
|
-
|
1738
|
-
|
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 = (
|
1747
|
-
|
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[
|
1752
|
-
|
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
|
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
|
1804
|
-
"""Get the
|
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
|
-
|
1824
|
-
|
1825
|
-
|
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
|
-
|
1840
|
-
|
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.
|
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
|
-
|
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 = (
|
1913
|
-
|
1914
|
-
|
1915
|
-
|
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 = (
|
1959
|
-
|
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 (
|
1978
|
-
|
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 = (
|
1982
|
-
|
2268
|
+
rex = (rex < np.min(self._res_class_bin)) | (
|
2269
|
+
rex >= np.max(self._res_class_bin)
|
2270
|
+
)
|
1983
2271
|
|
1984
|
-
boolean_exclude =
|
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 = {
|
2005
|
-
|
2006
|
-
|
2007
|
-
|
2008
|
-
|
2009
|
-
|
2010
|
-
|
2011
|
-
|
2012
|
-
|
2013
|
-
|
2014
|
-
|
2015
|
-
|
2016
|
-
|
2017
|
-
|
2018
|
-
|
2019
|
-
|
2020
|
-
|
2021
|
-
|
2022
|
-
|
2023
|
-
|
2024
|
-
|
2025
|
-
|
2026
|
-
|
2027
|
-
|
2028
|
-
|
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[
|
2032
|
-
ARGS[
|
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[
|
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(
|
2047
|
-
|
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[
|
2074
|
-
summary[
|
2075
|
-
summary[
|
2076
|
-
summary[
|
2077
|
-
|
2078
|
-
|
2079
|
-
|
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(
|
2086
|
-
|
2087
|
-
|
2088
|
-
|
2089
|
-
|
2090
|
-
|
2091
|
-
|
2092
|
-
|
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
|
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 = {
|
2180
|
-
|
2181
|
-
|
2182
|
-
|
2183
|
-
|
2184
|
-
|
2185
|
-
|
2186
|
-
|
2187
|
-
|
2188
|
-
|
2189
|
-
|
2190
|
-
|
2191
|
-
|
2192
|
-
|
2193
|
-
|
2194
|
-
|
2195
|
-
|
2196
|
-
|
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]}"
|