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.
- {NREL_reV-0.8.9.dist-info → NREL_reV-0.9.2.dist-info}/METADATA +2 -1
- {NREL_reV-0.8.9.dist-info → NREL_reV-0.9.2.dist-info}/RECORD +34 -34
- reV/SAM/SAM.py +38 -0
- reV/SAM/generation.py +43 -10
- reV/bespoke/bespoke.py +304 -245
- reV/bespoke/cli_bespoke.py +2 -0
- reV/bespoke/place_turbines.py +181 -37
- reV/config/output_request.py +2 -1
- reV/config/project_points.py +1 -3
- reV/econ/econ.py +24 -13
- reV/econ/economies_of_scale.py +54 -35
- reV/generation/base.py +22 -2
- reV/generation/generation.py +50 -23
- reV/generation/output_attributes/lcoe_fcr_inputs.json +38 -3
- reV/handlers/__init__.py +0 -1
- reV/handlers/multi_year.py +45 -17
- reV/handlers/transmission.py +44 -27
- reV/hybrids/hybrid_methods.py +16 -14
- reV/hybrids/hybrids.py +10 -10
- reV/nrwal/nrwal.py +1 -1
- reV/qa_qc/qa_qc.py +1 -1
- reV/qa_qc/summary.py +4 -4
- reV/rep_profiles/rep_profiles.py +1 -1
- reV/supply_curve/exclusions.py +1 -1
- reV/supply_curve/extent.py +1 -1
- reV/supply_curve/points.py +254 -131
- reV/supply_curve/sc_aggregation.py +13 -45
- reV/supply_curve/supply_curve.py +200 -141
- reV/utilities/__init__.py +114 -39
- reV/version.py +1 -1
- {NREL_reV-0.8.9.dist-info → NREL_reV-0.9.2.dist-info}/LICENSE +0 -0
- {NREL_reV-0.8.9.dist-info → NREL_reV-0.9.2.dist-info}/WHEEL +0 -0
- {NREL_reV-0.8.9.dist-info → NREL_reV-0.9.2.dist-info}/entry_points.txt +0 -0
- {NREL_reV-0.8.9.dist-info → NREL_reV-0.9.2.dist-info}/top_level.txt +0 -0
reV/bespoke/cli_bespoke.py
CHANGED
@@ -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: "{}"'
|
reV/bespoke/place_turbines.py
CHANGED
@@ -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
|
58
|
-
should have everything in the plant defined, such
|
59
|
-
turbine coordinates and plant capacity need to
|
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,
|
63
|
-
return the objective to be minimized during layout
|
64
|
-
Variables available are:
|
65
|
-
|
66
|
-
- n_turbines
|
67
|
-
- system_capacity
|
68
|
-
- aep
|
69
|
-
-
|
70
|
-
|
71
|
-
|
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
|
74
|
-
evaluated by `fixed_operating_cost_function`
|
75
|
-
- variable_operating_cost
|
76
|
-
as evaluated by
|
77
|
-
|
78
|
-
|
79
|
-
|
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
|
83
|
-
capital cost in $. Has access to the same variables as
|
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,
|
87
|
-
return the fixed operating cost in $/year. Has access
|
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
|
91
|
-
return the variable operating cost in $/kWh.
|
92
|
-
variables as the objective_function.
|
93
|
-
|
94
|
-
|
95
|
-
|
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
|
-
|
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.
|
221
|
-
self.wind_plant["
|
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
|
reV/config/output_request.py
CHANGED
@@ -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
|
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',
|
reV/config/project_points.py
CHANGED
@@ -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 =
|
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
|
79
|
+
specified. Typically, the CSV contains the following
|
80
|
+
columns:
|
80
81
|
|
81
|
-
- ``gid``: Integer specifying the GID of each
|
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
|
-
|
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 ``
|
92
|
-
site-specific
|
93
|
-
that do not correspond to a config
|
94
|
-
but they will be ignored. A
|
95
|
-
guidelines as the CSV input
|
96
|
-
|
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 `
|
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 =
|
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:
|
reV/econ/economies_of_scale.py
CHANGED
@@ -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
|
-
|
203
|
-
SupplyCurveField.
|
204
|
-
|
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
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
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
|
-
|
262
|
-
SupplyCurveField.
|
263
|
-
|
264
|
-
|
265
|
-
|
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
|
-
|
279
|
-
SupplyCurveField.
|
280
|
-
|
281
|
-
|
282
|
-
|
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 `
|
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.
|