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.
@@ -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
- for ri, res_bin in enumerate(res_class_bins):
1097
- try:
1098
- pointsum = GenerationSupplyCurvePoint.summarize(
1099
- gid,
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
- except EmptySupplyCurvePointError:
1124
- logger.debug("SC point {} is empty".format(gid))
1125
- else:
1126
- pointsum['res_class'] = ri
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
- summary.append(pointsum)
1129
- logger.debug(
1130
- "Serial aggregation completed gid {}: "
1131
- "{} out of {} points complete".format(
1132
- gid, n_finished, len(gids)
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:", "&nbsp;"]
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:])
@@ -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
- lat_lon_cols = get_lat_lon_cols(resource.meta)
78
- solar_zenith_angle = SolarPosition(
79
- resource.time_index,
80
- resource.meta[lat_lon_cols].values).zenith
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 = resource._res_arrays['precipitationrate']
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(resource.sites, curtail_mult)
131
+ resource.curtail_windspeed(sites, curtail_mult)
128
132
 
129
133
  return resource
reV/version.py CHANGED
@@ -2,4 +2,4 @@
2
2
  reV Version number
3
3
  """
4
4
 
5
- __version__ = "0.9.7"
5
+ __version__ = "0.12.2"
@@ -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