NREL-reV 0.14.2__py3-none-any.whl → 0.14.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {nrel_rev-0.14.2.dist-info → nrel_rev-0.14.5.dist-info}/METADATA +5 -5
- {nrel_rev-0.14.2.dist-info → nrel_rev-0.14.5.dist-info}/RECORD +20 -20
- {nrel_rev-0.14.2.dist-info → nrel_rev-0.14.5.dist-info}/WHEEL +1 -1
- reV/bespoke/bespoke.py +96 -19
- reV/bespoke/place_turbines.py +46 -11
- reV/cli.py +3 -1
- reV/config/output_request.py +3 -0
- reV/config/project_points.py +4 -0
- reV/supply_curve/cli_sc_aggregation.py +39 -2
- reV/supply_curve/exclusions.py +55 -26
- reV/supply_curve/extent.py +18 -9
- reV/supply_curve/points.py +18 -3
- reV/supply_curve/sc_aggregation.py +64 -15
- reV/supply_curve/tech_mapping.py +8 -7
- reV/utilities/__init__.py +402 -13
- reV/utilities/cli_functions.py +43 -1
- reV/version.py +1 -1
- {nrel_rev-0.14.2.dist-info → nrel_rev-0.14.5.dist-info}/entry_points.txt +0 -0
- {nrel_rev-0.14.2.dist-info → nrel_rev-0.14.5.dist-info}/licenses/LICENSE +0 -0
- {nrel_rev-0.14.2.dist-info → nrel_rev-0.14.5.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: NREL-reV
|
3
|
-
Version: 0.14.
|
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
|
24
|
-
Requires-Dist: NREL-NRWAL
|
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
|
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.
|
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.
|
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=
|
4
|
-
reV/version.py,sha256=
|
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=
|
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=
|
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=
|
31
|
-
reV/config/project_points.py,sha256=
|
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=
|
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=
|
83
|
-
reV/supply_curve/extent.py,sha256=
|
84
|
-
reV/supply_curve/points.py,sha256=
|
85
|
-
reV/supply_curve/sc_aggregation.py,sha256=
|
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=
|
88
|
-
reV/utilities/__init__.py,sha256=
|
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=
|
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.
|
96
|
-
nrel_rev-0.14.
|
97
|
-
nrel_rev-0.14.
|
98
|
-
nrel_rev-0.14.
|
99
|
-
nrel_rev-0.14.
|
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,,
|
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,
|
262
|
-
|
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
|
-
|
1284
|
-
|
1285
|
-
|
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 = [
|
1318
|
-
|
1319
|
-
|
1320
|
-
|
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
|
-
|
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,
|
2470
|
-
|
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,
|
reV/bespoke/place_turbines.py
CHANGED
@@ -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 =
|
133
|
-
|
134
|
-
|
135
|
-
|
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
|
-
|
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"]
|
reV/config/output_request.py
CHANGED
@@ -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',
|
reV/config/project_points.py
CHANGED
@@ -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={})
|