NREL-reV 0.8.7__py3-none-any.whl → 0.9.0__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.
Files changed (43) hide show
  1. {NREL_reV-0.8.7.dist-info → NREL_reV-0.9.0.dist-info}/METADATA +13 -10
  2. {NREL_reV-0.8.7.dist-info → NREL_reV-0.9.0.dist-info}/RECORD +43 -43
  3. {NREL_reV-0.8.7.dist-info → NREL_reV-0.9.0.dist-info}/WHEEL +1 -1
  4. reV/SAM/SAM.py +217 -133
  5. reV/SAM/econ.py +18 -14
  6. reV/SAM/generation.py +611 -422
  7. reV/SAM/windbos.py +93 -79
  8. reV/bespoke/bespoke.py +681 -377
  9. reV/bespoke/cli_bespoke.py +2 -0
  10. reV/bespoke/place_turbines.py +187 -43
  11. reV/config/output_request.py +2 -1
  12. reV/config/project_points.py +218 -140
  13. reV/econ/econ.py +166 -114
  14. reV/econ/economies_of_scale.py +91 -45
  15. reV/generation/base.py +331 -184
  16. reV/generation/generation.py +326 -200
  17. reV/generation/output_attributes/lcoe_fcr_inputs.json +38 -3
  18. reV/handlers/__init__.py +0 -1
  19. reV/handlers/exclusions.py +16 -15
  20. reV/handlers/multi_year.py +57 -26
  21. reV/handlers/outputs.py +6 -5
  22. reV/handlers/transmission.py +44 -27
  23. reV/hybrids/hybrid_methods.py +30 -30
  24. reV/hybrids/hybrids.py +305 -189
  25. reV/nrwal/nrwal.py +262 -168
  26. reV/qa_qc/cli_qa_qc.py +14 -10
  27. reV/qa_qc/qa_qc.py +217 -119
  28. reV/qa_qc/summary.py +228 -146
  29. reV/rep_profiles/rep_profiles.py +349 -230
  30. reV/supply_curve/aggregation.py +349 -188
  31. reV/supply_curve/competitive_wind_farms.py +90 -48
  32. reV/supply_curve/exclusions.py +138 -85
  33. reV/supply_curve/extent.py +75 -50
  34. reV/supply_curve/points.py +735 -390
  35. reV/supply_curve/sc_aggregation.py +357 -248
  36. reV/supply_curve/supply_curve.py +604 -347
  37. reV/supply_curve/tech_mapping.py +144 -82
  38. reV/utilities/__init__.py +274 -16
  39. reV/utilities/pytest_utils.py +8 -4
  40. reV/version.py +1 -1
  41. {NREL_reV-0.8.7.dist-info → NREL_reV-0.9.0.dist-info}/LICENSE +0 -0
  42. {NREL_reV-0.8.7.dist-info → NREL_reV-0.9.0.dist-info}/entry_points.txt +0 -0
  43. {NREL_reV-0.8.7.dist-info → NREL_reV-0.9.0.dist-info}/top_level.txt +0 -0
@@ -54,6 +54,8 @@ def _log_bespoke_cli_inputs(config):
54
54
  .format(config.get("fixed_operating_cost_function")))
55
55
  logger.info('Bespoke variable operating cost function: "{}"'
56
56
  .format(config.get("variable_operating_cost_function")))
57
+ logger.info('Bespoke balance of system cost function: "{}"'
58
+ .format(config.get("balance_of_system_cost_function")))
57
59
  logger.info('Bespoke wake loss multiplier: "{}"'
58
60
  .format(config.get("wake_loss_multiplier", 1)))
