NREL-reV 0.14.4__tar.gz → 0.14.5__tar.gz

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.
Files changed (106) hide show
  1. {nrel_rev-0.14.4 → nrel_rev-0.14.5/NREL_reV.egg-info}/PKG-INFO +1 -1
  2. {nrel_rev-0.14.4/NREL_reV.egg-info → nrel_rev-0.14.5}/PKG-INFO +1 -1
  3. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/bespoke/bespoke.py +47 -6
  4. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/bespoke/place_turbines.py +38 -9
  5. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/cli.py +3 -1
  6. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/config/project_points.py +4 -0
  7. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/supply_curve/cli_sc_aggregation.py +39 -2
  8. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/supply_curve/exclusions.py +55 -26
  9. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/supply_curve/points.py +8 -3
  10. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/supply_curve/sc_aggregation.py +64 -15
  11. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/supply_curve/tech_mapping.py +8 -7
  12. nrel_rev-0.14.5/reV/utilities/__init__.py +722 -0
  13. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/utilities/cli_functions.py +43 -1
  14. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/version.py +1 -1
  15. nrel_rev-0.14.4/reV/utilities/__init__.py +0 -334
  16. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/LICENSE +0 -0
  17. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/MANIFEST.in +0 -0
  18. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/NREL_reV.egg-info/SOURCES.txt +0 -0
  19. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/NREL_reV.egg-info/dependency_links.txt +0 -0
  20. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/NREL_reV.egg-info/entry_points.txt +0 -0
  21. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/NREL_reV.egg-info/requires.txt +0 -0
  22. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/NREL_reV.egg-info/top_level.txt +0 -0
  23. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/README.rst +0 -0
  24. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/pyproject.toml +0 -0
  25. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/SAM/SAM.py +0 -0
  26. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/SAM/__init__.py +0 -0
  27. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/SAM/defaults/USA AZ Phoenix Sky Harbor Intl Ap (TMY3).csv +0 -0
  28. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/SAM/defaults/USA CA Daggett (TMY2).csv +0 -0
  29. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/SAM/defaults/US_Wave.csv +0 -0
  30. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/SAM/defaults/WY Southern-Flat Lands.srw +0 -0
  31. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/SAM/defaults/geothermal.json +0 -0
  32. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/SAM/defaults/i_pvwattsv5.json +0 -0
  33. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/SAM/defaults.py +0 -0
  34. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/SAM/econ.py +0 -0
  35. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/SAM/generation.py +0 -0
  36. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/SAM/version_checker.py +0 -0
  37. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/SAM/windbos.py +0 -0
  38. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/__init__.py +0 -0
  39. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/bespoke/__init__.py +0 -0
  40. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/bespoke/cli_bespoke.py +0 -0
  41. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/bespoke/gradient_free.py +0 -0
  42. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/bespoke/pack_turbs.py +0 -0
  43. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/config/__init__.py +0 -0
  44. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/config/base_analysis_config.py +0 -0
  45. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/config/base_config.py +0 -0
  46. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/config/cli_project_points.py +0 -0
  47. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/config/curtailment.py +0 -0
  48. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/config/execution.py +0 -0
  49. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/config/output_request.py +0 -0
  50. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/config/sam_config.py +0 -0
  51. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/econ/__init__.py +0 -0
  52. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/econ/cli_econ.py +0 -0
  53. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/econ/econ.py +0 -0
  54. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/econ/economies_of_scale.py +0 -0
  55. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/econ/utilities.py +0 -0
  56. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/generation/__init__.py +0 -0
  57. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/generation/base.py +0 -0
  58. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/generation/cli_gen.py +0 -0
  59. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/generation/generation.py +0 -0
  60. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/generation/output_attributes/generation.json +0 -0
  61. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/generation/output_attributes/lcoe_fcr.json +0 -0
  62. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/generation/output_attributes/lcoe_fcr_inputs.json +0 -0
  63. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/generation/output_attributes/linear_fresnel.json +0 -0
  64. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/generation/output_attributes/other.json +0 -0
  65. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/generation/output_attributes/single_owner.json +0 -0
  66. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/generation/output_attributes/solar_water_heat.json +0 -0
  67. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/generation/output_attributes/trough_heat.json +0 -0
  68. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/generation/output_attributes/windbos.json +0 -0
  69. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/handlers/__init__.py +0 -0
  70. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/handlers/cli_collect.py +0 -0
  71. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/handlers/cli_multi_year.py +0 -0
  72. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/handlers/exclusions.py +0 -0
  73. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/handlers/multi_year.py +0 -0
  74. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/handlers/outputs.py +0 -0
  75. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/handlers/transmission.py +0 -0
  76. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/hybrids/__init__.py +0 -0
  77. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/hybrids/cli_hybrids.py +0 -0
  78. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/hybrids/hybrid_methods.py +0 -0
  79. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/hybrids/hybrids.py +0 -0
  80. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/losses/__init__.py +0 -0
  81. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/losses/power_curve.py +0 -0
  82. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/losses/scheduled.py +0 -0
  83. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/losses/utils.py +0 -0
  84. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/nrwal/__init__.py +0 -0
  85. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/nrwal/cli_nrwal.py +0 -0
  86. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/nrwal/nrwal.py +0 -0
  87. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/qa_qc/__init__.py +0 -0
  88. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/qa_qc/cli_qa_qc.py +0 -0
  89. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/qa_qc/qa_qc.py +0 -0
  90. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/qa_qc/summary.py +0 -0
  91. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/rep_profiles/__init__.py +0 -0
  92. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/rep_profiles/cli_rep_profiles.py +0 -0
  93. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/rep_profiles/rep_profiles.py +0 -0
  94. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/supply_curve/__init__.py +0 -0
  95. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/supply_curve/aggregation.py +0 -0
  96. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/supply_curve/cli_supply_curve.py +0 -0
  97. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/supply_curve/cli_tech_mapping.py +0 -0
  98. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/supply_curve/competitive_wind_farms.py +0 -0
  99. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/supply_curve/extent.py +0 -0
  100. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/supply_curve/supply_curve.py +0 -0
  101. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/utilities/_clean_readme.py +0 -0
  102. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/utilities/curtailment.py +0 -0
  103. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/utilities/exceptions.py +0 -0
  104. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/utilities/pytest_utils.py +0 -0
  105. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/reV/utilities/slots.py +0 -0
  106. {nrel_rev-0.14.4 → nrel_rev-0.14.5}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: NREL-reV
