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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: NREL-reV
3
- Version: 0.14.2
3
+ Version: 0.14.5
4
4
  Summary: National Renewable Energy Laboratory's (NREL's) Renewable Energy Potential(V) Model: reV
5
5
  Author-email: Galen Maclaurin <galen.maclaurin@nrel.gov>
6
6
  Maintainer-email: Grant Buster <gbuster@nrel.gov>, Paul Pinchuk <ppinchuk@nrel.gov>
@@ -20,17 +20,17 @@ Classifier: Programming Language :: Python :: 3.13
20
20
  Requires-Python: >=3.9
21
21
  Description-Content-Type: text/x-rst
22
22
  License-File: LICENSE
23
- Requires-Dist: NREL-gaps<0.9,>=0.8.0
24
- Requires-Dist: NREL-NRWAL<0.1,>=0.0.11
23
+ Requires-Dist: NREL-gaps>=0.8.0
24
+ Requires-Dist: NREL-NRWAL>=0.0.11
25
25
  Requires-Dist: NREL-PySAM~=7.0.0
26
- Requires-Dist: NREL-rex<0.4,>=0.3.5
26
+ Requires-Dist: NREL-rex>=0.4.0
27
27
  Requires-Dist: numpy<3,>=2.0.2
28
28
  Requires-Dist: packaging<25,>=24.2
29
29
  Requires-Dist: plotly<7,>=6.0.1
30
30
  Requires-Dist: plotting<0.1,>=0.0.7
31
31
  Requires-Dist: shapely<3,>=2.0.7
32
32
  Provides-Extra: test
33
- Requires-Dist: pytest<9,>=8.3.3; extra == "test"
33
+ Requires-Dist: pytest<9,>=8.4.0; extra == "test"
34
34
  Provides-Extra: dev
35
35
  Requires-Dist: flake8; extra == "dev"
36
36
  Requires-Dist: pre-commit; extra == "dev"
@@ -1,7 +1,7 @@
1
- nrel_rev-0.14.2.dist-info/licenses/LICENSE,sha256=hDwoTANtan2ZpufBlXm5C3W_PJ-mCqItvlcobgjxL7k,1526
1
+ nrel_rev-0.14.5.dist-info/licenses/LICENSE,sha256=hDwoTANtan2ZpufBlXm5C3W_PJ-mCqItvlcobgjxL7k,1526
2
2
  reV/__init__.py,sha256=tXTpWu_qVo3uotfSw_TJ-gNbidGaIPPfUTwBlpCMJ-g,856
3
- reV/cli.py,sha256=u7G5M5moA7q8fCgC_1MB30Z7R14GNcngVf6eVNkfQU8,1682
4
- reV/version.py,sha256=iZ2NEm6j_ZMkwjscuehf0I9OUwsEHrSS3xbfjV9LCbM,51
3
+ reV/cli.py,sha256=tkJzwkGNz0h7PfKpjUAv6aM-Jpn_z8jUbXyjb5GCFBM,1792
4
+ reV/version.py,sha256=DNYUQmgQgGdiJDrMQAgH6IJ-51uB5WIunyqwp0YBFMM,51
5
5
  reV/SAM/SAM.py,sha256=3NK9rRaJzqH6wz7CU_5XguKCRhmoilOpDdFFsFgOaxQ,33163
6
6
  reV/SAM/__init__.py,sha256=LJqoncyKDY5ZP5WA4kboh561bce11F9Ge645Izah0EY,240
7
7
  reV/SAM/defaults.py,sha256=JQMJomX7wsbMzxKjx_IMnA_9QFsV2yWCyl_JToDVSJo,6703
@@ -16,19 +16,19 @@ reV/SAM/defaults/WY Southern-Flat Lands.srw,sha256=oSlFI7nnycW7SMnTXEXBXYkoh8rhV
16
16
  reV/SAM/defaults/geothermal.json,sha256=gnlxOBxxkbDDacIw6B9yP9iRkfmwj0WTWs8Zb71ytCo,7119
17
17
  reV/SAM/defaults/i_pvwattsv5.json,sha256=sF8rSe1bcqsKLXchvRxlp25dXOVe_q7rPJTf_vWR20Y,312
18
18
  reV/bespoke/__init__.py,sha256=vpXbyBUrUsTgK8UP_LafMjLiDg2CRG9WZLHPsOJoxek,109
19
- reV/bespoke/bespoke.py,sha256=ZMl4kz9oAaXpZaNa5EgiD43RvKiKl3xymRv-z4lDYmQ,113169
19
+ reV/bespoke/bespoke.py,sha256=fZvNpNeKf8LYQbDDhX4I8iOgm_3MGdCf0aq-wzQcflo,116916
20
20
  reV/bespoke/cli_bespoke.py,sha256=b6Xu0GKpXqPX3qVJ6-z0FrO97uCsH_1dVOa4r6IvesQ,2911
21
21
  reV/bespoke/gradient_free.py,sha256=URWV1yiO2jyWk3_GOpfpLV_wlgJhXXGmTUwCB3WTV0Y,12015
