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.
@@ -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 (i.e. do
136
- not specify `weight` or `use_as_weights`).
137
- By default ``None``, which applies the original layer mask
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
- if not isinstance(layers, list):
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 arugments {layer: {kwarg: value}}
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
- if layers_dict is not None:
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
@@ -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.excl_row_slices[r]
305
- c = self.excl_col_slices[c]
306
- lats.append(self.exclusions[LATITUDE, r, c].mean())
307
- lons.append(self.exclusions[LONGITUDE, r, c].mean())
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.excl_row_slices[r]
332
- c = self.excl_col_slices[c]
333
- lats.append(self.exclusions[LATITUDE, r, c].mean())
334
- lons.append(self.exclusions[LONGITUDE, r, c].mean())
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")
@@ -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.power_density_ac is None:
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.power_density_ac is None:
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 should be
363
- datasets of the `excl_fpath` file. If ``None`` or empty
364
- dictionary, no exclusions are applied. By default, ``None``.
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 latter should match the layer name in 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
- ``{"mode", "mean", "min", "max", "sum", "category"}``,
491
- describing how the high-resolution data should be aggregated
492
- for each supply curve point. ``fpath`` is an optional key
493
- that can point to an HDF5 file containing the layer data. If
494
- left out, the data is assumed to exist in the file(s)
495
- specified by the `excl_fpath` input. If ``None``, no data
496
- layer aggregation is performed. By default, ``None``
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 ``None``, a constant power density is inferred from the
507
- generation meta data technology. By default, ``None``.
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['zone_id'] = zone_id
1270
+ pointsum[SupplyCurveField.ZONE_ID] = zone_id
1222
1271
 
1223
1272
  summary.append(pointsum)
1224
1273
  logger.debug(
@@ -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
- (tech) mapping, which deviates from how the effect of the
48
- ``sc_resolution`` parameter works in other functionality within
49
- ``reV``.
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
- it will be overwritten.
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
- (tech) mapping, which deviates from how the effect of the
446
- ``sc_resolution`` parameter works in other functionality within
447
- ``reV``.
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