3
- Version: 0.14.4
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>
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: NREL-reV
3
- Version: 0.14.4
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>
@@ -310,7 +310,24 @@ class BespokeSinglePlant:
310
310
  cost ($) as evaluated by
311
311
  `balance_of_system_cost_function`
312
312
  - ``self.wind_plant``: the SAM wind plant object,
313
- 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)``
314
331
 
315
332
  capital_cost_function : str
316
333
  The plant capital cost function as a string, must return the total
@@ -1265,6 +1282,14 @@ class BespokeSinglePlant:
1265
1282
  for k, v in plant.outputs.items():
1266
1283
  self._outputs[k + "-{}".format(year)] = v
1267
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"""
1268
1293
  means = {}
1269
1294
  for k1, v1 in self._outputs.items():
1270
1295
  if isinstance(v1, Number) and parse_year(k1, option="boolean"):
@@ -1277,6 +1302,9 @@ class BespokeSinglePlant:
1277
1302
 
1278
1303
  self._outputs.update(means)
1279
1304
 
1305
+ def _add_extra_meta_columns(self):
1306
+ """Copy over some non-temporal datasets to meta"""
1307
+
1280
1308
  self._meta[SupplyCurveField.MEAN_RES] = self.res_df["windspeed"].mean()
1281
1309
  self._meta[SupplyCurveField.MEAN_CF_DC] = np.nan
1282
1310
  self._meta[SupplyCurveField.MEAN_CF_AC] = np.nan
@@ -1302,10 +1330,6 @@ class BespokeSinglePlant:
1302
1330
  self.outputs["annual_wake_loss_internal_percent-means"]
1303
1331
  )
1304
1332
 
1305
- logger.debug("Timeseries analysis complete!")
1306
-
1307
- return self.outputs
1308
-
1309
1333
  def run_plant_optimization(self):
1310
1334
  """Run the wind plant layout optimization and export outputs
1311
1335
  to outputs property.
@@ -1626,7 +1650,24 @@ class BespokeWindPlants(BaseAggregation):
1626
1650
  cost ($) as evaluated by
1627
1651
  `balance_of_system_cost_function`
1628
1652
  - ``self.wind_plant``: the SAM wind plant object,
1629
- 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)``
1630
1671
 
1631
1672
  capital_cost_function : str
1632
1673
  The plant capital cost function written out as a string.
@@ -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
@@ -96,7 +97,24 @@ class PlaceTurbines:
96
97
  cost ($) as evaluated by
