NREL-reV 0.14.2__py3-none-any.whl → 0.14.5__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.14.2.dist-info → nrel_rev-0.14.5.dist-info}/METADATA +5 -5
- {nrel_rev-0.14.2.dist-info → nrel_rev-0.14.5.dist-info}/RECORD +20 -20
- {nrel_rev-0.14.2.dist-info → nrel_rev-0.14.5.dist-info}/WHEEL +1 -1
- reV/bespoke/bespoke.py +96 -19
- reV/bespoke/place_turbines.py +46 -11
- reV/cli.py +3 -1
- reV/config/output_request.py +3 -0
- reV/config/project_points.py +4 -0
- reV/supply_curve/cli_sc_aggregation.py +39 -2
- reV/supply_curve/exclusions.py +55 -26
- reV/supply_curve/extent.py +18 -9
- reV/supply_curve/points.py +18 -3
- reV/supply_curve/sc_aggregation.py +64 -15
- reV/supply_curve/tech_mapping.py +8 -7
- reV/utilities/__init__.py +402 -13
- reV/utilities/cli_functions.py +43 -1
- reV/version.py +1 -1
- {nrel_rev-0.14.2.dist-info → nrel_rev-0.14.5.dist-info}/entry_points.txt +0 -0
- {nrel_rev-0.14.2.dist-info → nrel_rev-0.14.5.dist-info}/licenses/LICENSE +0 -0
- {nrel_rev-0.14.2.dist-info → nrel_rev-0.14.5.dist-info}/top_level.txt +0 -0
reV/supply_curve/exclusions.py
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
Generate reV inclusion mask from exclusion layers
|
4
4
|
"""
|
5
5
|
import logging
|
6
|
+
import fnmatch
|
6
7
|
from warnings import warn
|
7
8
|
|
8
9
|
import numpy as np
|
@@ -132,10 +133,10 @@ class LayerMask:
|
|
132
133
|
layer is equal to 1, 2, 3, 4, or 5**. Outside of these
|
133
134
|
regions (i.e. outside of federal park regions), the viewshed
|
134
135
|
exclusion is **NOT** applied. If the extent mask created by
|
135
|
-
these options is not boolean, an error is thrown (
|
136
|
-
not specify `weight` or `use_as_weights`
|
137
|
-
By default ``None``, which
|
138
|
-
to the full extent.
|
136
|
+
these options is not boolean, an error is thrown (in other
|
137
|
+
words, do not specify `weight` or `use_as_weights` or else
|
138
|
+
you will run into errors). By default ``None``, which
|
139
|
+
applies the original layer mask to the full extent.
|
139
140
|
**kwargs
|
140
141
|
Optional inputs to maintain legacy kwargs of ``inclusion_*``
|
141
142
|
instead of ``include_*``.
|
@@ -600,19 +601,7 @@ class ExclusionMask:
|
|
600
601
|
self._check_layers = check_layers
|
601
602
|
|
602
603
|
if layers is not None:
|
603
|
-
|
604
|
-
layers = [layers]
|
605
|
-
|
606
|
-
missing = [layer.name for layer in layers
|
607
|
-
if layer.name not in self.excl_layers]
|
608
|
-
if any(missing):
|
609
|
-
msg = ("ExclusionMask layers {} are missing from: {}"
|
610
|
-
.format(missing, self._excl_h5))
|
611
|
-
logger.error(msg)
|
612
|
-
raise KeyError(msg)
|
613
|
-
|
614
|
-
for layer in layers:
|
615
|
-
self.add_layer(layer)
|
604
|
+
self._add_many_layers(layers)
|
616
605
|
|
617
606
|
if kernel in ["queen", "rook"]:
|
618
607
|
self._min_area = min_area
|
@@ -623,6 +612,22 @@ class ExclusionMask:
|
|
623
612
|
else:
|
624
613
|
raise KeyError('kernel must be "queen" or "rook"')
|
625
614
|
|
615
|
+
def _add_many_layers(self, layers):
|
616
|
+
"""Add multiple layers (with check for missing layers)"""
|
617
|
+
if not isinstance(layers, list):
|
618
|
+
layers = [layers]
|
619
|
+
|
620
|
+
missing = [layer.name for layer in layers
|
621
|
+
if layer.name not in self.excl_layers]
|
622
|
+
if any(missing):
|
623
|
+
msg = ("ExclusionMask layers {} are missing from: {}"
|
624
|
+
.format(missing, self._excl_h5))
|
625
|
+
logger.error(msg)
|
626
|
+
raise KeyError(msg)
|
627
|
+
|
628
|
+
for layer in layers:
|
629
|
+
self.add_layer(layer)
|
630
|
+
|
626
631
|
def __enter__(self):
|
627
632
|
return self
|
628
633
|
|
@@ -1113,7 +1118,7 @@ class ExclusionMaskFromDict(ExclusionMask):
|
|
1113
1118
|
excl_h5 : str | list | tuple
|
1114
1119
|
Path to one or more exclusions .h5 files
|
1115
1120
|
layers_dict : dict | NoneType
|
1116
|
-
Dictionary of LayerMask
|
1121
|
+
Dictionary of LayerMask arguments {layer: {kwarg: value}}
|
1117
1122
|
min_area : float | NoneType
|
1118
1123
|
Minimum required contiguous area in sq-km
|
1119
1124
|
kernel : str
|
@@ -1125,16 +1130,34 @@ class ExclusionMaskFromDict(ExclusionMask):
|
|
1125
1130
|
Run a pre-flight check on each layer to ensure they contain
|
1126
1131
|
un-excluded values
|
1127
1132
|
"""
|
1128
|
-
|
1129
|
-
layers = []
|
1130
|
-
for layer, kwargs in layers_dict.items():
|
1131
|
-
layers.append(LayerMask(layer, **kwargs))
|
1132
|
-
else:
|
1133
|
-
layers = None
|
1134
|
-
|
1135
|
-
super().__init__(excl_h5, layers=layers, min_area=min_area,
|
1133
|
+
super().__init__(excl_h5, layers=layers_dict, min_area=min_area,
|
1136
1134
|
kernel=kernel, hsds=hsds, check_layers=check_layers)
|
1137
1135
|
|
1136
|
+
def _add_many_layers(self, layers):
|
1137
|
+
"""Add multiple layers (with check for missing layers)"""
|
1138
|
+
missing = set()
|
1139
|
+
final_layers = {}
|
1140
|
+
|
1141
|
+
# sort pattern-first so that users can overwrite specific layers
|
1142
|
+
sorted_layers = sorted(layers, key=_unix_patterns_first)
|
1143
|
+
for layer_pattern in sorted_layers:
|
1144
|
+
kwargs = layers[layer_pattern]
|
1145
|
+
layer_names = fnmatch.filter(self.excl_layers, layer_pattern)
|
1146
|
+
if not layer_names:
|
1147
|
+
missing.add(layer_pattern)
|
1148
|
+
|
1149
|
+
for layer in layer_names:
|
1150
|
+
final_layers[layer] = LayerMask(layer, **kwargs)
|
1151
|
+
|
1152
|
+
if any(missing):
|
1153
|
+
msg = ("ExclusionMask layers {} are missing from: {}"
|
1154
|
+
.format(missing, self._excl_h5))
|
1155
|
+
logger.error(msg)
|
1156
|
+
raise KeyError(msg)
|
1157
|
+
|
1158
|
+
for layer in final_layers.values():
|
1159
|
+
self.add_layer(layer)
|
1160
|
+
|
1138
1161
|
@classmethod
|
1139
1162
|
def extract_inclusion_mask(cls, excl_fpath, tm_dset, excl_dict=None,
|
1140
1163
|
area_filter_kernel='queen', min_area=None):
|
@@ -1295,3 +1318,9 @@ class FrictionMask(ExclusionMask):
|
|
1295
1318
|
mask = f.mask
|
1296
1319
|
|
1297
1320
|
return mask
|
1321
|
+
|
1322
|
+
|
1323
|
+
def _unix_patterns_first(layer_name):
|
1324
|
+
"""Key that will put layer names with unix patterns first"""
|
1325
|
+
special_chars = {"?", "*", "!", "[", "]"}
|
1326
|
+
return -1 * any(char in layer_name for char in special_chars), layer_name
|
reV/supply_curve/extent.py
CHANGED
@@ -10,7 +10,6 @@ import pandas as pd
|
|
10
10
|
from rex.utilities.utilities import get_chunk_ranges
|
11
11
|
|
12
12
|
from reV.handlers.exclusions import LATITUDE, LONGITUDE, ExclusionLayers
|
13
|
-
from reV.utilities import SupplyCurveField
|
14
13
|
from reV.utilities.exceptions import SupplyCurveError, SupplyCurveInputError
|
15
14
|
|
16
15
|
logger = logging.getLogger(__name__)
|
@@ -297,14 +296,19 @@ class SupplyCurveExtent:
|
|
297
296
|
lats = []
|
298
297
|
lons = []
|
299
298
|
|
299
|
+
max_center = self.resolution // 2 + 1
|
300
|
+
min_center = max_center - 2 + self.resolution % 2
|
301
|
+
|
300
302
|
sc_cols, sc_rows = np.meshgrid(
|
301
303
|
np.arange(self.n_cols), np.arange(self.n_rows)
|
302
304
|
)
|
303
305
|
for r, c in zip(sc_rows.flatten(), sc_cols.flatten()):
|
304
|
-
r = self.
|
305
|
-
|
306
|
-
|
307
|
-
|
306
|
+
r = slice(r * self.resolution + min_center,
|
307
|
+
r * self.resolution + max_center)
|
308
|
+
c = slice(c * self.resolution + min_center,
|
309
|
+
c * self.resolution + max_center)
|
310
|
+
lats.append(np.median(self.exclusions[LATITUDE, r, c]))
|
311
|
+
lons.append(np.median(self.exclusions[LONGITUDE, r, c]))
|
308
312
|
|
309
313
|
self._latitude = np.array(lats, dtype="float32")
|
310
314
|
self._longitude = np.array(lons, dtype="float32")
|
@@ -324,14 +328,19 @@ class SupplyCurveExtent:
|
|
324
328
|
lats = []
|
325
329
|
lons = []
|
326
330
|
|
331
|
+
max_center = self.resolution // 2 + 1
|
332
|
+
min_center = max_center - 2 + self.resolution % 2
|
333
|
+
|
327
334
|
sc_cols, sc_rows = np.meshgrid(
|
328
335
|
np.arange(self.n_cols), np.arange(self.n_rows)
|
329
336
|
)
|
330
337
|
for r, c in zip(sc_rows.flatten(), sc_cols.flatten()):
|
331
|
-
r = self.
|
332
|
-
|
333
|
-
|
334
|
-
|
338
|
+
r = slice(r * self.resolution + min_center,
|
339
|
+
r * self.resolution + max_center)
|
340
|
+
c = slice(c * self.resolution + min_center,
|
341
|
+
c * self.resolution + max_center)
|
342
|
+
lats.append(np.median(self.exclusions[LATITUDE, r, c]))
|
343
|
+
lons.append(np.median(self.exclusions[LONGITUDE, r, c]))
|
335
344
|
|
336
345
|
self._latitude = np.array(lats, dtype="float32")
|
337
346
|
self._longitude = np.array(lons, dtype="float32")
|
reV/supply_curve/points.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
|
+
# pylint: disable=anomalous-backslash-in-string
|
2
3
|
"""
|
3
4
|
reV supply curve points frameworks.
|
4
5
|
"""
|
@@ -1452,8 +1453,12 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
|
|
1452
1453
|
"""Supply curve point summary framework that ties a reV SC point to its
|
1453
1454
|
respective generation and resource data."""
|
1454
1455
|
|
1455
|
-
# technology-dependent power density estimates in MW/km2
|
1456
1456
|
POWER_DENSITY = {"pv": 36, "wind": 3}
|
1457
|
+
"""Technology-dependent power density estimates (in MW/km\ :sup:`2`).
|
1458
|
+
|
1459
|
+
The PV power density is a \**DC power density*\*, while the wind power
|
1460
|
+
density is an \**AC power density*\*.
|
1461
|
+
"""
|
1457
1462
|
|
1458
1463
|
def __init__(
|
1459
1464
|
self,
|
@@ -1856,6 +1861,15 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
|
|
1856
1861
|
|
1857
1862
|
return None
|
1858
1863
|
|
1864
|
+
@property
|
1865
|
+
def mean_wake_losses(self):
|
1866
|
+
"""float: Mean wake losses, if applicable."""
|
1867
|
+
if "annual_wake_loss_internal_percent-means" not in self.gen.datasets:
|
1868
|
+
return None
|
1869
|
+
|
1870
|
+
wakes = self.gen["annual_wake_loss_internal_percent-means"]
|
1871
|
+
return self.exclusion_weighted_mean(wakes)
|
1872
|
+
|
1859
1873
|
@property
|
1860
1874
|
def mean_lcoe(self):
|
1861
1875
|
"""Get the mean LCOE for the non-excluded data.
|
@@ -2107,7 +2121,7 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
|
|
2107
2121
|
`None` for solar runs with "dc_ac_ratio" dataset in the
|
2108
2122
|
generation file
|
2109
2123
|
"""
|
2110
|
-
if self.
|
2124
|
+
if "dc_ac_ratio" not in self.gen.datasets:
|
2111
2125
|
return None
|
2112
2126
|
|
2113
2127
|
return self.area * self.power_density_ac
|
@@ -2129,7 +2143,7 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
|
|
2129
2143
|
`None` for solar runs with "dc_ac_ratio" dataset in the
|
2130
2144
|
generation file
|
2131
2145
|
"""
|
2132
|
-
if self.
|
2146
|
+
if "dc_ac_ratio" not in self.gen.datasets:
|
2133
2147
|
return None
|
2134
2148
|
|
2135
2149
|
return self.area * self.power_density
|
@@ -2368,6 +2382,7 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
|
|
2368
2382
|
SupplyCurveField.MEAN_CF_DC: self.mean_cf_dc,
|
2369
2383
|
SupplyCurveField.MEAN_LCOE: self.mean_lcoe,
|
2370
2384
|
SupplyCurveField.MEAN_RES: self.mean_res,
|
2385
|
+
SupplyCurveField.WAKE_LOSSES: self.mean_wake_losses,
|
2371
2386
|
SupplyCurveField.AREA_SQ_KM: self.area,
|
2372
2387
|
SupplyCurveField.CAPACITY_AC_MW: (
|
2373
2388
|
self.capacity if self.capacity_ac is None else self.capacity_ac
|
@@ -245,6 +245,7 @@ class SupplyCurveAggFileHandler(AbstractAggFileHandler):
|
|
245
245
|
class SupplyCurveAggregation(BaseAggregation):
|
246
246
|
"""SupplyCurveAggregation"""
|
247
247
|
|
248
|
+
# pylint: disable=line-too-long
|
248
249
|
def __init__(self, excl_fpath, tm_dset, econ_fpath=None,
|
249
250
|
excl_dict=None, area_filter_kernel='queen', min_area=None,
|
250
251
|
resolution=64, excl_area=None, res_fpath=None, gids=None,
|
@@ -334,6 +335,12 @@ class SupplyCurveAggregation(BaseAggregation):
|
|
334
335
|
"exclude_nodata": True,
|
335
336
|
"nodata_value": -1
|
336
337
|
},
|
338
|
+
"wildcard*exclusion": {
|
339
|
+
"exclude_values": 1,
|
340
|
+
},
|
341
|
+
"wildcard_unique_exclusion": {
|
342
|
+
"exclude_values": [1, 2, 3],
|
343
|
+
},
|
337
344
|
"partial_setback": {
|
338
345
|
"use_as_weights": True
|
339
346
|
},
|
@@ -359,9 +366,24 @@ class SupplyCurveAggregation(BaseAggregation):
|
|
359
366
|
...
|
360
367
|
}
|
361
368
|
|
362
|
-
Note that all the keys given in this dictionary
|
363
|
-
datasets of the `excl_fpath` file
|
364
|
-
|
369
|
+
Note that all the keys given in this dictionary must be
|
370
|
+
datasets of the `excl_fpath` file or you will get an error.
|
371
|
+
You *may* include Unix-style wildcards (i.e. ``*``, ``?``,
|
372
|
+
or ``[]``) in the keys, but note that the same exclusion
|
373
|
+
configuration will be applied to **all** datasets that match
|
374
|
+
the wildcard pattern unless you explicitly override it for a
|
375
|
+
specific layer. For example, in the configuration above,
|
376
|
+
**all** of the layers matching the pattern
|
377
|
+
``wildcard*exclusion`` will be used as exclusions where the
|
378
|
+
respective layer values equal ``1``, **except** for the
|
379
|
+
``wildcard_unique_exclusion`` layer, which will be used as
|
380
|
+
an exclusion wherever that particular layer values equal
|
381
|
+
``1``, ``2``, or ``3``. You can use this strategy to
|
382
|
+
"exclude" layers from the wildcard match - simply set the
|
383
|
+
``exclude_values`` key to a value that does not exist in
|
384
|
+
that layer and it will be effectively ignored. If ``None``
|
385
|
+
or empty dictionary, no exclusions are applied.
|
386
|
+
By default, ``None``.
|
365
387
|
area_filter_kernel : {"queen", "rook"}, optional
|
366
388
|
Contiguous area filter method to use on final exclusions
|
367
389
|
mask. The filters are defined as::
|
@@ -484,16 +506,36 @@ class SupplyCurveAggregation(BaseAggregation):
|
|
484
506
|
The ``"output_layer_name"`` is the column name under which
|
485
507
|
the aggregated data will appear in the output CSV file. The
|
486
508
|
``"output_layer_name"`` does not have to match the ``dset``
|
487
|
-
input value. The
|
509
|
+
input value. The ``dset`` should match the layer name in the
|
488
510
|
HDF5 from which the data to aggregate should be pulled. The
|
489
|
-
``method`` should be one of
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
511
|
+
``method`` key should be one of the following:
|
512
|
+
|
513
|
+
- ``"mode"``: Output values will be the numerical mode
|
514
|
+
of the non-excluded high resolution data layer cell
|
515
|
+
values
|
516
|
+
- ``"mean"``: Output values will be the arithmetic mean
|
517
|
+
of the non-excluded high resolution data layer cell
|
518
|
+
values
|
519
|
+
- ``"min"``: Output values will be the numerical minimum
|
520
|
+
value of the non-excluded high resolution data layer
|
521
|
+
cell values
|
522
|
+
- ``"max"``: Output values will be the numerical maximum
|
523
|
+
value of the non-excluded high resolution data layer
|
524
|
+
cell values
|
525
|
+
- ``"sum"``: Output values will be the sum of the
|
526
|
+
non-excluded high resolution data layer cell values
|
527
|
+
- ``"category"``: Output values will be a string
|
528
|
+
representation of a dictionary where the keys are the
|
529
|
+
unique values of the non-excluded high resolution data
|
530
|
+
layer cells and the values are the *total
|
531
|
+
high-resolution pixel area* corresponding to that data
|
532
|
+
layer value
|
533
|
+
|
534
|
+
``fpath`` is an optional key that can point to an HDF5 file
|
535
|
+
containing the layer data. If left out, the data is assumed
|
536
|
+
to exist in the file(s) specified by the `excl_fpath` input.
|
537
|
+
If ``None``, no data layer aggregation is performed.
|
538
|
+
By default, ``None``
|
497
539
|
power_density : float | str, optional
|
498
540
|
Power density value (in MW/km\ :sup:`2`) or filepath to
|
499
541
|
variable power density CSV file containing the following
|
@@ -503,8 +545,15 @@ class SupplyCurveAggregation(BaseAggregation):
|
|
503
545
|
- ``power_density`` : power density value (in
|
504
546
|
MW/km\ :sup:`2`)
|
505
547
|
|
506
|
-
If
|
507
|
-
|
548
|
+
If you are running reV for PV (more specifically, you have a
|
549
|
+
`dc_ac_ratio` in your generation file), then this input
|
550
|
+
should represent the \**DC power density*\*. For all other
|
551
|
+
technologies (wind, geothermal, etc), this input should
|
552
|
+
represent the \**AC power density*\*. If ``None``, a
|
553
|
+
constant power density value is pulled from
|
554
|
+
:obj:`~reV.supply_curve.points.GenerationSupplyCurvePoint.POWER_DENSITY`
|
555
|
+
by looking up the technology from the generation meta data.
|
556
|
+
By default, ``None``.
|
508
557
|
friction_fpath : str, optional
|
509
558
|
Filepath to friction surface data (cost based exclusions).
|
510
559
|
Must be paired with the `friction_dset` input below. The
|
@@ -1218,7 +1267,7 @@ class SupplyCurveAggregation(BaseAggregation):
|
|
1218
1267
|
.format(gid, zone_id))
|
1219
1268
|
else:
|
1220
1269
|
pointsum['res_class'] = ri
|
1221
|
-
pointsum[
|
1270
|
+
pointsum[SupplyCurveField.ZONE_ID] = zone_id
|
1222
1271
|
|
1223
1272
|
summary.append(pointsum)
|
1224
1273
|
logger.debug(
|
reV/supply_curve/tech_mapping.py
CHANGED
@@ -32,6 +32,7 @@ class TechMapping:
|
|
32
32
|
|
33
33
|
def __init__(self, excl_fpath, sc_resolution=1200):
|
34
34
|
"""
|
35
|
+
|
35
36
|
Parameters
|
36
37
|
----------
|
37
38
|
excl_fpath : str
|
@@ -44,9 +45,9 @@ class TechMapping:
|
|
44
45
|
map the exclusion pixels in 1200x1200 pixel chunks.
|
45
46
|
|
46
47
|
.. Note:: This parameter does not affect the exclusion to resource
|
47
|
-
|
48
|
-
|
49
|
-
|
48
|
+
(tech) mapping, which deviates from how the effect of the
|
49
|
+
``sc_resolution`` parameter works in other functionality
|
50
|
+
within ``reV``.
|
50
51
|
|
51
52
|
"""
|
52
53
|
self._excl_fpath = excl_fpath
|
@@ -433,7 +434,7 @@ class TechMapping:
|
|
433
434
|
techmap (exclusions-to-resource mapping data) will be saved.
|
434
435
|
|
435
436
|
.. Important:: If this dataset already exists in the h5 file,
|
436
|
-
|
437
|
+
it will be overwritten.
|
437
438
|
|
438
439
|
sc_resolution : int | None, optional
|
439
440
|
Defines how many exclusion pixels are mapped at a time. Units
|
@@ -442,9 +443,9 @@ class TechMapping:
|
|
442
443
|
map the exclusion pixels in 1200x1200 pixel chunks.
|
443
444
|
|
444
445
|
.. Note:: This parameter does not affect the exclusion to resource
|
445
|
-
|
446
|
-
|
447
|
-
|
446
|
+
(tech) mapping, which deviates from how the effect of the
|
447
|
+
``sc_resolution`` parameter works in other functionality
|
448
|
+
within ``reV``.
|
448
449
|
|
449
450
|
dist_margin : float, optional
|
450
451
|
Extra margin to multiply times the computed distance between
|