NREL-reV 0.8.9__py3-none-any.whl → 0.9.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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: "{}"'
@@ -48,51 +48,74 @@ class PlaceTurbines:
48
48
  capital_cost_function,
49
49
  fixed_operating_cost_function,
50
50
  variable_operating_cost_function,
51
+ balance_of_system_cost_function,
51
52
  include_mask, pixel_side_length, min_spacing,
52
53
  wake_loss_multiplier=1):
53
54
  """
54
55
  Parameters
55
56
  ----------
56
57
  wind_plant : WindPowerPD
57
- wind plant object to analyze wind plant performance. This object
58
- should have everything in the plant defined, such that only the
59
- turbine coordinates and plant capacity need to be defined during
60
- 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.
61
62
  objective_function : str
62
- The objective function of the optimization as a string, should
63
- return the objective to be minimized during layout optimization.
64
- Variables available are:
65
-
66
- - n_turbines: the number of turbines
67
- - system_capacity: wind plant capacity
68
- - aep: annual energy production
69
- - fixed_charge_rate: user input fixed_charge_rate if included
70
- as part of the sam system config.
71
- - 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
72
84
  by `capital_cost_function`
73
- - fixed_operating_cost: plant fixed annual operating cost as
74
- evaluated by `fixed_operating_cost_function`
75
- - variable_operating_cost: plant variable annual operating cost
76
- as evaluated by `variable_operating_cost_function`
77
- - self.wind_plant: the SAM wind plant object, through which
78
- all SAM variables can be accessed
79
- - 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
80
94
 
81
95
  capital_cost_function : str
82
- The plant capital cost function as a string, must return the total
83
- capital cost in $. Has access to the same variables as the
84
- 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.
85
99
  fixed_operating_cost_function : str
86
- The plant annual fixed operating cost function as a string, must
87
- return the fixed operating cost in $/year. Has access to the same
88
- 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.
89
103
  variable_operating_cost_function : str
90
- The plant annual variable operating cost function as a string, must
91
- return the variable operating cost in $/kWh. Has access to the same
92
- variables as the objective_function.
93
- exclusions : ExclusionMaskFromDict
94
- The exclusions that define where turbines can be placed. Contains
95
- 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`.
96
119
  min_spacing : float
97
120
  The minimum spacing between turbines (in meters).
98
121
  wake_loss_multiplier : float, optional
@@ -110,6 +133,7 @@ class PlaceTurbines:
110
133
  self.fixed_operating_cost_function = fixed_operating_cost_function
111
134
  self.variable_operating_cost_function = \
112
135
  variable_operating_cost_function
136
+ self.balance_of_system_cost_function = balance_of_system_cost_function
113
137
 
114
138
  self.objective_function = objective_function
115
139
  self.include_mask = include_mask
@@ -128,12 +152,14 @@ class PlaceTurbines:
128
152
  self.packing_polygons = None
129
153
  self.optimized_design_variables = None
130
154
  self.safe_polygons = None
155
+ self._optimized_nn_conn_dist_m = None
131
156
 
132
157
  self.ILLEGAL = ('import ', 'os.', 'sys.', '.__', '__.', 'eval', 'exec')
133
158
  self._preflight(self.objective_function)
134
159
  self._preflight(self.capital_cost_function)
135
160
  self._preflight(self.fixed_operating_cost_function)
136
161
  self._preflight(self.variable_operating_cost_function)
162
+ self._preflight(self.balance_of_system_cost_function)
137
163
 
138
164
  def _preflight(self, eqn):
139
165
  """Run preflight checks on the equation string."""
@@ -147,7 +173,7 @@ class PlaceTurbines:
147
173
  """From the exclusions data, create a shapely MultiPolygon as
148
174
  self.safe_polygons that defines where turbines can be placed.
149
175
  """
150
- nx, ny = np.shape(self.include_mask)
176
+ ny, nx = np.shape(self.include_mask)
151
177
  self.safe_polygons = MultiPolygon()
152
178
  side_x = np.arange(nx + 1) * self.pixel_side_length
153
179
  side_y = np.arange(ny, -1, -1) * self.pixel_side_length
@@ -210,6 +236,23 @@ class PlaceTurbines:
210
236
  self.x_locations = packing.turbine_x
211
237
  self.y_locations = packing.turbine_y
212
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
+
213
256
  # pylint: disable=W0641,W0123
214
257
  def optimization_objective(self, x):
215
258
  """The optimization objective used in the bespoke optimization
@@ -217,8 +260,9 @@ class PlaceTurbines:
217
260
  x = [bool(y) for y in x]
218
261
  if len(x) > 0:
219
262
  n_turbines = np.sum(x)