97
98
  `balance_of_system_cost_function`
98
99
  - ``self.wind_plant``: the SAM wind plant object,
99
- 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)``
100
118
 
101
119
  capital_cost_function : str
102
120
  The plant capital cost function as a string, must return the
@@ -133,13 +151,15 @@ class PlaceTurbines:
133
151
  # inputs
134
152
  self.wind_plant = wind_plant
135
153
 
136
- self.capital_cost_function = capital_cost_function
137
- self.fixed_operating_cost_function = fixed_operating_cost_function
138
- self.variable_operating_cost_function = \
139
- variable_operating_cost_function
140
- 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)
141
161
 
142
- self.objective_function = objective_function
162
+ self.objective_function = _fix_wp_keys(objective_function)
143
163
  self.include_mask = include_mask
144
164
  self.pixel_side_length = pixel_side_length
145
165
  self.min_spacing = min_spacing
@@ -158,7 +178,6 @@ class PlaceTurbines:
158
178
  self.safe_polygons = None
159
179
  self._optimized_nn_conn_dist_m = None
160
180
 
161
- self.ILLEGAL = ('import ', 'os.', 'sys.', '.__', '__.', 'eval', 'exec')
162
181
  self._preflight(self.objective_function)
163
182
  self._preflight(self.capital_cost_function)
164
183
  self._preflight(self.fixed_operating_cost_function)
@@ -167,7 +186,10 @@ class PlaceTurbines:
167
186
 
168
187
  def _preflight(self, eqn):
169
188
  """Run preflight checks on the equation string."""
170
- for substr in self.ILLEGAL:
189
+ _illegal_substr = ('import ', 'os.', 'sys.', '.__', '__.', 'eval',
190
+ 'exec')
191
+
192
+ for substr in _illegal_substr:
171
193
  if substr in str(eqn):
172
194
  msg = ('Will not evaluate string which contains "{}": {}'
173
195
  .format(substr, eqn))
@@ -656,3 +678,10 @@ def _compute_nn_conn_dist(x_coords, y_coords):
656
678
  left_to_connect.mask[next_connection] = 1
657
679
 
658
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))
@@ -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"]
@@ -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={})
@@ -3,6 +3,7 @@
3
3
  Generate reV inclusion mask from exclusion layers
4
4
  """
5
5
  import logging
6
+ import fnmatch
6
7
  from warnings import warn
7
8
 
8
9
  import numpy as np
@@ -132,10 +133,10 @@ class LayerMask:
132
133
  layer is equal to 1, 2, 3, 4, or 5**. Outside of these
133
134
  regions (i.e. outside of federal park regions), the viewshed
134
135
  exclusion is **NOT** applied. If the extent mask created by
135
- these options is not boolean, an error is thrown (i.e. do
136
- not specify `weight` or `use_as_weights`).
137
- By default ``None``, which applies the original layer mask
138
- to the full extent.
136
+ these options is not boolean, an error is thrown (in other
137
+ words, do not specify `weight` or `use_as_weights` or else
138
+ you will run into errors). By default ``None``, which
139
+ applies the original layer mask to the full extent.
139
140
  **kwargs
140
141
  Optional inputs to maintain legacy kwargs of ``inclusion_*``
141
142
  instead of ``include_*``.
@@ -600,19 +601,7 @@ class ExclusionMask:
600
601
  self._check_layers = check_layers
601
602
 
602
603
  if layers is not None:
603
- if not isinstance(layers, list):
604
- layers = [layers]
605
-
606
- missing = [layer.name for layer in layers
607
- if layer.name not in self.excl_layers]
608
- if any(missing):
609
- msg = ("ExclusionMask layers {} are missing from: {}"
610
- .format(missing, self._excl_h5))
611
- logger.error(msg)
612
- raise KeyError(msg)
613
-
614
- for layer in layers:
615
- self.add_layer(layer)
604
+ self._add_many_layers(layers)
616
605
 
617
606
  if kernel in ["queen", "rook"]:
618
607
  self._min_area = min_area
@@ -623,6 +612,22 @@ class ExclusionMask:
623
612
  else:
624
613
  raise KeyError('kernel must be "queen" or "rook"')
625
614
 
615
+ def _add_many_layers(self, layers):
616
+ """Add multiple layers (with check for missing layers)"""
617
+ if not isinstance(layers, list):
618
+ layers = [layers]
619
+
620
+ missing = [layer.name for layer in layers
621
+ if layer.name not in self.excl_layers]
622
+ if any(missing):
623
+ msg = ("ExclusionMask layers {} are missing from: {}"
624
+ .format(missing, self._excl_h5))
625
+ logger.error(msg)
626
+ raise KeyError(msg)
627
+
628
+ for layer in layers:
629
+ self.add_layer(layer)
630
+
626
631
  def __enter__(self):
627
632
  return self
628
633
 
@@ -1113,7 +1118,7 @@ class ExclusionMaskFromDict(ExclusionMask):
1113
1118
  excl_h5 : str | list | tuple
1114
1119
  Path to one or more exclusions .h5 files
1115
1120
  layers_dict : dict | NoneType
1116
- Dictionary of LayerMask arugments {layer: {kwarg: value}}
1121
+ Dictionary of LayerMask arguments {layer: {kwarg: value}}
1117
1122
  min_area : float | NoneType
1118
1123
  Minimum required contiguous area in sq-km
1119
1124
  kernel : str
@@ -1125,16 +1130,34 @@ class ExclusionMaskFromDict(ExclusionMask):
1125
1130
  Run a pre-flight check on each layer to ensure they contain
1126
1131
  un-excluded values
1127
1132
  """