22
22
  reV/bespoke/pack_turbs.py,sha256=Dcd9F8obF8LPztzeycB5kxa5hXKSCiz3jC1WeAFUx28,3508
23
- reV/bespoke/place_turbines.py,sha256=NfAs0OMaXPmwHnz9sTAtntWjvwGwSE8SCQ8fGlGzNoQ,25543
23
+ reV/bespoke/place_turbines.py,sha256=pc5JofO9LAwwsQDGbxauGOoMeO6TXVr9uXABgVAADw0,27121
24
24
  reV/config/__init__.py,sha256=oqFNU4JESU_fPxFmPyQNFAXLDAdzmTlPuabXTe3Rf2Y,92
25
25
  reV/config/base_analysis_config.py,sha256=NvA3g5zQz8mIrV8ZSENLq0XBZGXa6RTGkwpZ76TVZj8,5615
26
26
  reV/config/base_config.py,sha256=a748VQ3CRs9RVi5sSEPcaWOyH6R3t5tssaFqZntHyaE,10075
27
27
  reV/config/cli_project_points.py,sha256=6edOlLNOG-ZEbcpNS2MPfu-DXjcOTEh_MEvKOvQSZj4,6010
28
28
  reV/config/curtailment.py,sha256=1bH7xzxOmD4PwLKcXFNotMAa9iCfGBUm2DKTOXViCJg,5548
29
29
  reV/config/execution.py,sha256=hyf8W7XYUXE6tXBXs-4En7h_aDTYu8FzbslgBsKLJkQ,5046
30
- reV/config/output_request.py,sha256=Sj3L5hcypLTCtLnKAqS7GSaI4_Hpb28QLuWT9IMCvrc,4520
31
- reV/config/project_points.py,sha256=CSq7lHgWfJ1pq1XlWJ5eykmGGhumkxVcg__Ln9RR4-Y,41886
30
+ reV/config/output_request.py,sha256=cww5MxOUnXE_HwwLYitsFSwwUT8mWkctSElq9-zUl_8,4725
31
+ reV/config/project_points.py,sha256=BdLwWrESsDZDQJ59rki-1Z0xXF4QUXZkbB8nsPrtOEA,42147
32
32
  reV/config/sam_config.py,sha256=xvvx2FTuliq0Sk-BjRE3I9zdDmIdwHVBnWtXCcsoc40,7998
33
33
  reV/econ/__init__.py,sha256=UId1LNaAP9lErCEXVce6JZf0qVRUvwNFOPrajdRevGo,130
34
34
  reV/econ/cli_econ.py,sha256=2KNy3JQD0EKjStaoD2r6nv3ELFw88h2E_up-UKj_sfE,4286
@@ -75,25 +75,25 @@ reV/rep_profiles/cli_rep_profiles.py,sha256=i3fRolT7HTzTQePXpNLDyxwgpRa7FEfHO1mO
75
75
  reV/rep_profiles/rep_profiles.py,sha256=dJ2jcklbelYYNS3IUXIYHg8E0poXLOWrwdqeBBnqYIE,48271
76
76
  reV/supply_curve/__init__.py,sha256=dbf0cO0qmt1VhV8dnDddztrcpnwua9T2HBhM6tLKaP8,274
77
77
  reV/supply_curve/aggregation.py,sha256=DvIUmj0PbZ8NxgOSmpXvzn9wJnOiQO2Xm2w_WCRVK-U,40908
78
- reV/supply_curve/cli_sc_aggregation.py,sha256=iDBHlUUPg-Ce4N4Rqb4scn8A_Ygq2q4i9v_ypbH-krs,3387
78
+ reV/supply_curve/cli_sc_aggregation.py,sha256=fJwhOixN3VlzImHStWfcB8Bum_POY-RRgkhWi8Slgao,4633
79
79
  reV/supply_curve/cli_supply_curve.py,sha256=e-XrHQIe4OqWTL6u-TUAyHrw7Alk7vkXQ2HoLbE3zTM,2163
80
80
  reV/supply_curve/cli_tech_mapping.py,sha256=Dirn4JOmu_3BIP7WgcRLAepsreKqmDhChHLPEUqcDAo,1735
81
81
  reV/supply_curve/competitive_wind_farms.py,sha256=eOjM72-4oWtsqxB7Wh2gnB2zVAt4LY3iPE_DqWdXbQ4,15795
