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.
- {NREL_reV-0.8.7.dist-info → NREL_reV-0.9.0.dist-info}/METADATA +13 -10
- {NREL_reV-0.8.7.dist-info → NREL_reV-0.9.0.dist-info}/RECORD +43 -43
- {NREL_reV-0.8.7.dist-info → NREL_reV-0.9.0.dist-info}/WHEEL +1 -1
- reV/SAM/SAM.py +217 -133
- reV/SAM/econ.py +18 -14
- reV/SAM/generation.py +611 -422
- reV/SAM/windbos.py +93 -79
- reV/bespoke/bespoke.py +681 -377
- reV/bespoke/cli_bespoke.py +2 -0
- reV/bespoke/place_turbines.py +187 -43
- reV/config/output_request.py +2 -1
- reV/config/project_points.py +218 -140
- reV/econ/econ.py +166 -114
- reV/econ/economies_of_scale.py +91 -45
- reV/generation/base.py +331 -184
- reV/generation/generation.py +326 -200
- reV/generation/output_attributes/lcoe_fcr_inputs.json +38 -3
- reV/handlers/__init__.py +0 -1
- reV/handlers/exclusions.py +16 -15
- reV/handlers/multi_year.py +57 -26
- reV/handlers/outputs.py +6 -5
- reV/handlers/transmission.py +44 -27
- reV/hybrids/hybrid_methods.py +30 -30
- reV/hybrids/hybrids.py +305 -189
- reV/nrwal/nrwal.py +262 -168
- reV/qa_qc/cli_qa_qc.py +14 -10
- reV/qa_qc/qa_qc.py +217 -119
- reV/qa_qc/summary.py +228 -146
- reV/rep_profiles/rep_profiles.py +349 -230
- reV/supply_curve/aggregation.py +349 -188
- reV/supply_curve/competitive_wind_farms.py +90 -48
- reV/supply_curve/exclusions.py +138 -85
- reV/supply_curve/extent.py +75 -50
- reV/supply_curve/points.py +735 -390
- reV/supply_curve/sc_aggregation.py +357 -248
- reV/supply_curve/supply_curve.py +604 -347
- reV/supply_curve/tech_mapping.py +144 -82
- reV/utilities/__init__.py +274 -16
- reV/utilities/pytest_utils.py +8 -4
- reV/version.py +1 -1
- {NREL_reV-0.8.7.dist-info → NREL_reV-0.9.0.dist-info}/LICENSE +0 -0
- {NREL_reV-0.8.7.dist-info → NREL_reV-0.9.0.dist-info}/entry_points.txt +0 -0
- {NREL_reV-0.8.7.dist-info → NREL_reV-0.9.0.dist-info}/top_level.txt +0 -0
reV/econ/econ.py
CHANGED
@@ -1,26 +1,24 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
|
-
"""
|
3
|
-
reV econ module (lcoe-fcr, single owner, etc...)
|
4
|
-
"""
|
2
|
+
"""reV econ module (lcoe-fcr, single owner, etc...)"""
|
5
3
|
import logging
|
6
|
-
import numpy as np
|
7
4
|
import os
|
8
|
-
import pandas as pd
|
9
5
|
import pprint
|
10
6
|
from warnings import warn
|
11
7
|
|
8
|
+
import numpy as np
|
9
|
+
import pandas as pd
|
10
|
+
from rex.multi_file_resource import MultiFileResource
|
11
|
+
from rex.resource import Resource
|
12
|
+
from rex.utilities.utilities import check_res_file
|
13
|
+
|
12
14
|
from reV.config.project_points import PointsControl
|
13
15
|
from reV.generation.base import BaseGen
|
14
16
|
from reV.handlers.outputs import Outputs
|
15
17
|
from reV.SAM.econ import LCOE as SAM_LCOE
|
16
18
|
from reV.SAM.econ import SingleOwner
|
17
19
|
from reV.SAM.windbos import WindBos
|
18
|
-
from reV.utilities
|
19
|
-
from reV.utilities import
|
20
|
-
|
21
|
-
from rex.resource import Resource
|
22
|
-
from rex.multi_file_resource import MultiFileResource
|
23
|
-
from rex.utilities.utilities import check_res_file
|
20
|
+
from reV.utilities import ModuleName, ResourceMetaField
|
21
|
+
from reV.utilities.exceptions import ExecutionError, OffshoreWindInputWarning
|
24
22
|
|
25
23
|
logger = logging.getLogger(__name__)
|
26
24
|
|
@@ -29,22 +27,23 @@ class Econ(BaseGen):
|
|
29
27
|
"""Econ"""
|
30
28
|
|
31
29
|
# Mapping of reV econ output strings to SAM econ modules
|
32
|
-
OPTIONS = {
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
30
|
+
OPTIONS = {
|
31
|
+
"lcoe_fcr": SAM_LCOE,
|
32
|
+
"ppa_price": SingleOwner,
|
33
|
+
"project_return_aftertax_npv": SingleOwner,
|
34
|
+
"lcoe_real": SingleOwner,
|
35
|
+
"lcoe_nom": SingleOwner,
|
36
|
+
"flip_actual_irr": SingleOwner,
|
37
|
+
"gross_revenue": SingleOwner,
|
38
|
+
"total_installed_cost": WindBos,
|
39
|
+
"turbine_cost": WindBos,
|
40
|
+
"sales_tax_cost": WindBos,
|
41
|
+
"bos_cost": WindBos,
|
42
|
+
"fixed_charge_rate": SAM_LCOE,
|
43
|
+
"capital_cost": SAM_LCOE,
|
44
|
+
"fixed_operating_cost": SAM_LCOE,
|
45
|
+
"variable_operating_cost": SAM_LCOE,
|
46
|
+
}
|
48
47
|
"""Available ``reV`` econ `output_request` options"""
|
49
48
|
|
50
49
|
# Mapping of reV econ outputs to scale factors and units.
|
@@ -54,7 +53,7 @@ class Econ(BaseGen):
|
|
54
53
|
def __init__(self, project_points, sam_files, cf_file, site_data=None,
|
55
54
|
output_request=('lcoe_fcr',), sites_per_worker=100,
|
56
55
|
memory_utilization_limit=0.4, append=False):
|
57
|
-
"""
|
56
|
+
"""ReV econ analysis class.
|
58
57
|
|
59
58
|
``reV`` econ analysis runs SAM econ calculations, typically to
|
60
59
|
compute LCOE (using :py:class:`PySAM.Lcoefcr.Lcoefcr`), though
|
@@ -176,17 +175,26 @@ class Econ(BaseGen):
|
|
176
175
|
"""
|
177
176
|
|
178
177
|
# get a points control instance
|
179
|
-
pc = self.get_pc(
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
178
|
+
pc = self.get_pc(
|
179
|
+
points=project_points,
|
180
|
+
points_range=None,
|
181
|
+
sam_configs=sam_files,
|
182
|
+
cf_file=cf_file,
|
183
|
+
sites_per_worker=sites_per_worker,
|
184
|
+
append=append,
|
185
|
+
)
|
186
|
+
|
187
|
+
super().__init__(
|
188
|
+
pc,
|
189
|
+
output_request,
|
190
|
+
site_data=site_data,
|
191
|
+
memory_utilization_limit=memory_utilization_limit,
|
192
|
+
)
|
185
193
|
|
186
194
|
self._cf_file = cf_file
|
187
195
|
self._append = append
|
188
|
-
self._run_attrs[
|
189
|
-
self._run_attrs[
|
196
|
+
self._run_attrs["cf_file"] = cf_file
|
197
|
+
self._run_attrs["sam_module"] = self._sam_module.MODULE
|
190
198
|
|
191
199
|
@property
|
192
200
|
def cf_file(self):
|
@@ -212,19 +220,20 @@ class Econ(BaseGen):
|
|
212
220
|
with Outputs(self.cf_file) as cfh:
|
213
221
|
# only take meta that belongs to this project's site list
|
214
222
|
self._meta = cfh.meta[
|
215
|
-
cfh.meta[
|
223
|
+
cfh.meta[ResourceMetaField.GID].isin(
|
224
|
+
self.points_control.sites)]
|
216
225
|
|
217
|
-
if
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
logger.warning(w)
|
226
|
+
if ("offshore" in self._meta and self._meta["offshore"].sum() > 1):
|
227
|
+
w = ('Found offshore sites in econ meta data. '
|
228
|
+
'This functionality has been deprecated. '
|
229
|
+
'Please run the reV offshore module to '
|
230
|
+
'calculate offshore wind lcoe.')
|
231
|
+
warn(w, OffshoreWindInputWarning)
|
232
|
+
logger.warning(w)
|
225
233
|
|
226
234
|
elif self._meta is None and self.cf_file is None:
|
227
|
-
self._meta = pd.DataFrame(
|
235
|
+
self._meta = pd.DataFrame(
|
236
|
+
{ResourceMetaField.GID: self.points_control.sites})
|
228
237
|
|
229
238
|
return self._meta
|
230
239
|
|
@@ -233,7 +242,7 @@ class Econ(BaseGen):
|
|
233
242
|
"""Get the generation resource time index data."""
|
234
243
|
if self._time_index is None and self.cf_file is not None:
|
235
244
|
with Outputs(self.cf_file) as cfh:
|
236
|
-
if
|
245
|
+
if "time_index" in cfh.datasets:
|
237
246
|
self._time_index = cfh.time_index
|
238
247
|
|
239
248
|
return self._time_index
|
@@ -264,11 +273,11 @@ class Econ(BaseGen):
|
|
264
273
|
res_kwargs = {}
|
265
274
|
else:
|
266
275
|
res_cls = Resource
|
267
|
-
res_kwargs = {
|
276
|
+
res_kwargs = {"hsds": hsds}
|
268
277
|
|
269
278
|
with res_cls(cf_file, **res_kwargs) as f:
|
270
|
-
gid0 = f.meta[
|
271
|
-
gid1 = f.meta[
|
279
|
+
gid0 = f.meta[ResourceMetaField.GID].values[0]
|
280
|
+
gid1 = f.meta[ResourceMetaField.GID].values[-1]
|
272
281
|
|
273
282
|
i0 = pp.index(gid0)
|
274
283
|
i1 = pp.index(gid1) + 1
|
@@ -277,8 +286,15 @@ class Econ(BaseGen):
|
|
277
286
|
return pc
|
278
287
|
|
279
288
|
@classmethod
|
280
|
-
def get_pc(
|
281
|
-
|
289
|
+
def get_pc(
|
290
|
+
cls,
|
291
|
+
points,
|
292
|
+
points_range,
|
293
|
+
sam_configs,
|
294
|
+
cf_file,
|
295
|
+
sites_per_worker=None,
|
296
|
+
append=False,
|
297
|
+
):
|
282
298
|
"""
|
283
299
|
Get a PointsControl instance.
|
284
300
|
|
@@ -311,13 +327,19 @@ class Econ(BaseGen):
|
|
311
327
|
pc : reV.config.project_points.PointsControl
|
312
328
|
PointsControl object instance.
|
313
329
|
"""
|
314
|
-
pc = super().get_pc(
|
315
|
-
|
316
|
-
|
330
|
+
pc = super().get_pc(
|
331
|
+
points,
|
332
|
+
points_range,
|
333
|
+
sam_configs,
|
334
|
+
ModuleName.ECON,
|
335
|
+
sites_per_worker=sites_per_worker,
|
336
|
+
res_file=cf_file,
|
337
|
+
)
|
317
338
|
|
318
339
|
if append:
|
319
|
-
pc = cls._econ_append_pc(
|
320
|
-
|
340
|
+
pc = cls._econ_append_pc(
|
341
|
+
pc.project_points, cf_file, sites_per_worker=sites_per_worker
|
342
|
+
)
|
321
343
|
|
322
344
|
return pc
|
323
345
|
|
@@ -330,9 +352,10 @@ class Econ(BaseGen):
|
|
330
352
|
pc : reV.config.project_points.PointsControl
|
331
353
|
Iterable points control object from reV config module.
|
332
354
|
Must have project_points with df property with all relevant
|
333
|
-
site-specific inputs and a
|
334
|
-
inputs in this dataframe, which
|
335
|
-
only the data relevant to
|
355
|
+
site-specific inputs and a `SiteDataField.GID` column.
|
356
|
+
By passing site-specific inputs in this dataframe, which
|
357
|
+
was split using points_control, only the data relevant to
|
358
|
+
the current sites is passed.
|
336
359
|
econ_fun : method
|
337
360
|
reV_run() method from one of the econ modules (SingleOwner,
|
338
361
|
SAM_LCOE, WindBos).
|
@@ -354,15 +377,16 @@ class Econ(BaseGen):
|
|
354
377
|
|
355
378
|
# Extract the site df from the project points df.
|
356
379
|
site_df = pc.project_points.df
|
357
|
-
site_df = site_df.set_index(
|
380
|
+
site_df = site_df.set_index(ResourceMetaField.GID, drop=True)
|
358
381
|
|
359
382
|
# SAM execute econ analysis based on output request
|
360
383
|
try:
|
361
|
-
out = econ_fun(
|
362
|
-
|
384
|
+
out = econ_fun(
|
385
|
+
pc, site_df, output_request=output_request, **kwargs
|
386
|
+
)
|
363
387
|
except Exception as e:
|
364
388
|
out = {}
|
365
|
-
logger.exception(
|
389
|
+
logger.exception("Worker failed for PC: {}".format(pc))
|
366
390
|
raise e
|
367
391
|
|
368
392
|
return out
|
@@ -381,24 +405,27 @@ class Econ(BaseGen):
|
|
381
405
|
Output variables requested from SAM.
|
382
406
|
"""
|
383
407
|
|
384
|
-
output_request =
|
408
|
+
output_request = super()._parse_output_request(req)
|
385
409
|
|
386
410
|
for request in output_request:
|
387
411
|
if request not in self.OUT_ATTRS:
|
388
|
-
msg = (
|
389
|
-
|
412
|
+
msg = (
|
413
|
+
'User output request "{}" not recognized. '
|
414
|
+
"Will attempt to extract from PySAM.".format(request)
|
415
|
+
)
|
390
416
|
logger.debug(msg)
|
391
417
|
|
392
|
-
modules = []
|
393
|
-
|
394
|
-
if request in self.OPTIONS:
|
395
|
-
modules.append(self.OPTIONS[request])
|
418
|
+
modules = [self.OPTIONS[request] for request in output_request
|
419
|
+
if request in self.OPTIONS]
|
396
420
|
|
397
421
|
if not any(modules):
|
398
|
-
msg = (
|
399
|
-
|
400
|
-
|
401
|
-
|
422
|
+
msg = (
|
423
|
+
"None of the user output requests were recognized. "
|
424
|
+
"Cannot run reV econ. "
|
425
|
+
"At least one of the following must be requested: {}".format(
|
426
|
+
list(self.OPTIONS.keys())
|
427
|
+
)
|
428
|
+
)
|
402
429
|
logger.exception(msg)
|
403
430
|
raise ExecutionError(msg)
|
404
431
|
|
@@ -413,9 +440,11 @@ class Econ(BaseGen):
|
|
413
440
|
self._sam_module = SingleOwner
|
414
441
|
self._fun = SingleOwner.reV_run
|
415
442
|
else:
|
416
|
-
msg = (
|
417
|
-
|
418
|
-
|
443
|
+
msg = (
|
444
|
+
"Econ outputs requested from different SAM modules not "
|
445
|
+
"currently supported. Output request variables require "
|
446
|
+
"SAM methods: {}".format(modules)
|
447
|
+
)
|
419
448
|
raise ValueError(msg)
|
420
449
|
|
421
450
|
return list(set(output_request))
|
@@ -441,12 +470,14 @@ class Econ(BaseGen):
|
|
441
470
|
"""
|
442
471
|
|
443
472
|
if dset in self.site_data:
|
444
|
-
data_shape = (n_sites,
|
473
|
+
data_shape = (n_sites,)
|
445
474
|
data = self.site_data[dset].values[0]
|
446
475
|
|
447
476
|
if isinstance(data, (list, tuple, np.ndarray, str)):
|
448
|
-
msg = (
|
449
|
-
|
477
|
+
msg = (
|
478
|
+
"Cannot pass through non-scalar site_data "
|
479
|
+
'input key "{}" as an output_request!'.format(dset)
|
480
|
+
)
|
450
481
|
logger.error(msg)
|
451
482
|
raise ExecutionError(msg)
|
452
483
|
|
@@ -455,8 +486,7 @@ class Econ(BaseGen):
|
|
455
486
|
|
456
487
|
return data_shape
|
457
488
|
|
458
|
-
def run(self, out_fpath=None, max_workers=1, timeout=1800,
|
459
|
-
pool_size=None):
|
489
|
+
def run(self, out_fpath=None, max_workers=1, timeout=1800, pool_size=None):
|
460
490
|
"""Execute a parallel reV econ run with smart data flushing.
|
461
491
|
|
462
492
|
Parameters
|
@@ -494,52 +524,74 @@ class Econ(BaseGen):
|
|
494
524
|
else:
|
495
525
|
self._init_fpath(out_fpath, ModuleName.ECON)
|
496
526
|
|
497
|
-
self._init_h5(mode=
|
527
|
+
self._init_h5(mode="a" if self._append else "w")
|
498
528
|
self._init_out_arrays()
|
499
529
|
|
500
530
|
diff = list(set(self.points_control.sites)
|
501
|
-
- set(self.meta[
|
531
|
+
- set(self.meta[ResourceMetaField.GID].values))
|
502
532
|
if diff:
|
503
|
-
raise Exception(
|
504
|
-
|
505
|
-
|
506
|
-
|
533
|
+
raise Exception(
|
534
|
+
"The following analysis sites were requested "
|
535
|
+
"through project points for econ but are not "
|
536
|
+
'found in the CF file ("{}"): {}'.format(self.cf_file, diff)
|
537
|
+
)
|
507
538
|
|
508
539
|
# make a kwarg dict
|
509
|
-
kwargs = {
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
logger.
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
540
|
+
kwargs = {
|
541
|
+
"output_request": self.output_request,
|
542
|
+
"cf_file": self.cf_file,
|
543
|
+
"year": self.year,
|
544
|
+
}
|
545
|
+
|
546
|
+
logger.info(
|
547
|
+
"Running econ with smart data flushing " "for: {}".format(
|
548
|
+
self.points_control
|
549
|
+
)
|
550
|
+
)
|
551
|
+
logger.debug(
|
552
|
+
'The following project points were specified: "{}"'.format(
|
553
|
+
self.project_points
|
554
|
+
)
|
555
|
+
)
|
556
|
+
logger.debug(
|
557
|
+
"The following SAM configs are available to this run:\n{}".format(
|
558
|
+
pprint.pformat(self.sam_configs, indent=4)
|
559
|
+
)
|
560
|
+
)
|
561
|
+
logger.debug(
|
562
|
+
"The SAM output variables have been requested:\n{}".format(
|
563
|
+
self.output_request
|
564
|
+
)
|
565
|
+
)
|
521
566
|
|
522
567
|
try:
|
523
|
-
kwargs[
|
568
|
+
kwargs["econ_fun"] = self._fun
|
524
569
|
if max_workers == 1:
|
525
|
-
logger.debug(
|
526
|
-
|
570
|
+
logger.debug(
|
571
|
+
"Running serial econ for: {}".format(self.points_control)
|
572
|
+
)
|
527
573
|
for i, pc_sub in enumerate(self.points_control):
|
528
574
|
self.out = self._run_single_worker(pc_sub, **kwargs)
|
529
|
-
logger.info(
|
530
|
-
|
531
|
-
|
532
|
-
|
575
|
+
logger.info(
|
576
|
+
"Finished reV econ serial compute for: {} "
|
577
|
+
"(iteration {} out of {})".format(
|
578
|
+
pc_sub, i + 1, len(self.points_control)
|
579
|
+
)
|
580
|
+
)
|
533
581
|
self.flush()
|
534
582
|
else:
|
535
|
-
logger.debug(
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
583
|
+
logger.debug(
|
584
|
+
"Running parallel econ for: {}".format(self.points_control)
|
585
|
+
)
|
586
|
+
self._parallel_run(
|
587
|
+
max_workers=max_workers,
|
588
|
+
pool_size=pool_size,
|
589
|
+
timeout=timeout,
|
590
|
+
**kwargs,
|
591
|
+
)
|
540
592
|
|
541
593
|
except Exception as e:
|
542
|
-
logger.exception(
|
594
|
+
logger.exception("SmartParallelJob.execute() failed for econ.")
|
543
595
|
raise e
|
544
596
|
|
545
597
|
return self._out_fpath
|
reV/econ/economies_of_scale.py
CHANGED
@@ -3,14 +3,18 @@
|
|
3
3
|
reV module for calculating economies of scale where larger power plants will
|
4
4
|
have reduced capital cost.
|
5
5
|
"""
|
6
|
-
|
6
|
+
|
7
7
|
import copy
|
8
|
+
import logging
|
8
9
|
import re
|
9
|
-
|
10
|
+
|
11
|
+
# pylint: disable=unused-import
|
12
|
+
import numpy as np
|
10
13
|
import pandas as pd
|
14
|
+
from rex.utilities.utilities import check_eval_str
|
11
15
|
|
12
16
|
from reV.econ.utilities import lcoe_fcr
|
13
|
-
from
|
17
|
+
from reV.utilities import SupplyCurveField
|
14
18
|
|
15
19
|
logger = logging.getLogger(__name__)
|
16
20
|
|
@@ -57,23 +61,26 @@ class EconomiesOfScale:
|
|
57
61
|
check_eval_str(str(self._eqn))
|
58
62
|
|
59
63
|
if isinstance(self._data, pd.DataFrame):
|
60
|
-
self._data = {
|
61
|
-
|
64
|
+
self._data = {
|
65
|
+
k: self._data[k].values.flatten() for k in self._data.columns
|
66
|
+
}
|
62
67
|
|
63
68
|
if not isinstance(self._data, dict):
|
64
|
-
e = (
|
65
|
-
|
69
|
+
e = (
|
70
|
+
"Cannot evaluate EconomiesOfScale with data input of type: "
|
71
|
+
"{}".format(type(self._data))
|
72
|
+
)
|
73
|
+
|
66
74
|
logger.error(e)
|
67
75
|
raise TypeError(e)
|
68
76
|
|
69
|
-
missing = []
|
70
|
-
for name in self.vars:
|
71
|
-
if name not in self._data:
|
72
|
-
missing.append(name)
|
77
|
+
missing = [name for name in self.vars if name not in self._data]
|
73
78
|
|
74
79
|
if any(missing):
|
75
|
-
e = (
|
76
|
-
|
80
|
+
e = (
|
81
|
+
"Cannot evaluate EconomiesOfScale, missing data for variables"
|
82
|
+
": {} for equation: {}".format(missing, self._eqn)
|
83
|
+
)
|
77
84
|
logger.error(e)
|
78
85
|
raise KeyError(e)
|
79
86
|
|
@@ -90,7 +97,7 @@ class EconomiesOfScale:
|
|
90
97
|
@staticmethod
|
91
98
|
def is_method(s):
|
92
99
|
"""Check if a string is a numpy/pandas or python builtin method"""
|
93
|
-
return bool(s.startswith((
|
100
|
+
return bool(s.startswith(("np.", "pd.")) or s in dir(__builtins__))
|
94
101
|
|
95
102
|
@property
|
96
103
|
def vars(self):
|
@@ -106,14 +113,14 @@ class EconomiesOfScale:
|
|
106
113
|
"""
|
107
114
|
var_names = []
|
108
115
|
if self._eqn is not None:
|
109
|
-
delimiters = (
|
110
|
-
|
116
|
+
delimiters = (">", "<", ">=", "<=", "==", ",", "*", "/", "+", "-",
|
117
|
+
" ", "(", ")", "[", "]")
|
118
|
+
regex_pattern = "|".join(map(re.escape, delimiters))
|
111
119
|
var_names = []
|
112
120
|
for sub in re.split(regex_pattern, str(self._eqn)):
|
113
|
-
if sub:
|
114
|
-
|
115
|
-
|
116
|
-
var_names = sorted(list(set(var_names)))
|
121
|
+
if sub and not self.is_num(sub) and not self.is_method(sub):
|
122
|
+
var_names.append(sub)
|
123
|
+
var_names = sorted(set(var_names))
|
117
124
|
|
118
125
|
return var_names
|
119
126
|
|
@@ -162,9 +169,10 @@ class EconomiesOfScale:
|
|
162
169
|
break
|
163
170
|
|
164
171
|
if out is None:
|
165
|
-
e = (
|
166
|
-
|
167
|
-
|
172
|
+
e = (
|
173
|
+
"Could not find requested key list ({}) in the input "
|
174
|
+
"dictionary keys: {}".format(key_list, list(input_dict.keys()))
|
175
|
+
)
|
168
176
|
logger.error(e)
|
169
177
|
raise KeyError(e)
|
170
178
|
|
@@ -183,6 +191,33 @@ class EconomiesOfScale:
|
|
183
191
|
"""
|
184
192
|
return self._evaluate()
|
185
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
|
+
cost = cap * cost_per_mw
|
216
|
+
if cost > 0:
|
217
|
+
return cost
|
218
|
+
|
219
|
+
return None
|
220
|
+
|
186
221
|
@property
|
187
222
|
def raw_capital_cost(self):
|
188
223
|
"""Unscaled (raw) capital cost found in the data input arg.
|
@@ -192,7 +227,13 @@ class EconomiesOfScale:
|
|
192
227
|
out : float | np.ndarray
|
193
228
|
Unscaled (raw) capital_cost found in the data input arg.
|
194
229
|
"""
|
195
|
-
|
230
|
+
raw_capital_cost_from_cap = self._cost_from_cap(
|
231
|
+
SupplyCurveField.COST_SITE_OCC_USD_PER_AC_MW
|
232
|
+
)
|
233
|
+
if raw_capital_cost_from_cap is not None:
|
234
|
+
return raw_capital_cost_from_cap
|
235
|
+
|
236
|
+
key_list = ["capital_cost", "mean_capital_cost"]
|
196
237
|
return self._get_prioritized_keys(self._data, key_list)
|
197
238
|
|
198
239
|
@property
|
@@ -210,18 +251,6 @@ class EconomiesOfScale:
|
|
210
251
|
cc *= self.capital_cost_scalar
|
211
252
|
return cc
|
212
253
|
|
213
|
-
@property
|
214
|
-
def system_capacity(self):
|
215
|
-
"""Get the system capacity in kW (SAM input, not the reV supply
|
216
|
-
curve capacity).
|
217
|
-
|
218
|
-
Returns
|
219
|
-
-------
|
220
|
-
out : float | np.ndarray
|
221
|
-
"""
|
222
|
-
key_list = ['system_capacity', 'mean_system_capacity']
|
223
|
-
return self._get_prioritized_keys(self._data, key_list)
|
224
|
-
|
225
254
|
@property
|
226
255
|
def fcr(self):
|
227
256
|
"""Fixed charge rate from input data arg
|
@@ -231,8 +260,12 @@ class EconomiesOfScale:
|
|
231
260
|
out : float | np.ndarray
|
232
261
|
Fixed charge rate from input data arg
|
233
262
|
"""
|
234
|
-
|
235
|
-
|
263
|
+
fcr = self._data.get(SupplyCurveField.FIXED_CHARGE_RATE)
|
264
|
+
if fcr is not None and fcr > 0:
|
265
|
+
return fcr
|
266
|
+
|
267
|
+
key_list = ["fixed_charge_rate", "mean_fixed_charge_rate", "fcr",
|
268
|
+
"mean_fcr"]
|
236
269
|
return self._get_prioritized_keys(self._data, key_list)
|
237
270
|
|
238
271
|
@property
|
@@ -244,8 +277,14 @@ class EconomiesOfScale:
|
|
244
277
|
out : float | np.ndarray
|
245
278
|
Fixed operating cost from input data arg
|
246
279
|
"""
|
247
|
-
|
248
|
-
|
280
|
+
foc_from_cap = self._cost_from_cap(
|
281
|
+
SupplyCurveField.COST_BASE_FOC_USD_PER_AC_MW
|
282
|
+
)
|
283
|
+
if foc_from_cap is not None:
|
284
|
+
return foc_from_cap
|
285
|
+
|
286
|
+
key_list = ["fixed_operating_cost", "mean_fixed_operating_cost",
|
287
|
+
"foc", "mean_foc"]
|
249
288
|
return self._get_prioritized_keys(self._data, key_list)
|
250
289
|
|
251
290
|
@property
|
@@ -257,8 +296,14 @@ class EconomiesOfScale:
|
|
257
296
|
out : float | np.ndarray
|
258
297
|
Variable operating cost from input data arg
|
259
298
|
"""
|
260
|
-
|
261
|
-
|
299
|
+
voc_from_cap = self._cost_from_cap(
|
300
|
+
SupplyCurveField.COST_BASE_VOC_USD_PER_AC_MW
|
301
|
+
)
|
302
|
+
if voc_from_cap is not None:
|
303
|
+
return voc_from_cap
|
304
|
+
|
305
|
+
key_list = ["variable_operating_cost", "mean_variable_operating_cost",
|
306
|
+
"voc", "mean_voc"]
|
262
307
|
return self._get_prioritized_keys(self._data, key_list)
|
263
308
|
|
264
309
|
@property
|
@@ -284,7 +329,7 @@ class EconomiesOfScale:
|
|
284
329
|
-------
|
285
330
|
lcoe : float | np.ndarray
|
286
331
|
"""
|
287
|
-
key_list = [
|
332
|
+
key_list = [SupplyCurveField.RAW_LCOE, SupplyCurveField.MEAN_LCOE]
|
288
333
|
return copy.deepcopy(self._get_prioritized_keys(self._data, key_list))
|
289
334
|
|
290
335
|
@property
|
@@ -300,5 +345,6 @@ class EconomiesOfScale:
|
|
300
345
|
LCOE calculated with the scaled capital cost based on the
|
301
346
|
EconomiesOfScale input equation.
|
302
347
|
"""
|
303
|
-
return lcoe_fcr(
|
304
|
-
|
348
|
+
return lcoe_fcr(
|
349
|
+
self.fcr, self.scaled_capital_cost, self.foc, self.aep, self.voc
|
350
|
+
)
|