1128
- if layers_dict is not None:
1129
- layers = []
1130
- for layer, kwargs in layers_dict.items():
1131
- layers.append(LayerMask(layer, **kwargs))
1132
- else:
1133
- layers = None
1134
-
1135
- super().__init__(excl_h5, layers=layers, min_area=min_area,
1133
+ super().__init__(excl_h5, layers=layers_dict, min_area=min_area,
1136
1134
  kernel=kernel, hsds=hsds, check_layers=check_layers)
1137
1135
 
1136
+ def _add_many_layers(self, layers):
1137
+ """Add multiple layers (with check for missing layers)"""
1138
+ missing = set()
1139
+ final_layers = {}
1140
+
1141
+ # sort pattern-first so that users can overwrite specific layers
1142
+ sorted_layers = sorted(layers, key=_unix_patterns_first)
1143
+ for layer_pattern in sorted_layers:
1144
+ kwargs = layers[layer_pattern]
1145
+ layer_names = fnmatch.filter(self.excl_layers, layer_pattern)
1146
+ if not layer_names:
1147
+ missing.add(layer_pattern)
1148
+
1149
+ for layer in layer_names:
1150
+ final_layers[layer] = LayerMask(layer, **kwargs)
1151
+
1152
+ if any(missing):
1153
+ msg = ("ExclusionMask layers {} are missing from: {}"
1154
+ .format(missing, self._excl_h5))
1155
+ logger.error(msg)
1156
+ raise KeyError(msg)
1157
+
1158
+ for layer in final_layers.values():
1159
+ self.add_layer(layer)
1160
+
1138
1161
  @classmethod
1139
1162
  def extract_inclusion_mask(cls, excl_fpath, tm_dset, excl_dict=None,
1140
1163
  area_filter_kernel='queen', min_area=None):
@@ -1295,3 +1318,9 @@ class FrictionMask(ExclusionMask):
1295
1318
  mask = f.mask
1296
1319
 
1297
1320
  return mask
1321
+
1322
+
1323
+ def _unix_patterns_first(layer_name):
1324
+ """Key that will put layer names with unix patterns first"""
1325
+ special_chars = {"?", "*", "!", "[", "]"}
1326
+ return -1 * any(char in layer_name for char in special_chars), layer_name
@@ -1,4 +1,5 @@
1
1
  # -*- coding: utf-8 -*-
2
+ # pylint: disable=anomalous-backslash-in-string
2
3
  """
3
4
  reV supply curve points frameworks.
4
5
  """
@@ -1452,8 +1453,12 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
1452
1453
  """Supply curve point summary framework that ties a reV SC point to its
1453
1454
  respective generation and resource data."""
1454
1455
 
1455
- # technology-dependent power density estimates in MW/km2
1456
1456
  POWER_DENSITY = {"pv": 36, "wind": 3}
1457
+ """Technology-dependent power density estimates (in MW/km\ :sup:`2`).
1458
+
1459
+ The PV power density is a \**DC power density*\*, while the wind power
1460
+ density is an \**AC power density*\*.
1461
+ """
1457
1462
 
1458
1463
  def __init__(
1459
1464
  self,
@@ -2116,7 +2121,7 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
2116
2121
  `None` for solar runs with "dc_ac_ratio" dataset in the
2117
2122
  generation file
2118
2123
  """
2119
- if self.power_density_ac is None:
2124
+ if "dc_ac_ratio" not in self.gen.datasets:
2120
2125
  return None
2121
2126
 
2122
2127
  return self.area * self.power_density_ac
@@ -2138,7 +2143,7 @@ class GenerationSupplyCurvePoint(AggregationSupplyCurvePoint):
2138
2143
  `None` for solar runs with "dc_ac_ratio" dataset in the
2139
2144
  generation file
2140
2145
  """
2141
- if self.power_density_ac is None:
2146
+ if "dc_ac_ratio" not in self.gen.datasets:
2142
2147
  return None
2143
2148
 
2144
2149
  return self.area * self.power_density
@@ -245,6 +245,7 @@ class SupplyCurveAggFileHandler(AbstractAggFileHandler):
245
245
  class SupplyCurveAggregation(BaseAggregation):
246
246
  """SupplyCurveAggregation"""
247
247
 
248
+ # pylint: disable=line-too-long
248
249
  def __init__(self, excl_fpath, tm_dset, econ_fpath=None,
249
250
  excl_dict=None, area_filter_kernel='queen', min_area=None,
250
251
  resolution=64, excl_area=None, res_fpath=None, gids=None,
@@ -334,6 +335,12 @@ class SupplyCurveAggregation(BaseAggregation):
334
335
  "exclude_nodata": True,
335
336
  "nodata_value": -1
336
337
  },
338
+ "wildcard*exclusion": {
339
+ "exclude_values": 1,
340
+ },
341
+ "wildcard_unique_exclusion": {
342
+ "exclude_values": [1, 2, 3],
343
+ },
337
344
  "partial_setback": {
338
345
  "use_as_weights": True
339
346
  },
@@ -359,9 +366,24 @@ class SupplyCurveAggregation(BaseAggregation):
359
366
  ...
360
367
  }
361
368
 
362
- Note that all the keys given in this dictionary should be
363
- datasets of the `excl_fpath` file. If ``None`` or empty
364
- dictionary, no exclusions are applied. By default, ``None``.
369
+ Note that all the keys given in this dictionary must be
370
+ datasets of the `excl_fpath` file or you will get an error.
371
+ You *may* include Unix-style wildcards (i.e. ``*``, ``?``,
372
+ or ``[]``) in the keys, but note that the same exclusion
373
+ configuration will be applied to **all** datasets that match
374
+ the wildcard pattern unless you explicitly override it for a
375
+ specific layer. For example, in the configuration above,
376
+ **all** of the layers matching the pattern
377
+ ``wildcard*exclusion`` will be used as exclusions where the
378
+ respective layer values equal ``1``, **except** for the
379
+ ``wildcard_unique_exclusion`` layer, which will be used as
380
+ an exclusion wherever that particular layer values equal
381
+ ``1``, ``2``, or ``3``. You can use this strategy to
382
+ "exclude" layers from the wildcard match - simply set the
383
+ ``exclude_values`` key to a value that does not exist in
384
+ that layer and it will be effectively ignored. If ``None``
385
+ or empty dictionary, no exclusions are applied.
386
+ By default, ``None``.
365
387
  area_filter_kernel : {"queen", "rook"}, optional
366
388
  Contiguous area filter method to use on final exclusions
367
389
  mask. The filters are defined as::
@@ -484,16 +506,36 @@ class SupplyCurveAggregation(BaseAggregation):
484
506
  The ``"output_layer_name"`` is the column name under which
485
507
  the aggregated data will appear in the output CSV file. The
486
508
  ``"output_layer_name"`` does not have to match the ``dset``
487
- input value. The latter should match the layer name in the
509
+ input value. The ``dset`` should match the layer name in the
488
510
  HDF5 from which the data to aggregate should be pulled. The
489
- ``method`` should be one of
490
- ``{"mode", "mean", "min", "max", "sum", "category"}``,
491
- describing how the high-resolution data should be aggregated
492
- for each supply curve point. ``fpath`` is an optional key
493
- that can point to an HDF5 file containing the layer data. If
494
- left out, the data is assumed to exist in the file(s)
495
- specified by the `excl_fpath` input. If ``None``, no data
496
- layer aggregation is performed. By default, ``None``
511
+ ``method`` key should be one of the following:
512
+
513
+ - ``"mode"``: Output values will be the numerical mode
514
+ of the non-excluded high resolution data layer cell
515
+ values
516
+ - ``"mean"``: Output values will be the arithmetic mean
517
+ of the non-excluded high resolution data layer cell
518
+ values
519
+ - ``"min"``: Output values will be the numerical minimum
520
+ value of the non-excluded high resolution data layer
521
+ cell values
522
+ - ``"max"``: Output values will be the numerical maximum
523
+ value of the non-excluded high resolution data layer
524
+ cell values
525
+ - ``"sum"``: Output values will be the sum of the
526
+ non-excluded high resolution data layer cell values
527
+ - ``"category"``: Output values will be a string
528
+ representation of a dictionary where the keys are the
529
+ unique values of the non-excluded high resolution data
530
+ layer cells and the values are the *total
531
+ high-resolution pixel area* corresponding to that data
532
+ layer value
533
+
534
+ ``fpath`` is an optional key that can point to an HDF5 file
535
+ containing the layer data. If left out, the data is assumed
536
+ to exist in the file(s) specified by the `excl_fpath` input.
537
+ If ``None``, no data layer aggregation is performed.
538
+ By default, ``None``
497
539
  power_density : float | str, optional
498
540
  Power density value (in MW/km\ :sup:`2`) or filepath to
499
541
  variable power density CSV file containing the following
@@ -503,8 +545,15 @@ class SupplyCurveAggregation(BaseAggregation):
503
545
  - ``power_density`` : power density value (in
504
546
  MW/km\ :sup:`2`)
505
547
 
506
- If ``None``, a constant power density is inferred from the
507
- generation meta data technology. By default, ``None``.
548
+ If you are running reV for PV (more specifically, you have a
549
+ `dc_ac_ratio` in your generation file), then this input
550
+ should represent the \**DC power density*\*. For all other
551
+ technologies (wind, geothermal, etc), this input should
552
+ represent the \**AC power density*\*. If ``None``, a
553
+ constant power density value is pulled from
554
+ :obj:`~reV.supply_curve.points.GenerationSupplyCurvePoint.POWER_DENSITY`
555
+ by looking up the technology from the generation meta data.
556
+ By default, ``None``.
508
557
  friction_fpath : str, optional
509
558
  Filepath to friction surface data (cost based exclusions).
510
559
  Must be paired with the `friction_dset` input below. The
@@ -1218,7 +1267,7 @@ class SupplyCurveAggregation(BaseAggregation):
1218
1267
  .format(gid, zone_id))