82
- reV/supply_curve/exclusions.py,sha256=4-ZxTO5Vlu03vie0V_74uvdajQfCuC8FE96Pg8I4U_c,42950
83
- reV/supply_curve/extent.py,sha256=a31po753hXSxQ8lfcCvpE8hoKc4bY7MmYq0NO0jtdqA,17414
84
- reV/supply_curve/points.py,sha256=p9Kqzas1jX8uCXcvYX7o6mdUVsgeHCC_F9pjBaEypPI,93548
85
- reV/supply_curve/sc_aggregation.py,sha256=l7uXs-grLb0enblPNsEDKccSP11WZ2Pah5baWtgtvzE,68067
82
+ reV/supply_curve/exclusions.py,sha256=dzwgcXyDg5_MrcxKohW-j5YX7tWW6JlQimkUwLlhz2k,44056
83
+ reV/supply_curve/extent.py,sha256=168Vcr2XQNkoLLn3xCLfvE3udi39wbtQ7GAbvw1yys0,17917
84
+ reV/supply_curve/points.py,sha256=dl518Bv_i2jOM1N-BAVZcK3chmv23BphvHO47zcbTxU,94142
85
+ reV/supply_curve/sc_aggregation.py,sha256=VaFKS_EIPqlxlpFlxUiZiI8q_mJWdamh9fSuF6LXm28,70843
86
86
  reV/supply_curve/supply_curve.py,sha256=9zhAA_9XSxE18j1Z9FuC71Wr3I0VuakfR5mt1_gHYuU,69931
87
- reV/supply_curve/tech_mapping.py,sha256=WP-o5Sk5q1Jz_F9Rpe77_fvTYGH8R4GnYDfRMSTuHB8,17804
88
- reV/utilities/__init__.py,sha256=HVb1P-ee-z2P6UTjyc0s0gYFK7XYM71BQLEMxj26apA,10834
87
+ reV/supply_curve/tech_mapping.py,sha256=xP8j2KMY58kZ5MSH13nIs8IoDtFhNCzGY4hQeKxW33w,17878
88
+ reV/utilities/__init__.py,sha256=b99Su_feREbuKkP71W_NrlO7GH9vQRsH7TrP7f4-Eg0,22703
89
89
  reV/utilities/_clean_readme.py,sha256=IFI9wGPX5nnLTNVLJzH8IOHq9unQlAlHRu4Namib0LA,709
90
- reV/utilities/cli_functions.py,sha256=1_T_sXz0Ct8lW-vOk3mMRcpD6NYsc9cGI7dEujIi9z4,3864
90
+ reV/utilities/cli_functions.py,sha256=3q7d0MHNpb6vnzWSxQMPNXH176nGp_CHMgHCLknmv1k,5154
91
91
  reV/utilities/curtailment.py,sha256=As902-2aLGnCiVEutYfAFIOwuV--_rCQhxGNOY9RB-4,5241
92
92
  reV/utilities/exceptions.py,sha256=f7sRGsbFLpmL6Caq_H1cD4GfVhnLMyvYUsLPA1UVDDE,3974
93
93
  reV/utilities/pytest_utils.py,sha256=spCw9yQ8KEYOkQZpCi9IEmaWIvIqHqbUPDXXNQJJ68U,3241
94
94
  reV/utilities/slots.py,sha256=xsw-JuUVZ0YeoCNuwP_HxGNxFMA4xRs1tuImXHIJqaU,2618
95
- nrel_rev-0.14.2.dist-info/METADATA,sha256=lIgkZ3PQLu5YOtLYAP62nRtNVAhxkwRuHGqS_2mSwDY,10733
96
- nrel_rev-0.14.2.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
97
- nrel_rev-0.14.2.dist-info/entry_points.txt,sha256=4IfJtZm2iMJwrbC8J0Or7VjZWnFpvCaHYVpvSWfIwDA,616
98
- nrel_rev-0.14.2.dist-info/top_level.txt,sha256=S6YF2ZYgXUB6n28SY0K2H8YB9tMJdXQ9CyQbo6VC89M,4
99
- nrel_rev-0.14.2.dist-info/RECORD,,
95
+ nrel_rev-0.14.5.dist-info/METADATA,sha256=UCWoXqsoas-MeRXDg1ytuva-qI6B3XDpY1MhGb3ew_g,10718
96
+ nrel_rev-0.14.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
97
+ nrel_rev-0.14.5.dist-info/entry_points.txt,sha256=4IfJtZm2iMJwrbC8J0Or7VjZWnFpvCaHYVpvSWfIwDA,616
98
+ nrel_rev-0.14.5.dist-info/top_level.txt,sha256=S6YF2ZYgXUB6n28SY0K2H8YB9tMJdXQ9CyQbo6VC89M,4
99
+ nrel_rev-0.14.5.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.8.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
reV/bespoke/bespoke.py CHANGED
@@ -258,8 +258,9 @@ class BespokeSinglePlant:
258
258
  ws_bins=(0.0, 20.0, 5.0), wd_bins=(0.0, 360.0, 45.0),
259
259
  excl_dict=None, inclusion_mask=None, data_layers=None,
260
260
  resolution=64, excl_area=None, exclusion_shape=None,