220
- self.wind_plant["wind_farm_xCoordinates"] = self.x_locations[x]
221
- 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
222
266
 
223
267
  system_capacity = n_turbines * self.turbine_capacity
224
268
  self.wind_plant["system_capacity"] = system_capacity
@@ -226,8 +270,14 @@ class PlaceTurbines:
226
270
  self.wind_plant.assign_inputs()
227
271
  self.wind_plant.execute()
228
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)
229
277
  else:
230
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
231
281
 
232
282
  fixed_charge_rate = self.fixed_charge_rate
233
283
  capital_cost = eval(self.capital_cost_function,
@@ -236,13 +286,16 @@ class PlaceTurbines:
236
286
  globals(), locals())
237
287
  variable_operating_cost = eval(self.variable_operating_cost_function,
238
288
  globals(), locals())
239
-
289
+ balance_of_system_cost = eval(self.balance_of_system_cost_function,
290
+ globals(), locals())
240
291
  capital_cost *= self.wind_plant.sam_sys_inputs.get(
241
292
  'capital_cost_multiplier', 1)
242
293
  fixed_operating_cost *= self.wind_plant.sam_sys_inputs.get(
243
294
  'fixed_operating_cost_multiplier', 1)
244
295
  variable_operating_cost *= self.wind_plant.sam_sys_inputs.get(
245
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)
246
299
 
247
300
  objective = eval(self.objective_function, globals(), locals())
248
301
 
@@ -353,6 +406,7 @@ class PlaceTurbines:
353
406
  """
354
407
 
355
408
  fixed_charge_rate = self.fixed_charge_rate
409
+ avg_sl_dist_to_center_m = self.avg_sl_dist_to_center_m
356
410
  n_turbines = int(round(capacity_mw * 1e3 / self.turbine_capacity))
357
411
  system_capacity = n_turbines * self.turbine_capacity
358
412
  mult = self.wind_plant.sam_sys_inputs.get(
@@ -377,6 +431,28 @@ class PlaceTurbines:
377
431
  """This is the final optimized turbine y locations (m)"""
378
432
  return self.y_locations[self.optimized_design_variables]
379
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
+
380
456
  @property
381
457
  @none_until_optimized
382
458
  def nturbs(self):
@@ -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',
@@ -623,9 +623,7 @@ class ProjectPoints:
623
623
 
624
624
  # pylint: disable=no-member
625
625
  if SiteDataField.CONFIG not in df.columns:
626
- df = cls._parse_sites(
627
- df[SiteDataField.GID].values, res_file=res_file
628
- )
626
+ df[SiteDataField.CONFIG] = None
629
627
 
630
628
  gids = df[SiteDataField.GID].values
631
629
  if not np.array_equal(np.sort(gids), gids):
reV/econ/econ.py CHANGED
@@ -76,25 +76,36 @@ class Econ(BaseGen):
76
76
  (or slice) representing the GIDs of multiple sites can be
77
77
  specified to evaluate reV at multiple specific locations.
78
78
  A string pointing to a project points CSV file may also be
79
- specified. Typically, the CSV contains two columns:
79
+ specified. Typically, the CSV contains the following
80
+ columns:
80
81
 
81
- - ``gid``: Integer specifying the GID of each site.
82
+ - ``gid``: Integer specifying the generation GID of each
83
+ site.
82
84
  - ``config``: Key in the `sam_files` input dictionary
83
85
  (see below) corresponding to the SAM configuration to
84
86
  use for each particular site. This value can also be
85
87
  ``None`` (or left out completely) if you specify only
86
88
  a single SAM configuration file as the `sam_files`
87
89
  input.
88
-
89
- The CSV file may also contain site-specific inputs by
90
+ - ``capital_cost_multiplier``: This is an *optional*
91
+ multiplier input that, if included, will be used to
92
+ regionally scale the ``capital_cost`` input in the SAM
93
+ config. If you include this column in your CSV, you
94
+ *do not* need to specify ``capital_cost``, unless you
95
+ would like that value to vary regionally and
96
+ independently of the multiplier (i.e. the multiplier
97
+ will still be applied on top of the ``capital_cost``
98
+ input).
99
+
100
+ The CSV file may also contain other site-specific inputs by
90
101
  including a column named after a config keyword (e.g. a
91
- column called ``capital_cost`` may be included to specify a
92
- site-specific capital cost value for each location). Columns
93
- that do not correspond to a config key may also be included,
94
- but they will be ignored. A DataFrame following the same
95
- guidelines as the CSV input (or a dictionary that can be
96
- used to initialize such a DataFrame) may be used for this
97
- input as well.
102
+ column called ``wind_turbine_rotor_diameter`` may be
103
+ included to specify a site-specific turbine diameter for
104
+ each location). Columns that do not correspond to a config
105
+ key may also be included, but they will be ignored. A
106
+ DataFrame following the same guidelines as the CSV input
107
+ (or a dictionary that can be used to initialize such a
108
+ DataFrame) may be used for this input as well.
98
109
  sam_files : dict | str
99
110
  A dictionary mapping SAM input configuration ID(s) to SAM
100
111
  configuration(s). Keys are the SAM config ID(s) which
@@ -352,7 +363,7 @@ class Econ(BaseGen):
352
363
  pc : reV.config.project_points.PointsControl
353
364
  Iterable points control object from reV config module.
354
365
  Must have project_points with df property with all relevant
355
- site-specific inputs and a `SupplyCurveField.GID` column.
366
+ site-specific inputs and a `SiteDataField.GID` column.
356
367
  By passing site-specific inputs in this dataframe, which
357
368
  was split using points_control, only the data relevant to
358
369
  the current sites is passed.
@@ -405,7 +416,7 @@ class Econ(BaseGen):
405
416
  Output variables requested from SAM.
406
417
  """