59
61
  logger.info('The following project points were specified: "{}"'
@@ -4,11 +4,10 @@
4
4
  place turbines for bespoke wind plants
5
5
  """
6
6
  import numpy as np
7
+ from shapely.geometry import MultiPoint, MultiPolygon, Point, Polygon
7
8
 
8
- from shapely.geometry import Point, Polygon, MultiPolygon, MultiPoint
9
-
10
- from reV.bespoke.pack_turbs import PackTurbines
11
9
  from reV.bespoke.gradient_free import GeneticAlgorithm
10
+ from reV.bespoke.pack_turbs import PackTurbines
12
11
  from reV.utilities.exceptions import WhileLoopPackingError
13
12
 
14
13
 
@@ -49,51 +48,74 @@ class PlaceTurbines:
49
48
  capital_cost_function,
50
49
  fixed_operating_cost_function,
51
50
  variable_operating_cost_function,
51
+ balance_of_system_cost_function,
52
52
  include_mask, pixel_side_length, min_spacing,
53
53
  wake_loss_multiplier=1):
54
54
  """
55
55
  Parameters
56
56
  ----------
57
57
  wind_plant : WindPowerPD
58
- wind plant object to analyze wind plant performance. This object
59
- should have everything in the plant defined, such that only the
60
- turbine coordinates and plant capacity need to be defined during
61
- the optimization.
58
+ wind plant object to analyze wind plant performance. This
59
+ object should have everything in the plant defined, such
60
+ that only the turbine coordinates and plant capacity need to
61
+ be defined during the optimization.
62
62
  objective_function : str
63
- The objective function of the optimization as a string, should
64
- return the objective to be minimized during layout optimization.
65
- Variables available are:
66
-
67
- - n_turbines: the number of turbines
68
- - system_capacity: wind plant capacity
69
- - aep: annual energy production
70
- - fixed_charge_rate: user input fixed_charge_rate if included
71
- as part of the sam system config.
72
- - capital_cost: plant capital cost as evaluated
63
+ The objective function of the optimization as a string,
64
+ should return the objective to be minimized during layout
65
+ optimization. Variables available are:
66
+
67
+ - ``n_turbines``: the number of turbines
68
+ - ``system_capacity``: wind plant capacity
69
+ - ``aep``: annual energy production
70
+ - ``avg_sl_dist_to_center_m``: Average straight-line
71
+ distance to the supply curve point center from all
72
+ turbine locations (in m). Useful for computing plant
73
+ BOS costs.
74
+ - ``avg_sl_dist_to_medoid_m``: Average straight-line
75
+ distance to the medoid of all turbine locations
76
+ (in m). Useful for computing plant BOS costs.
77
+ - ``nn_conn_dist_m``: Total BOS connection distance
78
+ using nearest-neighbor connections. This variable is
79
+ only available for the
80
+ ``balance_of_system_cost_function`` equation.
81
+ - ``fixed_charge_rate``: user input fixed_charge_rate if
82
+ included as part of the sam system config.
83
+ - ``capital_cost``: plant capital cost as evaluated
73
84
  by `capital_cost_function`
74
- - fixed_operating_cost: plant fixed annual operating cost as
75
- evaluated by `fixed_operating_cost_function`
76
- - variable_operating_cost: plant variable annual operating cost
77
- as evaluated by `variable_operating_cost_function`
78
- - self.wind_plant: the SAM wind plant object, through which
79
- all SAM variables can be accessed
80
- - cost: the annual cost of the wind plant (from cost_function)
85
+ - ``fixed_operating_cost``: plant fixed annual operating
86
+ cost as evaluated by `fixed_operating_cost_function`
87
+ - ``variable_operating_cost``: plant variable annual
88
+ operating cost as evaluated by
89
+ `variable_operating_cost_function`
90
+ - ``balance_of_system_cost``: plant balance of system
91
+ cost as evaluated by `balance_of_system_cost_function`
92
+ - ``self.wind_plant``: the SAM wind plant object,
93
+ through which all SAM variables can be accessed
81
94
 
82
95
  capital_cost_function : str
83
- The plant capital cost function as a string, must return the total
84
- capital cost in $. Has access to the same variables as the
85
- objective_function.
96
+ The plant capital cost function as a string, must return the
97
+ total capital cost in $. Has access to the same variables as
98
+ the objective_function.
86
99
  fixed_operating_cost_function : str
87
- The plant annual fixed operating cost function as a string, must
88
- return the fixed operating cost in $/year. Has access to the same
89
- variables as the objective_function.
100
+ The plant annual fixed operating cost function as a string,
101
+ must return the fixed operating cost in $/year. Has access
102
+ to the same variables as the objective_function.
90
103
  variable_operating_cost_function : str
91
- The plant annual variable operating cost function as a string, must
92
- return the variable operating cost in $/kWh. Has access to the same
93
- variables as the objective_function.
94
- exclusions : ExclusionMaskFromDict
95
- The exclusions that define where turbines can be placed. Contains
96
- exclusions.latitude, exclusions.longitude, and exclusions.mask
104
+ The plant annual variable operating cost function as a
105
+ string, must return the variable operating cost in $/kWh.
106
+ Has access to the same variables as the objective_function.
107
+ You can set this to "0" to effectively ignore variable
108
+ operating costs.
109
+ balance_of_system_cost_function : str
110
+ The plant balance-of-system cost function as a string, must
111
+ return the variable operating cost in $. Has access to the
112
+ same variables as the objective_function. You can set this
113
+ to "0" to effectively ignore balance-of-system costs.
114
+ include_mask : np.ndarray
115
+ Supply curve point 2D inclusion mask where included pixels
116
+ are set to 1 and excluded pixels are set to 0.
117
+ pixel_side_length : int
118
+ Side length (m) of a single pixel of the `include_mask`.
97
119
  min_spacing : float
98
120
  The minimum spacing between turbines (in meters).
99
121
  wake_loss_multiplier : float, optional
@@ -111,6 +133,7 @@ class PlaceTurbines:
111
133
  self.fixed_operating_cost_function = fixed_operating_cost_function
112
134
  self.variable_operating_cost_function = \
113
135
  variable_operating_cost_function
136
+ self.balance_of_system_cost_function = balance_of_system_cost_function
114
137
 
115
138
  self.objective_function = objective_function
116
139
  self.include_mask = include_mask
@@ -129,12 +152,14 @@ class PlaceTurbines:
129
152
  self.packing_polygons = None
130
153
  self.optimized_design_variables = None
131
154
  self.safe_polygons = None
155
+ self._optimized_nn_conn_dist_m = None
132
156
 
133
157
  self.ILLEGAL = ('import ', 'os.', 'sys.', '.__', '__.', 'eval', 'exec')
134
158
  self._preflight(self.objective_function)
135
159
  self._preflight(self.capital_cost_function)
136
160
  self._preflight(self.fixed_operating_cost_function)
137
161
  self._preflight(self.variable_operating_cost_function)
162
+ self._preflight(self.balance_of_system_cost_function)
138
163
 
139
164
  def _preflight(self, eqn):
140
165
  """Run preflight checks on the equation string."""
@@ -148,7 +173,7 @@ class PlaceTurbines:
148
173
  """From the exclusions data, create a shapely MultiPolygon as
149
174
  self.safe_polygons that defines where turbines can be placed.
150
175
  """
151
- nx, ny = np.shape(self.include_mask)
176
+ ny, nx = np.shape(self.include_mask)
152
177
  self.safe_polygons = MultiPolygon()
153
178
  side_x = np.arange(nx + 1) * self.pixel_side_length
154
179
  side_y = np.arange(ny, -1, -1) * self.pixel_side_length
@@ -190,7 +215,7 @@ class PlaceTurbines:
190
215
  self.packing_polygons = MultiPolygon([])
191
216
 
192
217
  def initialize_packing(self):
193
- """run the turbine packing algorithm (maximizing plant capacity) to
218
+ """Run the turbine packing algorithm (maximizing plant capacity) to
194
219
  define potential turbine locations that will be used as design
195
220
  variables in the gentic algorithm.
196
221
  """
@@ -211,6 +236,23 @@ class PlaceTurbines:
211
236
  self.x_locations = packing.turbine_x
212
237
  self.y_locations = packing.turbine_y
213
238
 
239
+ def _sc_center(self):
240
+ """Supply curve point center. """
241
+ ny, nx = np.shape(self.include_mask)
242
+ cx = nx * self.pixel_side_length / 2
243
+ cy = ny * self.pixel_side_length / 2
244
+ return cx, cy
245
+
246
+ def _avg_sl_dist_to_cent(self, x_locs, y_locs):
247
+ """Average straight-line distance to center from turb locations. """
248
+ cx, cy = self._sc_center()
249
+ return np.hypot(x_locs - cx, y_locs - cy).mean()
250
+
251
+ def _avg_sl_dist_to_med(self, x_locs, y_locs):
252
+ """Average straight-line distance to turbine medoid. """
253
+ cx, cy = _turb_medoid(x_locs, y_locs)
254
+ return np.hypot(x_locs - cx, y_locs - cy).mean()
255
+
214
256
  # pylint: disable=W0641,W0123
215
257
  def optimization_objective(self, x):
216
258
  """The optimization objective used in the bespoke optimization
@@ -218,8 +260,9 @@ class PlaceTurbines:
218
260
  x = [bool(y) for y in x]
219
261
  if len(x) > 0:
220
262
  n_turbines = np.sum(x)
221
- self.wind_plant["wind_farm_xCoordinates"] = self.x_locations[x]
222
- self.wind_plant["wind_farm_yCoordinates"] = self.y_locations[x]
263
+ x_locs, y_locs = self.x_locations[x], self.y_locations[x]
264
+ self.wind_plant["wind_farm_xCoordinates"] = x_locs
265
+ self.wind_plant["wind_farm_yCoordinates"] = y_locs
223
266
 
224
267
  system_capacity = n_turbines * self.turbine_capacity
225
268
  self.wind_plant["system_capacity"] = system_capacity
@@ -227,8 +270,14 @@ class PlaceTurbines:
227
270
  self.wind_plant.assign_inputs()
228
271
  self.wind_plant.execute()
229
272
  aep = self._aep_after_scaled_wake_losses()
273
+ avg_sl_dist_to_center_m = self._avg_sl_dist_to_cent(x_locs, y_locs)
274
+ avg_sl_dist_to_medoid_m = self._avg_sl_dist_to_med(x_locs, y_locs)
275
+ if "nn_conn_dist_m" in self.balance_of_system_cost_function:
276
+ nn_conn_dist_m = _compute_nn_conn_dist(x_locs, y_locs)
230
277
  else:
231
278
  n_turbines = system_capacity = aep = 0
279
+ avg_sl_dist_to_center_m = avg_sl_dist_to_medoid_m = 0
280
+ nn_conn_dist_m = 0
232
281
 
233
282
  fixed_charge_rate = self.fixed_charge_rate
234
283
  capital_cost = eval(self.capital_cost_function,
@@ -237,13 +286,16 @@ class PlaceTurbines:
237
286
  globals(), locals())
238
287
  variable_operating_cost = eval(self.variable_operating_cost_function,
239
288
  globals(), locals())
240
-
289
+ balance_of_system_cost = eval(self.balance_of_system_cost_function,
290
+ globals(), locals())
241
291
  capital_cost *= self.wind_plant.sam_sys_inputs.get(
242
292
  'capital_cost_multiplier', 1)
243
293
  fixed_operating_cost *= self.wind_plant.sam_sys_inputs.get(
244
294
  'fixed_operating_cost_multiplier', 1)
245
295
  variable_operating_cost *= self.wind_plant.sam_sys_inputs.get(
246
296
  'variable_operating_cost_multiplier', 1)
297
+ balance_of_system_cost *= self.wind_plant.sam_sys_inputs.get(
298
+ 'balance_of_system_cost_multiplier', 1)
247
299
 
248
300
  objective = eval(self.objective_function, globals(), locals())
249
301
 
@@ -354,6 +406,7 @@ class PlaceTurbines:
354
406
  """
355
407
 
356
408
  fixed_charge_rate = self.fixed_charge_rate
409
+ avg_sl_dist_to_center_m = self.avg_sl_dist_to_center_m
357
410
  n_turbines = int(round(capacity_mw * 1e3 / self.turbine_capacity))
358
411
  system_capacity = n_turbines * self.turbine_capacity
359
412
  mult = self.wind_plant.sam_sys_inputs.get(
@@ -364,7 +417,7 @@ class PlaceTurbines:
364
417
  def fixed_charge_rate(self):
365
418
  """Fixed charge rate if input to the SAM WindPowerPD object, None if
366
419
  not found in inputs."""
367
- return self.wind_plant.sam_sys_inputs.get('fixed_charge_rate', None)
420
+ return self.wind_plant.sam_sys_inputs.get("fixed_charge_rate", None)
368
421
 
369
422
  @property
370
423
  @none_until_optimized
@@ -378,6 +431,28 @@ class PlaceTurbines:
378
431
  """This is the final optimized turbine y locations (m)"""
379
432
  return self.y_locations[self.optimized_design_variables]
380
433
 
434
+ @property
435
+ @none_until_optimized
436
+ def avg_sl_dist_to_center_m(self):
437
+ """This is the final avg straight line distance to center (m)"""
438
+ return self._avg_sl_dist_to_cent(self.turbine_x, self.turbine_y)
439
+
440
+ @property
441
+ @none_until_optimized
442
+ def avg_sl_dist_to_medoid_m(self):
443
+ """This is the final avg straight line distance to turb medoid (m)"""
444
+ return self._avg_sl_dist_to_med(self.turbine_x, self.turbine_y)
445
+
446
+ @property
447
+ @none_until_optimized
448
+ def nn_conn_dist_m(self):
449
+ """This is the final avg straight line distance to turb medoid (m)"""
450
+ if self._optimized_nn_conn_dist_m is None:
451
+ self._optimized_nn_conn_dist_m = _compute_nn_conn_dist(
452
+ self.turbine_x, self.turbine_y
453
+ )
454
+ return self._optimized_nn_conn_dist_m
455
+
381
456
  @property
382
457
  @none_until_optimized
383
458
  def nturbs(self):
@@ -395,7 +470,8 @@ class PlaceTurbines:
395
470
  def convex_hull(self):
396
471
  """This is the convex hull of the turbine locations"""
397
472
  turbines = MultiPoint([Point(x, y)
398
- for x,y in zip(self.turbine_x, self.turbine_y)])
473
+ for x, y in zip(self.turbine_x,
474
+ self.turbine_y)])
399
475
  return turbines.convex_hull
400
476
 
401
477
  @property
@@ -476,6 +552,10 @@ class PlaceTurbines:
476
552
  n_turbines = self.nturbs
477
553
  system_capacity = self.capacity
478
554
  aep = self.aep
555
+ avg_sl_dist_to_center_m = self.avg_sl_dist_to_center_m
556
+ avg_sl_dist_to_medoid_m = self.avg_sl_dist_to_medoid_m
557
+ nn_conn_dist_m = self.nn_conn_dist_m
558
+
479
559
  mult = self.wind_plant.sam_sys_inputs.get(
480
560
  'capital_cost_multiplier', 1)
481
561
  return eval(self.capital_cost_function, globals(), locals()) * mult
@@ -490,6 +570,10 @@ class PlaceTurbines:
490
570
  n_turbines = self.nturbs
491
571
  system_capacity = self.capacity
492
572
  aep = self.aep
573
+ avg_sl_dist_to_center_m = self.avg_sl_dist_to_center_m
574
+ avg_sl_dist_to_medoid_m = self.avg_sl_dist_to_medoid_m
575
+ nn_conn_dist_m = self.nn_conn_dist_m
576
+
493
577
  mult = self.wind_plant.sam_sys_inputs.get(
494
578
  'fixed_operating_cost_multiplier', 1)
495
579
  return eval(self.fixed_operating_cost_function,
@@ -505,11 +589,32 @@ class PlaceTurbines:
505
589
  n_turbines = self.nturbs
506
590
  system_capacity = self.capacity
507
591
  aep = self.aep
592
+ avg_sl_dist_to_center_m = self.avg_sl_dist_to_center_m
593
+ avg_sl_dist_to_medoid_m = self.avg_sl_dist_to_medoid_m
594
+ nn_conn_dist_m = self.nn_conn_dist_m
595
+
508
596
  mult = self.wind_plant.sam_sys_inputs.get(
509
597
  'variable_operating_cost_multiplier', 1)
510
598
  return eval(self.variable_operating_cost_function,
511
599
  globals(), locals()) * mult
512
600
 
601
+ @property
602
+ @none_until_optimized
603
+ def balance_of_system_cost(self):
604
+ """This is the balance of system cost of the optimized plant ($)"""
605
+ fixed_charge_rate = self.fixed_charge_rate
606
+ n_turbines = self.nturbs
607
+ system_capacity = self.capacity
608
+ aep = self.aep
609
+ avg_sl_dist_to_center_m = self.avg_sl_dist_to_center_m
610
+ avg_sl_dist_to_medoid_m = self.avg_sl_dist_to_medoid_m
611
+ nn_conn_dist_m = self.nn_conn_dist_m
612
+
613
+ mult = self.wind_plant.sam_sys_inputs.get(
614
+ 'balance_of_system_cost_multiplier', 1)
615
+ return eval(self.balance_of_system_cost_function,
616
+ globals(), locals()) * mult
617
+
513
618
  # pylint: disable=W0641,W0123
514
619
  @property
515
620
  @none_until_optimized
@@ -522,4 +627,43 @@ class PlaceTurbines:
522
627
  capital_cost = self.capital_cost
523
628
  fixed_operating_cost = self.fixed_operating_cost
524
629
  variable_operating_cost = self.variable_operating_cost
630
+ balance_of_system_cost = self.balance_of_system_cost
631
+ avg_sl_dist_to_center_m = self.avg_sl_dist_to_center_m
632
+ avg_sl_dist_to_medoid_m = self.avg_sl_dist_to_medoid_m
633
+ nn_conn_dist_m = self.nn_conn_dist_m
634
+
525
635
  return eval(self.objective_function, globals(), locals())
636
+
637
+
638
+ def _turb_medoid(x_locs, y_locs):
639
+ """Turbine medoid. """
640
+ return np.median(x_locs), np.median(y_locs)
641
+
642
+
643
+ def _compute_nn_conn_dist(x_coords, y_coords):
644
+ """Connect turbines using a greedy nearest-neighbor approach. """
645
+ if len(x_coords) <= 1:
646
+ return 0
647
+
648
+ coordinates = np.c_[x_coords, y_coords]
649
+ allowed_conns = np.r_[coordinates.mean(axis=0)[None], coordinates]
650
+
651
+ mask = np.zeros_like(allowed_conns)
652
+ mask[0] = 1
653
+ left_to_connect = np.ma.array(allowed_conns, mask=mask)
654
+
655
+ mask = np.ones_like(allowed_conns)
656
+ mask[0] = 0
657
+ allowed_conns = np.ma.array(allowed_conns, mask=mask)
658
+
659
+ total_dist = 0
660
+ for __ in range(len(coordinates)):
661
+ dists = left_to_connect[:, :, None] - allowed_conns.T[None]
662
+ dists = np.hypot(dists[:, 0], dists[:, 1])
663
+ min_dists = dists.min(axis=-1)
664
+ total_dist += min_dists.min()
665
+ next_connection = min_dists.argmin()
666
+ allowed_conns.mask[next_connection] = 0
667
+ left_to_connect.mask[next_connection] = 1
668
+
669
+ return total_dist
@@ -37,7 +37,7 @@ class OutputRequest(list):
37
37
  for request in inp:
38
38
  if request in self.CORRECTIONS.values():
39
39
  self.append(request)
40
- elif request in self.CORRECTIONS.keys():
40
+ elif request in self.CORRECTIONS:
41
41
  self.append(self.CORRECTIONS[request])
42
42
  msg = ('Correcting output request "{}" to "{}".'
43
43
  .format(request, self.CORRECTIONS[request]))
@@ -61,6 +61,7 @@ class SAMOutputRequest(OutputRequest):
61
61
  # all available SAM output variables should be in the values
62
62
  CORRECTIONS = {'cf_means': 'cf_mean',
63
63
  'cf': 'cf_mean',
64
+ 'capacity': 'system_capacity',
64
65
  'capacity_factor': 'cf_mean',
65
66
  'capacityfactor': 'cf_mean',
66
67
  'cf_profiles': 'cf_profile',