261
- eos_mult_baseline_cap_mw=200, prior_meta=None, gid_map=None,
262
- bias_correct=None, pre_loaded_data=None, close=True):
261
+ eos_mult_baseline_cap_mw=200, convex_hull_buffer=0,
262
+ prior_meta=None, gid_map=None, bias_correct=None,
263
+ pre_loaded_data=None, close=True):
263
264
  """
264
265
  Parameters
265
266
  ----------
@@ -309,7 +310,24 @@ class BespokeSinglePlant:
309
310
  cost ($) as evaluated by
310
311
  `balance_of_system_cost_function`
311
312
  - ``self.wind_plant``: the SAM wind plant object,
312
- through which all SAM variables can be accessed
313
+ through which all SAM variables can be accessed.
314
+
315
+ .. IMPORTANT::
316
+ When using the `self.wind_plant` variable,
317
+ DO NOT include quotes around variable names (keys).
318
+
319
+ - ❌ Wrong: ``self.wind_plant["annual_energy"]``
320
+ - ✅ Correct: ``self.wind_plant[annual_energy]``
321
+
322
+ .. IMPORTANT::
323
+ It's possible for SAM wind plant variables to be
324
+ ``None``, especially if something went wrong while
325
+ optimizing the wind plant layout. In this case,
326
+ your objective function may fail to evaluate and
327
+ terminate the program entirely. To avoid this, add
328
+ a default value for the variable in your objective
329
+ function, like so:
330
+ ``(self.wind_plant[annual_energy] or 0)``
313
331
 
314
332
  capital_cost_function : str
315
333
  The plant capital cost function as a string, must return the total
@@ -387,6 +405,10 @@ class BespokeSinglePlant:
387
405
  divided by the $-per-kW of a plant with this baseline
388
406
  capacity. By default, `200` (MW), which aligns the baseline
389
407
  with ATB assumptions. See here: https://tinyurl.com/y85hnu6h.
408
+ convex_hull_buffer : float, default=0
409
+ Buffer (in m) to apply to turbine location convex hull
410
+ before computing the convex hull area and capacity density.
411
+ By default, ``0``.
390
412
  prior_meta : pd.DataFrame | None
391
413
  Optional meta dataframe belonging to a prior run. This will only
392
414
  run the timeseries power generation step and assume that all of the
@@ -443,6 +465,11 @@ class BespokeSinglePlant:
443
465
  eos_mult_baseline_cap_mw
444
466
  )
445
467
  )
468
+ logger.debug(
469
+ "Bespoke convex hull buffer: {:,} m".format(
470
+ convex_hull_buffer
471
+ )
472
+ )
446
473
 
447
474
  if isinstance(min_spacing, str) and min_spacing.endswith("x"):
448
475
  rotor_diameter = sam_sys_inputs["wind_turbine_rotor_diameter"]
@@ -475,6 +502,7 @@ class BespokeSinglePlant:
475
502
  self._ws_bins = ws_bins
476
503
  self._wd_bins = wd_bins
477
504
  self._baseline_cap_mw = eos_mult_baseline_cap_mw
505
+ self.convex_hull_buffer = convex_hull_buffer
478
506
 
479
507
  self._res_df = None
480
508
  self._prior_meta = prior_meta is not None
@@ -537,7 +565,8 @@ class BespokeSinglePlant:
537
565
  (ws_mean, *_mean) if requested.
538
566
  """
539
567
 
540
- required = ("cf_mean", "annual_energy")
568
+ required = ("cf_mean", "annual_energy",
569
+ "annual_wake_loss_internal_percent")
541
570
  for req in required:
542
571
  if req not in self._out_req:
543
572
  self._out_req.append(req)
@@ -829,8 +858,12 @@ class BespokeSinglePlant:
829
858
  # `wind_plant_pd` PC may have PC losses applied, so keep the
830
859
  # original PC as to not double count losses here
831
860
  layout_config.pop("wind_turbine_powercurve_powerout", None)
832
- config.update(layout_config)
833
861
 
862
+ # Don't bring over wind resource choice from `wind_plant_pd`
863
+ layout_config.pop("wind_resource_model_choice", None)
864
+ layout_config.pop("wind_resource_distribution", None)
865
+
866
+ config.update(layout_config)
834
867
  return config
835
868
 
836
869
  @property
@@ -1072,7 +1105,8 @@ class BespokeSinglePlant:
1072
1105
  self.balance_of_system_cost_function,
1073
1106
  self.include_mask,
1074
1107
  self.pixel_side_length,
1075
- self.min_spacing)
1108
+ self.min_spacing,
1109
+ self.convex_hull_buffer)
1076
1110
 
1077
1111
  return self._plant_optm
1078
1112
 
@@ -1248,6 +1282,14 @@ class BespokeSinglePlant:
1248
1282
  for k, v in plant.outputs.items():
1249
1283
  self._outputs[k + "-{}".format(year)] = v
1250
1284
 
1285
+ self._compute_output_means()
1286
+ self._add_extra_meta_columns()
1287
+ logger.debug("Timeseries analysis complete!")
1288
+
1289
+ return self.outputs
1290
+
1291
+ def _compute_output_means(self):
1292
+ """Compute time series means and store them in the outputs dict"""
1251
1293
  means = {}
1252
1294
  for k1, v1 in self._outputs.items():
1253
1295
  if isinstance(v1, Number) and parse_year(k1, option="boolean"):
@@ -1260,11 +1302,15 @@ class BespokeSinglePlant:
1260
1302
 
1261
1303
  self._outputs.update(means)
1262
1304
 