1219
1268
  else:
1220
1269
  pointsum['res_class'] = ri
1221
- pointsum['zone_id'] = zone_id
1270
+ pointsum[SupplyCurveField.ZONE_ID] = zone_id
1222
1271
 
1223
1272
  summary.append(pointsum)
1224
1273
  logger.debug(
@@ -32,6 +32,7 @@ class TechMapping:
32
32
 
33
33
  def __init__(self, excl_fpath, sc_resolution=1200):
34
34
  """
35
+
35
36
  Parameters
36
37
  ----------
37
38
  excl_fpath : str
@@ -44,9 +45,9 @@ class TechMapping:
44
45
  map the exclusion pixels in 1200x1200 pixel chunks.
45
46
 
46
47
  .. Note:: This parameter does not affect the exclusion to resource
47
- (tech) mapping, which deviates from how the effect of the
48
- ``sc_resolution`` parameter works in other functionality within
49
- ``reV``.
48
+ (tech) mapping, which deviates from how the effect of the
49
+ ``sc_resolution`` parameter works in other functionality
50
+ within ``reV``.
50
51
 
51
52
  """
52
53
  self._excl_fpath = excl_fpath
@@ -433,7 +434,7 @@ class TechMapping:
433
434
  techmap (exclusions-to-resource mapping data) will be saved.
434
435
 
435
436
  .. Important:: If this dataset already exists in the h5 file,
436
- it will be overwritten.
437
+ it will be overwritten.
437
438
 
438
439
  sc_resolution : int | None, optional
439
440
  Defines how many exclusion pixels are mapped at a time. Units
@@ -442,9 +443,9 @@ class TechMapping:
442
443
  map the exclusion pixels in 1200x1200 pixel chunks.
443
444
 
444
445
  .. Note:: This parameter does not affect the exclusion to resource
445
- (tech) mapping, which deviates from how the effect of the
446
- ``sc_resolution`` parameter works in other functionality within
447
- ``reV``.
446
+ (tech) mapping, which deviates from how the effect of the
447
+ ``sc_resolution`` parameter works in other functionality
448
+ within ``reV``.
448
449
 
449
450
  dist_margin : float, optional
450
451
  Extra margin to multiply times the computed distance between