407
418
 
408
- output_request = self._output_request_type_check(req)
419
+ output_request = super()._parse_output_request(req)
409
420
 
410
421
  for request in output_request:
411
422
  if request not in self.OUT_ATTRS:
@@ -113,7 +113,8 @@ class EconomiesOfScale:
113
113
  """
114
114
  var_names = []
115
115
  if self._eqn is not None:
116
- delimiters = ("*", "/", "+", "-", " ", "(", ")", "[", "]", ",")
116
+ delimiters = (">", "<", ">=", "<=", "==", ",", "*", "/", "+", "-",
117
+ " ", "(", ")", "[", "]")
117
118
  regex_pattern = "|".join(map(re.escape, delimiters))
118
119
  var_names = []
119
120
  for sub in re.split(regex_pattern, str(self._eqn)):
@@ -190,6 +191,29 @@ class EconomiesOfScale:
190
191
  """
191
192
  return self._evaluate()
192
193
 
194
+ def _cost_from_cap(self, col_name):
195
+ """Get full cost value from cost per mw in data.
196
+
197
+ Parameters
198
+ ----------
199
+ col_name : str
200
+ Name of column containing the cost per mw value.
201
+
202
+ Returns
203
+ -------
204
+ float | None
205
+ Cost value if it was found in data, ``None`` otherwise.
206
+ """
207
+ cap = self._data.get(SupplyCurveField.CAPACITY_AC_MW)
208
+ if cap is None:
209
+ return None
210
+
211
+ cost_per_mw = self._data.get(col_name)
212
+ if cost_per_mw is None:
213
+ return None
214
+
215
+ return cap * cost_per_mw
216
+
193
217
  @property
194
218
  def raw_capital_cost(self):
195
219
  """Unscaled (raw) capital cost found in the data input arg.
@@ -199,10 +223,13 @@ class EconomiesOfScale:
199
223
  out : float | np.ndarray
200
224
  Unscaled (raw) capital_cost found in the data input arg.
201
225
  """
202
- key_list = [
203
- SupplyCurveField.CAPITAL_COST,
204
- "mean_capital_cost",
205
- ]
226
+ raw_capital_cost_from_cap = self._cost_from_cap(
227
+ SupplyCurveField.COST_SITE_OCC_USD_PER_AC_MW
228
+ )
229
+ if raw_capital_cost_from_cap is not None:
230
+ return raw_capital_cost_from_cap
231
+
232
+ key_list = ["capital_cost", "mean_capital_cost"]
206
233
  return self._get_prioritized_keys(self._data, key_list)
207
234
 
208
235
  @property
@@ -220,18 +247,6 @@ class EconomiesOfScale:
220
247
  cc *= self.capital_cost_scalar
221
248
  return cc
222
249
 
223
- @property
224
- def system_capacity(self):
225
- """Get the system capacity in kW (SAM input, not the reV supply
226
- curve capacity).
227
-
228
- Returns
229
- -------
230
- out : float | np.ndarray
231
- """
232
- key_list = ["system_capacity", "mean_system_capacity"]
233
- return self._get_prioritized_keys(self._data, key_list)
234
-
235
250
  @property
236
251
  def fcr(self):
237
252
  """Fixed charge rate from input data arg
@@ -241,12 +256,12 @@ class EconomiesOfScale:
241
256
  out : float | np.ndarray
242
257
  Fixed charge rate from input data arg
243
258
  """
