NREL-reV 0.9.7__py3-none-any.whl → 0.12.2__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.9.7.dist-info → nrel_rev-0.12.2.dist-info}/METADATA +28 -32
- {NREL_reV-0.9.7.dist-info → nrel_rev-0.12.2.dist-info}/RECORD +30 -29
- {NREL_reV-0.9.7.dist-info → nrel_rev-0.12.2.dist-info}/WHEEL +1 -1
- {NREL_reV-0.9.7.dist-info → nrel_rev-0.12.2.dist-info}/entry_points.txt +0 -1
- reV/SAM/SAM.py +30 -7
- reV/SAM/defaults/WY Southern-Flat Lands.srw +8765 -0
- reV/SAM/defaults/geothermal.json +27 -16
- reV/SAM/defaults/i_pvwattsv5.json +1 -1
- reV/SAM/defaults.py +3 -19
- reV/SAM/generation.py +18 -52
- reV/bespoke/bespoke.py +39 -81
- reV/bespoke/cli_bespoke.py +0 -2
- reV/bespoke/pack_turbs.py +15 -17
- reV/bespoke/place_turbines.py +2 -25
- reV/config/output_request.py +10 -1
- reV/config/project_points.py +126 -19
- reV/generation/base.py +22 -4
- reV/generation/generation.py +45 -22
- reV/generation/output_attributes/linear_fresnel.json +7 -0
- reV/losses/power_curve.py +3 -0
- reV/losses/scheduled.py +35 -14
- reV/supply_curve/aggregation.py +47 -0
- reV/supply_curve/points.py +54 -0
- reV/supply_curve/sc_aggregation.py +76 -38
- reV/utilities/__init__.py +2 -0
- reV/utilities/_clean_readme.py +28 -0
- reV/utilities/curtailment.py +17 -13
- reV/version.py +1 -1
- reV/bespoke/plotting_functions.py +0 -178
- {NREL_reV-0.9.7.dist-info → nrel_rev-0.12.2.dist-info/licenses}/LICENSE +0 -0
- {NREL_reV-0.9.7.dist-info → nrel_rev-0.12.2.dist-info}/top_level.txt +0 -0
reV/supply_curve/points.py
CHANGED
@@ -221,6 +221,7 @@ class SupplyCurvePoint(AbstractSupplyCurvePoint):
|
|
221
221
|
excl_area=None,
|
222
222
|
exclusion_shape=None,
|
223
223
|
close=True,
|
224
|
+
zone_mask=None,
|
224
225
|
):
|
225
226
|
"""
|
226
227
|
Parameters
|
@@ -254,6 +255,11 @@ class SupplyCurvePoint(AbstractSupplyCurvePoint):
|
|
254
255
|
will speed things up considerably.
|
255
256
|
close : bool
|
256
257
|
Flag to close object file handlers on exit.
|
258
|
+
zone_mask : np.ndarray | None, optional
|
259
|
+
2D array defining zone within the supply curve to be evaluated,
|
260
|
+
where values of 0 or False will be excluded. The shape of this
|
261
|
+
array will be checked against the input resolution. If not
|
262
|
+
specified, no zone mask will be applied.
|
257
263
|
"""
|
258
264
|
|
259
265
|
self._excl_dict = excl_dict
|
@@ -282,6 +288,18 @@ class SupplyCurvePoint(AbstractSupplyCurvePoint):
|
|
282
288
|
assert inclusion_mask.size == len(self._gids), msg
|
283
289
|
self._incl_mask = inclusion_mask.copy()
|
284
290
|
|
291
|
+
self._zone_mask = zone_mask
|
292
|
+
if zone_mask is not None:
|
293
|
+
msg = (
|
294
|
+
"Bad zone mask input shape of {} with stated "
|
295
|
+
"resolution of {}".format(zone_mask.shape, resolution)
|
296
|
+
)
|
297
|
+
assert len(zone_mask.shape) == 2, msg
|
298
|
+
assert zone_mask.shape[0] <= resolution, msg
|
299
|
+
assert zone_mask.shape[1] <= resolution, msg
|
300
|
+
assert zone_mask.size == len(self._gids), msg
|
301
|
+
self._zone_mask = zone_mask.copy()
|
302
|
+
|
285
303
|
self._centroid = None
|
286
304
|
self._excl_area = excl_area
|
287
305
|
self._check_excl()
|
@@ -454,6 +472,17 @@ class SupplyCurvePoint(AbstractSupplyCurvePoint):
|
|
454
472
|
|
455
473
|
return n_gids
|
456
474
|
|
475
|
+
@property
|
476
|
+
def zone_mask(self):
|
477
|
+
"""
|
478
|
+
Get the 2D zone mask, where 1 is included and 0 is excluded.
|
479
|
+
|
480
|
+
Returns
|
481
|
+
-------
|
482
|
+
np.ndarray
|
483
|
+
"""
|
484
|
+
return self._zone_mask
|
485
|
+
|
457
486
|
@property
|
458
487
|
def include_mask(self):
|
459
488
|
"""Get the 2D inclusion mask (normalized with expected range: [0, 1]
|
@@ -481,6 +510,10 @@ class SupplyCurvePoint(AbstractSupplyCurvePoint):
|
|
481
510
|
logger.warning(w)
|
482
511
|
warn(w)
|
483
512
|
|
513
|
+
if self.zone_mask is not None:
|
514
|
+
out_of_zone = self.zone_mask == 0
|
515
|
+
self._incl_mask[out_of_zone] = 0.0
|
516
|
+
|
484
517
|
return self._incl_mask
|
485
518
|
|
486
519
|
@property
|
@@ -955,6 +988,7 @@ class AggregationSupplyCurvePoint(SupplyCurvePoint):
|
|
955
988
|
close=True,
|
956
989
|
gen_index=None,
|
957
990
|
apply_exclusions=True,
|
991
|
+
zone_mask=None,
|
958
992
|
):
|
959
993
|
"""
|
960
994
|
Parameters
|
@@ -997,6 +1031,11 @@ class AggregationSupplyCurvePoint(SupplyCurvePoint):
|
|
997
1031
|
apply_exclusions : bool
|
998
1032
|
Flag to apply exclusions to the resource / generation gid's on
|
999
1033
|
initialization.
|
1034
|
+
zone_mask : np.ndarray | None, optional
|
1035
|
+
2D array defining zone within the supply curve to be evaluated,
|
1036
|
+
where values of 0 or False will be excluded. The shape of this
|
1037
|
+
array will be checked against the input resolution. If not
|
1038
|
+
specified, no zone mask will be applied.
|
1000
1039
|
"""
|
1001
1040
|
super().__init__(
|
1002
1041
|
gid,
|
@@ -1008,6 +1047,7 @@ class AggregationSupplyCurvePoint(SupplyCurvePoint):
|
|
1008
1047
|
excl_area=excl_area,
|
1009
1048
|
exclusion_shape=exclusion_shape,
|
1010
1049
|
close=close,
|
1050
|
+
zone_mask=zone_mask,
|
1011
1051
|
)
|
1012
1052
|
|
1013
1053
|
self._h5_fpath, self._h5 = self._parse_h5_file(agg_h5)
|
@@ -1435,6 +1475,7 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
|
|
1435
1475
|
friction_layer=None,
|
1436
1476
|
recalc_lcoe=True,
|
1437
1477
|
apply_exclusions=True,
|
1478
|
+
zone_mask=None,
|
1438
1479
|
):
|
1439
1480
|
"""
|
1440
1481
|
Parameters
|
@@ -1510,6 +1551,11 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
|
|
1510
1551
|
apply_exclusions : bool
|
1511
1552
|
Flag to apply exclusions to the resource / generation gid's on
|
1512
1553
|
initialization.
|
1554
|
+
zone_mask : np.ndarray | None, optional
|
1555
|
+
2D array defining zone within the supply curve to be evaluated,
|
1556
|
+
where values of 0 or False will be excluded. The shape of this
|
1557
|
+
array will be checked against the input resolution. If not
|
1558
|
+
specified, no zone mask will be applied.
|
1513
1559
|
"""
|
1514
1560
|
|
1515
1561
|
self._res_class_dset = res_class_dset
|
@@ -1540,6 +1586,7 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
|
|
1540
1586
|
exclusion_shape=exclusion_shape,
|
1541
1587
|
close=close,
|
1542
1588
|
apply_exclusions=False,
|
1589
|
+
zone_mask=zone_mask,
|
1543
1590
|
)
|
1544
1591
|
|
1545
1592
|
self._res_gid_set = None
|
@@ -2423,6 +2470,7 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
|
|
2423
2470
|
data_layers=None,
|
2424
2471
|
cap_cost_scale=None,
|
2425
2472
|
recalc_lcoe=True,
|
2473
|
+
zone_mask=None,
|
2426
2474
|
):
|
2427
2475
|
"""Get a summary dictionary of a single supply curve point.
|
2428
2476
|
|
@@ -2504,6 +2552,11 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
|
|
2504
2552
|
datasets to be aggregated in the gen input: system_capacity,
|
2505
2553
|
fixed_charge_rate, capital_cost, fixed_operating_cost,
|
2506
2554
|
and variable_operating_cost.
|
2555
|
+
zone_mask : np.ndarray | None, optional
|
2556
|
+
2D array defining zone within the supply curve to be evaluated,
|
2557
|
+
where values of 0 or False will be excluded. The shape of this
|
2558
|
+
array will be checked against the input resolution. If not
|
2559
|
+
specified, no zone mask will be applied.
|
2507
2560
|
|
2508
2561
|
Returns
|
2509
2562
|
-------
|
@@ -2525,6 +2578,7 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
|
|
2525
2578
|
"close": close,
|
2526
2579
|
"friction_layer": friction_layer,
|
2527
2580
|
"recalc_lcoe": recalc_lcoe,
|
2581
|
+
"zone_mask": zone_mask,
|
2528
2582
|
}
|
2529
2583
|
|
2530
2584
|
with cls(
|
@@ -252,7 +252,7 @@ class SupplyCurveAggregation(BaseAggregation):
|
|
252
252
|
res_class_bins=None, cf_dset='cf_mean-means',
|
253
253
|
lcoe_dset='lcoe_fcr-means', h5_dsets=None, data_layers=None,
|
254
254
|
power_density=None, friction_fpath=None, friction_dset=None,
|
255
|
-
cap_cost_scale=None, recalc_lcoe=True):
|
255
|
+
cap_cost_scale=None, recalc_lcoe=True, zones_dset=None):
|
256
256
|
r"""ReV supply curve points aggregation framework.
|
257
257
|
|
258
258
|
``reV`` supply curve aggregation combines a high-resolution
|
@@ -297,7 +297,6 @@ class SupplyCurveAggregation(BaseAggregation):
|
|
297
297
|
exclusion HDF5 file is a blocking operation, so you may
|
298
298
|
only run a single ``reV`` aggregation step at a time this
|
299
299
|
way.
|
300
|
-
|
301
300
|
econ_fpath : str, optional
|
302
301
|
Filepath to HDF5 file with ``reV`` econ output results
|
303
302
|
containing an `lcoe_dset` dataset. If ``None``, `lcoe_dset`
|
@@ -544,6 +543,15 @@ class SupplyCurveAggregation(BaseAggregation):
|
|
544
543
|
generation HDF5 output, or if `recalc_lcoe` is set to
|
545
544
|
``False``, the mean LCOE will be computed from the data
|
546
545
|
stored under the `lcoe_dset` instead. By default, ``True``.
|
546
|
+
zones_dset: str, optional
|
547
|
+
Dataset name in `excl_fpath` containing the zones to be applied.
|
548
|
+
If specified, supply curve aggregation will be performed separately
|
549
|
+
for each discrete zone within each supply curve site. This option
|
550
|
+
can be used for uses cases such as subdividing sites by parcel,
|
551
|
+
such that each parcel within each site is output to a separate
|
552
|
+
sc_gid. The input data layer should consist of unique integer
|
553
|
+
values for each zone. Values of zero will be treated as excluded
|
554
|
+
areas.
|
547
555
|
|
548
556
|
Examples
|
549
557
|
--------
|
@@ -708,6 +716,7 @@ class SupplyCurveAggregation(BaseAggregation):
|
|
708
716
|
self._friction_dset = friction_dset
|
709
717
|
self._data_layers = data_layers
|
710
718
|
self._recalc_lcoe = recalc_lcoe
|
719
|
+
self._zones_dset = zones_dset
|
711
720
|
|
712
721
|
logger.debug("Resource class bins: {}".format(self._res_class_bins))
|
713
722
|
|
@@ -950,6 +959,7 @@ class SupplyCurveAggregation(BaseAggregation):
|
|
950
959
|
excl_area=None,
|
951
960
|
cap_cost_scale=None,
|
952
961
|
recalc_lcoe=True,
|
962
|
+
zones_dset=None,
|
953
963
|
):
|
954
964
|
"""Standalone method to create agg summary - can be parallelized.
|
955
965
|
|
@@ -1043,6 +1053,15 @@ class SupplyCurveAggregation(BaseAggregation):
|
|
1043
1053
|
datasets to be aggregated in the h5_dsets input: system_capacity,
|
1044
1054
|
fixed_charge_rate, capital_cost, fixed_operating_cost,
|
1045
1055
|
and variable_operating_cost.
|
1056
|
+
zones_dset: str, optional
|
1057
|
+
Dataset name in `excl_fpath` containing the zones to be applied.
|
1058
|
+
If specified, supply curve aggregation will be performed separately
|
1059
|
+
for each discrete zone within each supply curve site. This option
|
1060
|
+
can be used for uses cases such as subdividing sites by parcel,
|
1061
|
+
such that each parcel within each site is output to a separate
|
1062
|
+
sc_gid. The input data layer should consist of unique integer
|
1063
|
+
values for each zone. Values of zero will be treated as excluded
|
1064
|
+
areas.
|
1046
1065
|
|
1047
1066
|
Returns
|
1048
1067
|
-------
|
@@ -1093,47 +1112,64 @@ class SupplyCurveAggregation(BaseAggregation):
|
|
1093
1112
|
inclusion_mask, gid, slice_lookup, resolution=resolution
|
1094
1113
|
)
|
1095
1114
|
|
1096
|
-
|
1097
|
-
|
1098
|
-
|
1099
|
-
|
1100
|
-
fh.exclusions,
|
1101
|
-
fh.gen,
|
1102
|
-
tm_dset,
|
1103
|
-
gen_index,
|
1104
|
-
res_class_dset=res_data,
|
1105
|
-
res_class_bin=res_bin,
|
1106
|
-
cf_dset=cf_dset,
|
1107
|
-
lcoe_dset=lcoe_data,
|
1108
|
-
h5_dsets=h5_dsets_data,
|
1109
|
-
data_layers=fh.data_layers,
|
1110
|
-
resolution=resolution,
|
1111
|
-
exclusion_shape=exclusion_shape,
|
1112
|
-
power_density=fh.power_density,
|
1113
|
-
args=args,
|
1114
|
-
excl_dict=excl_dict,
|
1115
|
-
inclusion_mask=gid_inclusions,
|
1116
|
-
excl_area=excl_area,
|
1117
|
-
close=False,
|
1118
|
-
friction_layer=fh.friction_layer,
|
1119
|
-
cap_cost_scale=cap_cost_scale,
|
1120
|
-
recalc_lcoe=recalc_lcoe,
|
1121
|
-
)
|
1115
|
+
zones = cls._get_gid_zones(
|
1116
|
+
excl_fpath, zones_dset, gid, slice_lookup
|
1117
|
+
)
|
1118
|
+
zone_ids = np.unique(zones[zones != 0]).tolist()
|
1122
1119
|
|
1123
|
-
|
1124
|
-
|
1125
|
-
|
1126
|
-
|
1120
|
+
for ri, res_bin in enumerate(res_class_bins):
|
1121
|
+
for zi, zone_id in enumerate(zone_ids, start=1):
|
1122
|
+
zone_mask = zones == zone_id
|
1123
|
+
try:
|
1124
|
+
pointsum = GenerationSupplyCurvePoint.summarize(
|
1125
|
+
gid,
|
1126
|
+
fh.exclusions,
|
1127
|
+
fh.gen,
|
1128
|
+
tm_dset,
|
1129
|
+
gen_index,
|
1130
|
+
res_class_dset=res_data,
|
1131
|
+
res_class_bin=res_bin,
|
1132
|
+
cf_dset=cf_dset,
|
1133
|
+
lcoe_dset=lcoe_data,
|
1134
|
+
h5_dsets=h5_dsets_data,
|
1135
|
+
data_layers=fh.data_layers,
|
1136
|
+
resolution=resolution,
|
1137
|
+
exclusion_shape=exclusion_shape,
|
1138
|
+
power_density=fh.power_density,
|
1139
|
+
args=args,
|
1140
|
+
excl_dict=excl_dict,
|
1141
|
+
inclusion_mask=gid_inclusions,
|
1142
|
+
excl_area=excl_area,
|
1143
|
+
close=False,
|
1144
|
+
friction_layer=fh.friction_layer,
|
1145
|
+
cap_cost_scale=cap_cost_scale,
|
1146
|
+
recalc_lcoe=recalc_lcoe,
|
1147
|
+
zone_mask=zone_mask,
|
1148
|
+
)
|
1127
1149
|
|
1128
|
-
|
1129
|
-
|
1130
|
-
|
1131
|
-
|
1132
|
-
|
1150
|
+
except EmptySupplyCurvePointError:
|
1151
|
+
logger.debug("SC point {}, zone ID {} is empty"
|
1152
|
+
.format(gid, zone_id))
|
1153
|
+
else:
|
1154
|
+
pointsum['res_class'] = ri
|
1155
|
+
pointsum['zone_id'] = zone_id
|
1156
|
+
|
1157
|
+
summary.append(pointsum)
|
1158
|
+
logger.debug(
|
1159
|
+
"Serial aggregation completed for"
|
1160
|
+
"resource class {}, zone ID {}: "
|
1161
|
+
"{:,d} out of {:,d} zones complete".format(
|
1162
|
+
ri, zone_id, zi, len(zone_ids)
|
1163
|
+
)
|
1133
1164
|
)
|
1134
|
-
)
|
1135
1165
|
|
1136
1166
|
n_finished += 1
|
1167
|
+
logger.debug(
|
1168
|
+
"Serial aggregation completed for gid {}: "
|
1169
|
+
"{:,d} out of {:,d} points complete".format(
|
1170
|
+
gid, n_finished, len(gids)
|
1171
|
+
)
|
1172
|
+
)
|
1137
1173
|
|
1138
1174
|
return summary
|
1139
1175
|
|
@@ -1227,6 +1263,7 @@ class SupplyCurveAggregation(BaseAggregation):
|
|
1227
1263
|
excl_area=self._excl_area,
|
1228
1264
|
cap_cost_scale=self._cap_cost_scale,
|
1229
1265
|
recalc_lcoe=self._recalc_lcoe,
|
1266
|
+
zones_dset=self._zones_dset,
|
1230
1267
|
)
|
1231
1268
|
)
|
1232
1269
|
|
@@ -1365,6 +1402,7 @@ class SupplyCurveAggregation(BaseAggregation):
|
|
1365
1402
|
excl_area=self._excl_area,
|
1366
1403
|
cap_cost_scale=self._cap_cost_scale,
|
1367
1404
|
recalc_lcoe=self._recalc_lcoe,
|
1405
|
+
zones_dset=self._zones_dset,
|
1368
1406
|
)
|
1369
1407
|
else:
|
1370
1408
|
summary = self.run_parallel(
|
reV/utilities/__init__.py
CHANGED
@@ -89,6 +89,7 @@ class SiteDataField(FieldEnum):
|
|
89
89
|
|
90
90
|
GID = "gid"
|
91
91
|
CONFIG = "config"
|
92
|
+
CURTAILMENT = "curtailment"
|
92
93
|
|
93
94
|
|
94
95
|
class ResourceMetaField(FieldEnum):
|
@@ -135,6 +136,7 @@ class SupplyCurveField(FieldEnum):
|
|
135
136
|
GEN_GIDS = "gen_gids"
|
136
137
|
GID_COUNTS = "gid_counts"
|
137
138
|
N_GIDS = "n_gids"
|
139
|
+
ZONE_ID = "zone_id"
|
138
140
|
MEAN_RES = "resource"
|
139
141
|
MEAN_CF_AC = "capacity_factor_ac"
|
140
142
|
MEAN_CF_DC = "capacity_factor_dc"
|
@@ -0,0 +1,28 @@
|
|
1
|
+
import sys
|
2
|
+
|
3
|
+
|
4
|
+
PYPI_DISALLOWED_RST = {"raw::", "<p", "</p", "<img", "---------"}
|
5
|
+
REMOVE_TEXT = ["A visual summary of this process is given below:", " "]
|
6
|
+
|
7
|
+
|
8
|
+
def _clean(fp):
|
9
|
+
"""Prep the README file for PyPi distribution"""
|
10
|
+
readme = []
|
11
|
+
with open(fp, encoding="utf-8") as f:
|
12
|
+
for line in f.readlines():
|
13
|
+
if any(substr in line for substr in PYPI_DISALLOWED_RST):
|
14
|
+
continue
|
15
|
+
readme.append(line)
|
16
|
+
|
17
|
+
readme = "".join(readme)
|
18
|
+
for substr in REMOVE_TEXT:
|
19
|
+
readme = readme.replace(substr, "")
|
20
|
+
|
21
|
+
readme = readme.lstrip()
|
22
|
+
|
23
|
+
with open(fp, "w", encoding="utf-8") as f:
|
24
|
+
f.write(readme)
|
25
|
+
|
26
|
+
|
27
|
+
if __name__ == "__main__":
|
28
|
+
_clean(*sys.argv[1:])
|
reV/utilities/curtailment.py
CHANGED
@@ -19,7 +19,7 @@ from rex.utilities.utilities import check_tz, get_lat_lon_cols
|
|
19
19
|
logger = logging.getLogger(__name__)
|
20
20
|
|
21
21
|
|
22
|
-
def curtail(resource, curtailment, random_seed=0):
|
22
|
+
def curtail(resource, curtailment, sites, random_seed=0):
|
23
23
|
"""Curtail the SAM wind resource object based on project points.
|
24
24
|
|
25
25
|
Parameters
|
@@ -28,6 +28,8 @@ def curtail(resource, curtailment, random_seed=0):
|
|
28
28
|
SAM resource object for WIND resource.
|
29
29
|
curtailment : reV.config.curtailment.Curtailment
|
30
30
|
Curtailment config object.
|
31
|
+
sites : list
|
32
|
+
List of GID's to apply this curtailment to.
|
31
33
|
random_seed : int | NoneType
|
32
34
|
Number to seed the numpy random number generator. Used to generate
|
33
35
|
reproducable psuedo-random results if the probability of curtailment
|
@@ -41,7 +43,8 @@ def curtail(resource, curtailment, random_seed=0):
|
|
41
43
|
where curtailment is in effect.
|
42
44
|
"""
|
43
45
|
|
44
|
-
shape = resource.shape
|
46
|
+
shape = (resource.shape[0], len(sites))
|
47
|
+
site_pos = [resource.sites.index(id) for id in sites]
|
45
48
|
|
46
49
|
# start with curtailment everywhere
|
47
50
|
curtail_mult = np.zeros(shape)
|
@@ -74,10 +77,10 @@ def curtail(resource, curtailment, random_seed=0):
|
|
74
77
|
raise KeyError(msg)
|
75
78
|
|
76
79
|
# Curtail resource when curtailment is possible and is nighttime
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
80
|
+
meta = resource["meta", sites]
|
81
|
+
lat_lon_cols = get_lat_lon_cols(meta)
|
82
|
+
solar_zenith_angle = SolarPosition(resource.time_index,
|
83
|
+
meta[lat_lon_cols].values).zenith
|
81
84
|
mask = (solar_zenith_angle > curtailment.dawn_dusk)
|
82
85
|
curtail_mult = np.where(mask, curtail_mult, 1)
|
83
86
|
|
@@ -92,28 +95,29 @@ def curtail(resource, curtailment, random_seed=0):
|
|
92
95
|
list(resource._res_arrays.keys())),
|
93
96
|
HandlerWarning)
|
94
97
|
else:
|
95
|
-
mask = (resource._res_arrays['precipitationrate']
|
98
|
+
mask = (resource._res_arrays['precipitationrate'][:, site_pos]
|
96
99
|
< curtailment.precipitation)
|
97
100
|
curtail_mult = np.where(mask, curtail_mult, 1)
|
98
101
|
|
99
102
|
# Curtail resource when curtailment is possible and temperature is high
|
100
103
|
if curtailment.temperature is not None:
|
101
|
-
mask = (resource._res_arrays['temperature']
|
104
|
+
mask = (resource._res_arrays['temperature'][:, site_pos]
|
102
105
|
> curtailment.temperature)
|
103
106
|
curtail_mult = np.where(mask, curtail_mult, 1)
|
104
107
|
|
105
108
|
# Curtail resource when curtailment is possible and not that windy
|
106
109
|
if curtailment.wind_speed is not None:
|
107
|
-
mask = (resource._res_arrays['windspeed']
|
110
|
+
mask = (resource._res_arrays['windspeed'][:, site_pos]
|
108
111
|
< curtailment.wind_speed)
|
109
112
|
curtail_mult = np.where(mask, curtail_mult, 1)
|
110
113
|
|
111
114
|
if curtailment.equation is not None:
|
112
115
|
# pylint: disable=W0123,W0612
|
113
|
-
wind_speed = resource._res_arrays['windspeed']
|
114
|
-
temperature = resource._res_arrays['temperature']
|
116
|
+
wind_speed = resource._res_arrays['windspeed'][:, site_pos]
|
117
|
+
temperature = resource._res_arrays['temperature'][:, site_pos]
|
115
118
|
if 'precipitationrate' in resource._res_arrays:
|
116
|
-
precipitation_rate =
|
119
|
+
precipitation_rate = (
|
120
|
+
resource._res_arrays['precipitationrate'][:, site_pos])
|
117
121
|
mask = eval(curtailment.equation)
|
118
122
|
curtail_mult = np.where(mask, curtail_mult, 1)
|
119
123
|
|
@@ -124,6 +128,6 @@ def curtail(resource, curtailment, random_seed=0):
|
|
124
128
|
curtail_mult = np.where(mask, curtail_mult, 1)
|
125
129
|
|
126
130
|
# Apply curtailment multiplier directly to resource
|
127
|
-
resource.curtail_windspeed(
|
131
|
+
resource.curtail_windspeed(sites, curtail_mult)
|
128
132
|
|
129
133
|
return resource
|
reV/version.py
CHANGED
@@ -1,178 +0,0 @@
|
|
1
|
-
# -*- coding: utf-8 -*-
|
2
|
-
"""
|
3
|
-
functions to plot turbine layouts and boundary polygons
|
4
|
-
"""
|
5
|
-
import numpy as np
|
6
|
-
import matplotlib.pyplot as plt
|
7
|
-
|
8
|
-
|
9
|
-
def get_xy(A):
|
10
|
-
"""separate polygon exterior coordinates to x and y
|
11
|
-
|
12
|
-
Parameters
|
13
|
-
----------
|
14
|
-
A : Polygon.exteroir.coords
|
15
|
-
Exterior coordinates from a shapely Polygon
|
16
|
-
|
17
|
-
Outputs
|
18
|
-
----------
|
19
|
-
x, y : array
|
20
|
-
Boundary polygon x and y coordinates
|
21
|
-
"""
|
22
|
-
x = np.zeros(len(A))
|
23
|
-
y = np.zeros(len(A))
|
24
|
-
for i, _ in enumerate(A):
|
25
|
-
x[i] = A[i][0]
|
26
|
-
y[i] = A[i][1]
|
27
|
-
return x, y
|
28
|
-
|
29
|
-
|
30
|
-
def plot_poly(geom, ax=None, color="black", linestyle="--", linewidth=0.5):
|
31
|
-
"""plot the wind plant boundaries
|
32
|
-
|
33
|
-
Parameters
|
34
|
-
----------
|
35
|
-
geom : Polygon | MultiPolygon
|
36
|
-
The shapely.Polygon or shapely.MultiPolygon that define the wind
|
37
|
-
plant boundary(ies).
|
38
|
-
ax : :py:class:`matplotlib.pyplot.axes`, optional
|
39
|
-
The figure axes on which the wind rose is plotted.
|
40
|
-
Defaults to :obj:`None`.
|
41
|
-
color : string, optional
|
42
|
-
The color for the wind plant boundaries
|
43
|
-
linestyle : string, optional
|
44
|
-
Style to plot the boundary lines
|
45
|
-
linewidth : float, optional
|
46
|
-
The width of the boundary lines
|
47
|
-
"""
|
48
|
-
if ax is None:
|
49
|
-
_, ax = plt.subplots()
|
50
|
-
|
51
|
-
if geom.type == 'Polygon':
|
52
|
-
exterior_coords = geom.exterior.coords[:]
|
53
|
-
x, y = get_xy(exterior_coords)
|
54
|
-
ax.fill(x, y, color="C0", alpha=0.25)
|
55
|
-
ax.plot(x, y, color=color, linestyle=linestyle, linewidth=linewidth)
|
56
|
-
|
57
|
-
for interior in geom.interiors:
|
58
|
-
interior_coords = interior.coords[:]
|
59
|
-
x, y = get_xy(interior_coords)
|
60
|
-
ax.fill(x, y, color="white", alpha=1.0)
|
61
|
-
ax.plot(x, y, "--k", linewidth=0.5)
|
62
|
-
|
63
|
-
elif geom.type == 'MultiPolygon':
|
64
|
-
|
65
|
-
for part in geom:
|
66
|
-
exterior_coords = part.exterior.coords[:]
|
67
|
-
x, y = get_xy(exterior_coords)
|
68
|
-
ax.fill(x, y, color="C0", alpha=0.25)
|
69
|
-
ax.plot(x, y, color=color, linestyle=linestyle,
|
70
|
-
linewidth=linewidth)
|
71
|
-
|
72
|
-
for interior in part.interiors:
|
73
|
-
interior_coords = interior.coords[:]
|
74
|
-
x, y = get_xy(interior_coords)
|
75
|
-
ax.fill(x, y, color="white", alpha=1.0)
|
76
|
-
ax.plot(x, y, "--k", linewidth=0.5)
|
77
|
-
return ax
|
78
|
-
|
79
|
-
|
80
|
-
def plot_turbines(x, y, r, ax=None, color="C0", nums=False):
|
81
|
-
"""plot wind turbine locations
|
82
|
-
|
83
|
-
Parameters
|
84
|
-
----------
|
85
|
-
x, y : array
|
86
|
-
Wind turbine x and y locations
|
87
|
-
r : float
|
88
|
-
Wind turbine radius
|
89
|
-
ax :py:class:`matplotlib.pyplot.axes`, optional
|
90
|
-
The figure axes on which the wind rose is plotted.
|
91
|
-
Defaults to :obj:`None`.
|
92
|
-
color : string, optional
|
93
|
-
The color for the wind plant boundaries
|
94
|
-
nums : bool, optional
|
95
|
-
Option to show the turbine numbers next to each turbine
|
96
|
-
"""
|
97
|
-
# Set up figure
|
98
|
-
if ax is None:
|
99
|
-
_, ax = plt.subplots()
|
100
|
-
|
101
|
-
n = len(x)
|
102
|
-
for i in range(n):
|
103
|
-
t = plt.Circle((x[i], y[i]), r, color=color)
|
104
|
-
ax.add_patch(t)
|
105
|
-
if nums is True:
|
106
|
-
ax.text(x[i], y[i], "%s" % (i + 1))
|
107
|
-
|
108
|
-
return ax
|
109
|
-
|
110
|
-
|
111
|
-
def plot_windrose(wind_directions, wind_speeds, wind_frequencies, ax=None,
|
112
|
-
colors=None):
|
113
|
-
"""plot windrose
|
114
|
-
|
115
|
-
Parameters
|
116
|
-
----------
|
117
|
-
wind_directions : 1D array
|
118
|
-
Wind direction samples
|
119
|
-
wind_speeds : 1D array
|
120
|
-
Wind speed samples
|
121
|
-
wind_frequencies : 2D array
|
122
|
-
Frequency of wind direction and speed samples
|
123
|
-
ax :py:class:`matplotlib.pyplot.axes`, optional
|
124
|
-
The figure axes on which the wind rose is plotted.
|
125
|
-
Defaults to :obj:`None`.
|
126
|
-
color : array, optional
|
127
|
-
The color for the different wind speed bins
|
128
|
-
"""
|
129
|
-
if ax is None:
|
130
|
-
_, ax = plt.subplots(subplot_kw=dict(polar=True))
|
131
|
-
|
132
|
-
ndirs = len(wind_directions)
|
133
|
-
nspeeds = len(wind_speeds)
|
134
|
-
|
135
|
-
if colors is None:
|
136
|
-
colors = []
|
137
|
-
for i in range(nspeeds):
|
138
|
-
colors = np.append(colors, "C%s" % i)
|
139
|
-
|
140
|
-
for i in range(ndirs):
|
141
|
-
wind_directions[i] = np.deg2rad(90.0 - wind_directions[i])
|
142
|
-
|
143
|
-
width = 0.8 * 2 * np.pi / len(wind_directions)
|
144
|
-
|
145
|
-
for i in range(ndirs):
|
146
|
-
bottom = 0.0
|
147
|
-
for j in range(nspeeds):
|
148
|
-
if i == 0:
|
149
|
-
if j < nspeeds - 1:
|
150
|
-
ax.bar(wind_directions[i], wind_frequencies[j, i],
|
151
|
-
bottom=bottom, width=width, edgecolor="black",
|
152
|
-
color=[colors[j]],
|
153
|
-
label="%s-%s m/s" % (int(wind_speeds[j]),
|
154
|
-
int(wind_speeds[j + 1]))
|
155
|
-
)
|
156
|
-
else:
|
157
|
-
ax.bar(wind_directions[i], wind_frequencies[j, i],
|
158
|
-
bottom=bottom, width=width, edgecolor="black",
|
159
|
-
color=[colors[j]],
|
160
|
-
label="%s+ m/s" % int(wind_speeds[j])
|
161
|
-
)
|
162
|
-
else:
|
163
|
-
ax.bar(wind_directions[i], wind_frequencies[j, i],
|
164
|
-
bottom=bottom, width=width, edgecolor="black",
|
165
|
-
color=[colors[j]])
|
166
|
-
bottom = bottom + wind_frequencies[j, i]
|
167
|
-
|
168
|
-
ax.legend(bbox_to_anchor=(1.3, 1), fontsize=10)
|
169
|
-
pi = np.pi
|
170
|
-
ax.set_xticks((0, pi / 4, pi / 2, 3 * pi / 4, pi, 5 * pi / 4,
|
171
|
-
3 * pi / 2, 7 * pi / 4))
|
172
|
-
ax.set_xticklabels(("E", "NE", "N", "NW", "W", "SW", "S", "SE"),
|
173
|
-
fontsize=10)
|
174
|
-
plt.yticks(fontsize=10)
|
175
|
-
|
176
|
-
plt.subplots_adjust(left=0.0, right=1.0, top=0.9, bottom=0.1)
|
177
|
-
|
178
|
-
return ax
|
File without changes
|
File without changes
|