1305
+ def _add_extra_meta_columns(self):
1306
+ """Copy over some non-temporal datasets to meta"""
1307
+
1263
1308
  self._meta[SupplyCurveField.MEAN_RES] = self.res_df["windspeed"].mean()
1264
1309
  self._meta[SupplyCurveField.MEAN_CF_DC] = np.nan
1265
1310
  self._meta[SupplyCurveField.MEAN_CF_AC] = np.nan
1266
1311
  self._meta[SupplyCurveField.MEAN_LCOE] = np.nan
1267
1312
  self._meta[SupplyCurveField.SC_POINT_ANNUAL_ENERGY_MWH] = np.nan
1313
+ self._meta[SupplyCurveField.WAKE_LOSSES] = np.nan
1268
1314
  # copy dataset outputs to meta data for supply curve table summary
1269
1315
  if "cf_mean-means" in self.outputs:
1270
1316
  self._meta.loc[:, SupplyCurveField.MEAN_CF_AC] = self.outputs[
@@ -1279,10 +1325,10 @@ class BespokeSinglePlant:
1279
1325
  self._meta[SupplyCurveField.SC_POINT_ANNUAL_ENERGY_MWH] = (
1280
1326
  self.outputs["annual_energy-means"] / 1000
1281
1327
  )
1282
-
1283
- logger.debug("Timeseries analysis complete!")
1284
-
1285
- return self.outputs
1328
+ if "annual_wake_loss_internal_percent-means" in self.outputs:
1329
+ self._meta[SupplyCurveField.WAKE_LOSSES] = (
1330
+ self.outputs["annual_wake_loss_internal_percent-means"]
1331
+ )
1286
1332
 
1287
1333
  def run_plant_optimization(self):
1288
1334
  """Run the wind plant layout optimization and export outputs
@@ -1314,10 +1360,14 @@ class BespokeSinglePlant:
1314
1360
  system_capacity_kw = self.plant_optimizer.capacity
1315
1361
  self._outputs["system_capacity"] = system_capacity_kw
1316
1362
 
1317
- txc = [int(np.round(c)) for c in self.plant_optimizer.turbine_x]
1318
- tyc = [int(np.round(c)) for c in self.plant_optimizer.turbine_y]
1319
- pxc = [int(np.round(c)) for c in self.plant_optimizer.x_locations]
1320
- pyc = [int(np.round(c)) for c in self.plant_optimizer.y_locations]
1363
+ txc = [float(np.round(c, decimals=2))
1364
+ for c in self.plant_optimizer.turbine_x]
1365
+ tyc = [float(np.round(c, decimals=2))
1366
+ for c in self.plant_optimizer.turbine_y]
1367
+ pxc = [float(np.round(c, decimals=2))
1368
+ for c in self.plant_optimizer.x_locations]
1369
+ pyc = [float(np.round(c, decimals=2))
1370
+ for c in self.plant_optimizer.y_locations]
1321
1371
 
1322
1372
  txc = json.dumps(txc)
1323
1373
  tyc = json.dumps(tyc)
@@ -1500,8 +1550,8 @@ class BespokeWindPlants(BaseAggregation):
1500
1550
  excl_dict=None, area_filter_kernel='queen', min_area=None,
1501
1551
  resolution=64, excl_area=None, data_layers=None,
1502
1552
  pre_extract_inclusions=False, eos_mult_baseline_cap_mw=200,
1503
- prior_run=None, gid_map=None, bias_correct=None,
1504
- pre_load_data=False):
1553
+ convex_hull_buffer=0, prior_run=None, gid_map=None,
1554
+ bias_correct=None, pre_load_data=False):
1505
1555
  """reV bespoke analysis class.
1506
1556
 
1507
1557
  Much like generation, ``reV`` bespoke analysis runs SAM
@@ -1600,7 +1650,24 @@ class BespokeWindPlants(BaseAggregation):
1600
1650
  cost ($) as evaluated by
1601
1651
  `balance_of_system_cost_function`
1602
1652
  - ``self.wind_plant``: the SAM wind plant object,
1603
- through which all SAM variables can be accessed
1653
+ through which all SAM variables can be accessed.
1654
+
1655
+ .. IMPORTANT::
1656
+ When using the `self.wind_plant` variable,
1657
+ DO NOT include quotes around variable names (keys).
1658
+
1659
+ - ❌ Wrong: ``self.wind_plant["annual_energy"]``
1660
+ - ✅ Correct: ``self.wind_plant[annual_energy]``
1661
+
1662
+ .. IMPORTANT::
1663
+ It's possible for SAM wind plant variables to be
1664
+ ``None``, especially if something went wrong while
1665
+ optimizing the wind plant layout. In this case,
1666
+ your objective function may fail to evaluate and
1667
+ terminate the program entirely. To avoid this, add
1668
+ a default value for the variable in your objective
1669
+ function, like so:
1670
+ ``(self.wind_plant[annual_energy] or 0)``
1604
1671
 
1605
1672
  capital_cost_function : str
1606
1673
  The plant capital cost function written out as a string.
@@ -1866,6 +1933,10 @@ class BespokeWindPlants(BaseAggregation):
1866
1933
  divided by the $-per-kW of a plant with this baseline
1867
1934
  capacity. By default, `200` (MW), which aligns the baseline
1868
1935
  with ATB assumptions. See here: https://tinyurl.com/y85hnu6h.
1936
+ convex_hull_buffer : float, default=0
1937
+ Buffer (in m) to apply to turbine location convex hull
1938
+ before computing the convex hull area and capacity density.
1939
+ By default, ``0``.
1869
1940
  prior_run : str, optional
1870
1941
  Optional filepath to a bespoke output HDF5 file belonging to
1871
1942
  a prior run. If specified, this module will only run the
@@ -1990,6 +2061,7 @@ class BespokeWindPlants(BaseAggregation):
1990
2061
  self._wd_bins = wd_bins
1991
2062
  self._data_layers = data_layers
1992
2063
  self._eos_mult_baseline_cap_mw = eos_mult_baseline_cap_mw
2064
+ self._convex_hull_buffer = convex_hull_buffer
1993
2065
  self._prior_meta = self._parse_prior_run(prior_run)
1994
2066
  self._gid_map = BespokeSinglePlant._parse_gid_map(gid_map)
1995
2067
  self._bias_correct = Gen._parse_bc(bias_correct)
@@ -2466,8 +2538,9 @@ class BespokeWindPlants(BaseAggregation):
2466
2538
  area_filter_kernel='queen', min_area=None,
2467
2539
  resolution=64, excl_area=0.0081, data_layers=None,
2468
2540
  gids=None, exclusion_shape=None, slice_lookup=None,
2469
- eos_mult_baseline_cap_mw=200, prior_meta=None,
2470
- gid_map=None, bias_correct=None, pre_loaded_data=None):
2541
+ eos_mult_baseline_cap_mw=200, convex_hull_buffer=0,
2542
+ prior_meta=None, gid_map=None, bias_correct=None,
2543
+ pre_loaded_data=None):
2471
2544
  """
2472
2545
  Standalone serial method to run bespoke optimization.
2473
2546
  See BespokeWindPlants docstring for parameter description.
@@ -2533,6 +2606,7 @@ class BespokeWindPlants(BaseAggregation):
2533
2606
  data_layers=data_layers,
2534
2607
  exclusion_shape=exclusion_shape,
2535
2608
  eos_mult_baseline_cap_mw=eos_mult_baseline_cap_mw,
2609
+ convex_hull_buffer=convex_hull_buffer,
2536
2610
  prior_meta=prior_meta,
2537
2611
  gid_map=gid_map,
2538
2612
  bias_correct=bias_correct,
@@ -2625,6 +2699,7 @@ class BespokeWindPlants(BaseAggregation):
2625
2699
  exclusion_shape=self.shape,
2626
2700
  slice_lookup=copy.deepcopy(self.slice_lookup),
2627
2701
  eos_mult_baseline_cap_mw=self._eos_mult_baseline_cap_mw,
2702
+ convex_hull_buffer=self._convex_hull_buffer,
2628
2703
  prior_meta=self._get_prior_meta(gid),
2629
2704
  gid_map=self._gid_map,
2630
2705
  bias_correct=self._get_bc_for_gid(gid),
@@ -2689,6 +2764,7 @@ class BespokeWindPlants(BaseAggregation):
2689
2764
  afk = self._area_filter_kernel
2690
2765
  i_bc = self._get_bc_for_gid(gid)
2691
2766
  ebc = self._eos_mult_baseline_cap_mw
2767
+ chb = self._convex_hull_buffer
2692
2768
 
2693
2769
  si = self.run_serial(self._excl_fpath,
2694
2770
  self._res_fpath,
@@ -2713,6 +2789,7 @@ class BespokeWindPlants(BaseAggregation):
2713
2789
  data_layers=self._data_layers,
2714
2790
  slice_lookup=slice_lookup,
2715
2791
  eos_mult_baseline_cap_mw=ebc,
2792
+ convex_hull_buffer=chb,
2716
2793
  prior_meta=prior_meta,
2717
2794
  gid_map=self._gid_map,
2718
2795
  bias_correct=i_bc,
@@ -3,6 +3,7 @@
3
3
  """