244
- key_list = [
245
- SupplyCurveField.FIXED_CHARGE_RATE,
246
- "mean_fixed_charge_rate",
247
- "fcr",
248
- "mean_fcr",
249
- ]
259
+ fcr = self._data.get(SupplyCurveField.FIXED_CHARGE_RATE)
260
+ if fcr is not None and fcr > 0:
261
+ return fcr
262
+
263
+ key_list = ["fixed_charge_rate", "mean_fixed_charge_rate", "fcr",
264
+ "mean_fcr"]
250
265
  return self._get_prioritized_keys(self._data, key_list)
251
266
 
252
267
  @property
@@ -258,12 +273,14 @@ class EconomiesOfScale:
258
273
  out : float | np.ndarray
259
274
  Fixed operating cost from input data arg
260
275
  """
261
- key_list = [
262
- SupplyCurveField.FIXED_OPERATING_COST,
263
- "mean_fixed_operating_cost",
264
- "foc",
265
- "mean_foc",
266
- ]
276
+ foc_from_cap = self._cost_from_cap(
277
+ SupplyCurveField.COST_SITE_FOC_USD_PER_AC_MW
278
+ )
279
+ if foc_from_cap is not None:
280
+ return foc_from_cap
281
+
282
+ key_list = ["fixed_operating_cost", "mean_fixed_operating_cost",
283
+ "foc", "mean_foc"]
267
284
  return self._get_prioritized_keys(self._data, key_list)
268
285
 
269
286
  @property
@@ -275,12 +292,14 @@ class EconomiesOfScale:
275
292
  out : float | np.ndarray
276
293
  Variable operating cost from input data arg
277
294
  """
278
- key_list = [
279
- SupplyCurveField.VARIABLE_OPERATING_COST,
280
- "mean_variable_operating_cost",
281
- "voc",
282
- "mean_voc",
283
- ]
295
+ voc_from_cap = self._cost_from_cap(
296
+ SupplyCurveField.COST_SITE_VOC_USD_PER_AC_MW
297
+ )
298
+ if voc_from_cap is not None:
299
+ return voc_from_cap
300
+
301
+ key_list = ["variable_operating_cost", "mean_variable_operating_cost",
302
+ "voc", "mean_voc"]
284
303
  return self._get_prioritized_keys(self._data, key_list)
285
304
 
286
305
  @property
reV/generation/base.py CHANGED
@@ -45,6 +45,21 @@ with open(os.path.join(ATTR_DIR, 'windbos.json')) as f:
45
45
  with open(os.path.join(ATTR_DIR, 'lcoe_fcr_inputs.json')) as f:
46
46
  LCOE_IN_ATTRS = json.load(f)
47
47
 
48
+ LCOE_REQUIRED_OUTPUTS = ("system_capacity", "capital_cost_multiplier",
49
+ "capital_cost", "fixed_operating_cost",
50
+ "variable_operating_cost", "base_capital_cost",
51
+ "base_fixed_operating_cost",
52
+ "base_variable_operating_cost", "fixed_charge_rate")
53
+ """Required econ outputs in generation file."""
54
+
55
+
56
+ def _add_lcoe_outputs(output_request):
57
+ """Add required lcoe outputs to output request. """
58
+ for out_var in LCOE_REQUIRED_OUTPUTS:
59
+ if out_var not in output_request:
60
+ output_request.append(out_var)
61
+ return output_request
62
+
48
63
 
49
64
  class BaseGen(ABC):
50
65
  """Base class for reV gen and econ classes to run SAM simulations."""
@@ -302,7 +317,7 @@ class BaseGen(ABC):
302
317
  Meta data df for sites in project points. Column names are meta
303
318
  data variables, rows are different sites. The row index
304
319
  does not indicate the site number if the project points are
305
- non-sequential or do not start from 0, so a `SupplyCurveField.GID`
320
+ non-sequential or do not start from 0, so a `SiteDataField.GID`
306
321
  column is added.
307
322
  """
308
323
  return self._meta
@@ -859,7 +874,6 @@ class BaseGen(ABC):
859
874
  """
860
875
  self.project_points.join_df(site_data, key=self.site_data.index.name)
861
876
 
862
- @abstractmethod
863
877
  def _parse_output_request(self, req):
864
878
  """Set the output variables requested from the user.
865
879
 
@@ -873,6 +887,12 @@ class BaseGen(ABC):
873
887
  output_request : list
874
888
  Output variables requested from SAM.
875
889
  """
890
+ output_request = self._output_request_type_check(req)
891
+
892
+ if "lcoe_fcr" in output_request:
893
+ output_request = _add_lcoe_outputs(output_request)
894
+
895
+ return output_request
876
896
 
877
897
  def _get_data_shape(self, dset, n_sites):
878
898
  """Get the output array shape based on OUT_ATTRS or PySAM.Outputs.