4
4
  place turbines for bespoke wind plants
5
5
  """
6
+ import re
6
7
  from functools import wraps
7
8
 
8
9
  import numpy as np
@@ -53,7 +54,8 @@ class PlaceTurbines:
53
54
  fixed_operating_cost_function,
54
55
  variable_operating_cost_function,
55
56
  balance_of_system_cost_function,
56
- include_mask, pixel_side_length, min_spacing):
57
+ include_mask, pixel_side_length, min_spacing,
58
+ convex_hull_buffer=0):
57
59
  """
58
60
  Parameters
59
61
  ----------
@@ -95,7 +97,24 @@ class PlaceTurbines:
95
97
  cost ($) as evaluated by
96
98
  `balance_of_system_cost_function`
97
99
  - ``self.wind_plant``: the SAM wind plant object,
98
- through which all SAM variables can be accessed
100
+ through which all SAM variables can be accessed.
101
+
102
+ .. IMPORTANT::
103
+ When using the `self.wind_plant` variable,
104
+ DO NOT include quotes around variable names (keys).
105
+
106
+ - ❌ Wrong: ``self.wind_plant["annual_energy"]``
107
+ - ✅ Correct: ``self.wind_plant[annual_energy]``
108
+
109
+ .. IMPORTANT::
110
+ It's possible for SAM wind plant variables to be
111
+ ``None``, especially if something went wrong while
112
+ optimizing the wind plant layout. In this case,
113
+ your objective function may fail to evaluate and
114
+ terminate the program entirely. To avoid this, add
115
+ a default value for the variable in your objective
116
+ function, like so:
117
+ ``(self.wind_plant[annual_energy] or 0)``
99
118
 
100
119
  capital_cost_function : str
101
120
  The plant capital cost function as a string, must return the
@@ -123,21 +142,28 @@ class PlaceTurbines:
123
142
  Side length (m) of a single pixel of the `include_mask`.
124
143
  min_spacing : float
125
144
  The minimum spacing between turbines (in meters).
145
+ convex_hull_buffer : float, default=0
146
+ Buffer (in m) to apply to turbine location convex hull
147
+ before computing the convex hull area and capacity density.
148
+ By default, ``0``.
126
149
  """
127
150
 
128
151
  # inputs
129
152
  self.wind_plant = wind_plant
130
153
 
131
- self.capital_cost_function = capital_cost_function
132
- self.fixed_operating_cost_function = fixed_operating_cost_function
133
- self.variable_operating_cost_function = \
134
- variable_operating_cost_function
135
- self.balance_of_system_cost_function = balance_of_system_cost_function
154
+ self.capital_cost_function = _fix_wp_keys(capital_cost_function)
155
+ self.fixed_operating_cost_function = _fix_wp_keys(
156
+ fixed_operating_cost_function)
157
+ self.variable_operating_cost_function = _fix_wp_keys(
158
+ variable_operating_cost_function)
159
+ self.balance_of_system_cost_function = _fix_wp_keys(
160
+ balance_of_system_cost_function)
136
161
 
137
- self.objective_function = objective_function
162
+ self.objective_function = _fix_wp_keys(objective_function)
138
163
  self.include_mask = include_mask
139
164
  self.pixel_side_length = pixel_side_length
140
165
  self.min_spacing = min_spacing
166
+ self.convex_hull_buffer = convex_hull_buffer
141
167
 
142
168
  # internal variables
143
169
  self.nrows, self.ncols = np.shape(include_mask)
@@ -152,7 +178,6 @@ class PlaceTurbines:
152
178
  self.safe_polygons = None
153
179
  self._optimized_nn_conn_dist_m = None
154
180
 
155
- self.ILLEGAL = ('import ', 'os.', 'sys.', '.__', '__.', 'eval', 'exec')
156
181
  self._preflight(self.objective_function)
157
182
  self._preflight(self.capital_cost_function)
158
183
  self._preflight(self.fixed_operating_cost_function)
@@ -161,7 +186,10 @@ class PlaceTurbines:
161
186
 
162
187
  def _preflight(self, eqn):
163
188
  """Run preflight checks on the equation string."""
164
- for substr in self.ILLEGAL:
189
+ _illegal_substr = ('import ', 'os.', 'sys.', '.__', '__.', 'eval',
190
+ 'exec')
191
+
192
+ for substr in _illegal_substr:
165
193
  if substr in str(eqn):
166
194
  msg = ('Will not evaluate string which contains "{}": {}'
167
195
  .format(substr, eqn))
@@ -455,7 +483,7 @@ class PlaceTurbines:
455
483
  turbines = MultiPoint([Point(x, y)
456
484
  for x, y in zip(self.turbine_x,
457
485
  self.turbine_y)])
458
- return turbines.convex_hull
486
+ return turbines.convex_hull.buffer(self.convex_hull_buffer)
459
487
 
460
488
  @property
461
489
  @none_until_optimized
@@ -650,3 +678,10 @@ def _compute_nn_conn_dist(x_coords, y_coords):
650
678
  left_to_connect.mask[next_connection] = 1
651
679
 
652
680
  return total_dist
681
+
682
+
683
+ def _fix_wp_keys(eqn):
684
+ """Surround key of `self.wind_plant` in quotes"""
685
+ pattern = r'(self\.wind_plant\[\s*)([^\]]+?)(\s*\])'
686
+ replacement = r'\1"\2"\3'
687
+ return re.sub(pattern, replacement, str(eqn))
reV/cli.py CHANGED
@@ -10,7 +10,8 @@ from reV.generation.cli_gen import gen_command
10
10
  from reV.econ.cli_econ import econ_command
11
11
  from reV.handlers.cli_collect import collect_command
12
12
  from reV.handlers.cli_multi_year import my_command
13
- from reV.supply_curve.cli_sc_aggregation import sc_agg_command
13
+ from reV.supply_curve.cli_sc_aggregation import (sc_agg_command,
14
+ sc_col_descriptions)
14
15
  from reV.supply_curve.cli_supply_curve import sc_command
15
16
  from reV.supply_curve.cli_tech_mapping import tm_command
16
17
  from reV.rep_profiles.cli_rep_profiles import rep_profiles_command
@@ -31,6 +32,7 @@ commands = [bespoke_command, gen_command, econ_command, collect_command,
31
32
  main = make_cli(commands, info={"name": "reV", "version": __version__})
32
33
  main.add_command(qa_qc_extra)
33
34
  main.add_command(project_points)
35
+ main.add_command(sc_col_descriptions)
34
36
 
35
37
  # export GAPs commands to namespace for documentation
36
38
  batch = main.commands["batch"]
@@ -107,6 +107,9 @@ class SAMOutputRequest(OutputRequest):
107
107
  'wind_direction': 'winddirection',
108
108
  'wind-direction': 'winddirection',
109
109
  'wl': 'annual_wake_loss_internal_percent',
110
+ 'wakes': 'annual_wake_loss_internal_percent',
111
+ 'wake_loss': 'annual_wake_loss_internal_percent',
112
+ 'wake_losses': 'annual_wake_loss_internal_percent',
110
113
  'wl_kwh': 'annual_wake_loss_internal_kWh',
111
114
  'wl_pct': 'annual_wake_loss_total_percent',
112
115
  'wl_ts': 'wake_loss_internal_percent',
@@ -627,10 +627,14 @@ class ProjectPoints:
627
627
  # pylint: disable=no-member
628
628
  if SiteDataField.CONFIG not in df.columns:
629
629
  df[SiteDataField.CONFIG] = None
630
+ df[SiteDataField.CONFIG] = (df[SiteDataField.CONFIG]
631
+ .replace({np.nan: None}))
630
632
 
631
633
  # pylint: disable=no-member
632
634
  if SiteDataField.CURTAILMENT not in df.columns:
633
635
  df[SiteDataField.CURTAILMENT] = None
636
+ df[SiteDataField.CURTAILMENT] = (df[SiteDataField.CURTAILMENT]
637
+ .replace({np.nan: None}))
634
638
 
635
639
  gids = df[SiteDataField.GID].values
636
640
  if not np.array_equal(np.sort(gids), gids):
@@ -2,16 +2,21 @@
2
2
  """
3
3
  reV Supply Curve Aggregation CLI utility functions.
4
4
  """
5
- import os
6
5
  import logging
6
+ from pathlib import Path
7
7
 
8
+ import click
9
+ import pandas as pd
10
+ from rex import init_logger
8
11
  from rex.multi_file_resource import MultiFileResource
9
12
  from rex.utilities.utilities import check_res_file
10
13
  from gaps.cli import as_click_command, CLICommandFromClass
11
14
 
15
+ from reV import __version__
12
16
  from reV.supply_curve.sc_aggregation import SupplyCurveAggregation
13
17
  from reV.utilities import ModuleName
14
- from reV.utilities.cli_functions import parse_from_pipeline
18
+ from reV.utilities.cli_functions import (parse_from_pipeline,
19
+ compile_descriptions)
15
20
  from reV.utilities.exceptions import ConfigError
16
21
 
17
22
 
@@ -103,6 +108,38 @@ sc_agg_command = CLICommandFromClass(SupplyCurveAggregation, method="run",
103
108
  main = as_click_command(sc_agg_command)
104
109
 
105
110
 
111
+ @click.command()
112
+ @click.version_option(version=__version__)
113
+ @click.option('--sc_fpath', '-sc', type=click.Path(), default=None,
114
+ help='Supply curve CSV file path used to subset the columns')
115
+ @click.option('--out_fp', '-of', default=None, type=click.Path(),
116
+ help='Output CSV file for the column descriptions')
117
+ @click.pass_context
118
+ def sc_col_descriptions(ctx, sc_fpath, out_fp):
119
+ """Generate reV supply curve column descriptions"""
120
+ if ctx.obj.get('VERBOSE', False):
121
+ log_level = 'DEBUG'
122
+ else:
123
+ log_level = 'INFO'
124
+
125
+ init_logger('reV', log_level=log_level)
126
+
127
+ cols = []
128
+ if sc_fpath:
129
+ cols = pd.read_csv(sc_fpath, nrows=0).columns.tolist()
130
+
131
+ if not out_fp:
132
+ if sc_fpath:
133
+ sc_name = Path(sc_fpath).stem
134
+ out_fp = Path(sc_fpath).parent / f"{sc_name}_column_lookup.csv"
135
+ else:
136
+ out_fp = "rev_supply_curve_column_lookup.csv"
137
+
138
+ columns = compile_descriptions(cols)
139
+ columns.to_csv(out_fp, index=False)
140
+ logger.info("Column descriptions saved to %s", out_fp)
141
+
142
+
106
143
  if __name__ == '__main__':
107
144
  try:
108
145
  main(obj={})