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
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.exceptions import (ExecutionError, OffshoreWindInputWarning)
19
- from reV.utilities import ModuleName
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 = {'lcoe_fcr': SAM_LCOE,
33
- 'ppa_price': SingleOwner,
34
- 'project_return_aftertax_npv': SingleOwner,
35
- 'lcoe_real': SingleOwner,
36
- 'lcoe_nom': SingleOwner,
37
- 'flip_actual_irr': SingleOwner,
38
- 'gross_revenue': SingleOwner,
39
- 'total_installed_cost': WindBos,
40
- 'turbine_cost': WindBos,
41
- 'sales_tax_cost': WindBos,
42
- 'bos_cost': WindBos,
43
- 'fixed_charge_rate': SAM_LCOE,
44
- 'capital_cost': SAM_LCOE,
45
- 'fixed_operating_cost': SAM_LCOE,
46
- 'variable_operating_cost': SAM_LCOE,
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
- """reV econ analysis class.
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(points=project_points, points_range=None,
180
- sam_configs=sam_files, cf_file=cf_file,
181
- sites_per_worker=sites_per_worker, append=append)
182
-
183
- super().__init__(pc, output_request, site_data=site_data,
184
- memory_utilization_limit=memory_utilization_limit)
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['cf_file'] = cf_file
189
- self._run_attrs['sam_module'] = self._sam_module.MODULE
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['gid'].isin(self.points_control.sites)]
223
+ cfh.meta[ResourceMetaField.GID].isin(
224
+ self.points_control.sites)]
216
225
 
217
- if 'offshore' in self._meta:
218
- if self._meta['offshore'].sum() > 1:
219
- w = ('Found offshore sites in econ meta data. '
220
- 'This functionality has been deprecated. '
221
- 'Please run the reV offshore module to '
222
- 'calculate offshore wind lcoe.')
223
- warn(w, OffshoreWindInputWarning)
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({'gid': self.points_control.sites})
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 'time_index' in cfh.datasets:
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 = {'hsds': hsds}
276
+ res_kwargs = {"hsds": hsds}
268
277
 
269
278
  with res_cls(cf_file, **res_kwargs) as f:
270
- gid0 = f.meta['gid'].values[0]
271
- gid1 = f.meta['gid'].values[-1]
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(cls, points, points_range, sam_configs, cf_file,
281
- sites_per_worker=None, append=False):
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(points, points_range, sam_configs, ModuleName.ECON,
315
- sites_per_worker=sites_per_worker,
316
- res_file=cf_file)
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(pc.project_points, cf_file,
320
- sites_per_worker=sites_per_worker)
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 'gid' column. By passing site-specific
334
- inputs in this dataframe, which was split using points_control,
335
- only the data relevant to the current sites is passed.
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('gid', drop=True)
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(pc, site_df, output_request=output_request,
362
- **kwargs)
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('Worker failed for PC: {}'.format(pc))
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 = self._output_request_type_check(req)
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 = ('User output request "{}" not recognized. '
389
- 'Will attempt to extract from PySAM.'.format(request))
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
- for request in output_request:
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 = ('None of the user output requests were recognized. '
399
- 'Cannot run reV econ. '
400
- 'At least one of the following must be requested: {}'
401
- .format(list(self.OPTIONS.keys())))
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 = ('Econ outputs requested from different SAM modules not '
417
- 'currently supported. Output request variables require '
418
- 'SAM methods: {}'.format(modules))
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 = ('Cannot pass through non-scalar site_data '
449
- 'input key "{}" as an output_request!'.format(dset))
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='a' if self._append else 'w')
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['gid'].values))
531
+ - set(self.meta[ResourceMetaField.GID].values))
502
532
  if diff:
503
- raise Exception('The following analysis sites were requested '
504
- 'through project points for econ but are not '
505
- 'found in the CF file ("{}"): {}'
506
- .format(self.cf_file, diff))
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 = {'output_request': self.output_request,
510
- 'cf_file': self.cf_file,
511
- 'year': self.year}
512
-
513
- logger.info('Running econ with smart data flushing '
514
- 'for: {}'.format(self.points_control))
515
- logger.debug('The following project points were specified: "{}"'
516
- .format(self.project_points))
517
- logger.debug('The following SAM configs are available to this run:\n{}'
518
- .format(pprint.pformat(self.sam_configs, indent=4)))
519
- logger.debug('The SAM output variables have been requested:\n{}'
520
- .format(self.output_request))
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['econ_fun'] = self._fun
568
+ kwargs["econ_fun"] = self._fun
524
569
  if max_workers == 1:
525
- logger.debug('Running serial econ for: {}'
526
- .format(self.points_control))
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('Finished reV econ serial compute for: {} '
530
- '(iteration {} out of {})'
531
- .format(pc_sub, i + 1,
532
- len(self.points_control)))
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('Running parallel econ for: {}'
536
- .format(self.points_control))
537
- self._parallel_run(max_workers=max_workers,
538
- pool_size=pool_size, timeout=timeout,
539
- **kwargs)
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('SmartParallelJob.execute() failed for econ.')
594
+ logger.exception("SmartParallelJob.execute() failed for econ.")
543
595
  raise e
544
596
 
545
597
  return self._out_fpath
@@ -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
- import logging
6
+
7
7
  import copy
8
+ import logging
8
9
  import re
9
- import numpy as np # pylint: disable=unused-import
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 rex.utilities.utilities import check_eval_str
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 = {k: self._data[k].values.flatten()
61
- for k in self._data.columns}
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 = ('Cannot evaluate EconomiesOfScale with data input of type: {}'
65
- .format(type(self._data)))
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 = ('Cannot evaluate EconomiesOfScale, missing data for variables'
76
- ': {} for equation: {}'.format(missing, self._eqn))
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(('np.', 'pd.')) or s in dir(__builtins__))
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
- regex_pattern = '|'.join(map(re.escape, delimiters))
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
- if not self.is_num(sub) and not self.is_method(sub):
115
- var_names.append(sub)
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 = ('Could not find requested key list ({}) in the input '
166
- 'dictionary keys: {}'
167
- .format(key_list, list(input_dict.keys())))
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
- key_list = ['capital_cost', 'mean_capital_cost']
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
- key_list = ['fixed_charge_rate', 'mean_fixed_charge_rate',
235
- 'fcr', 'mean_fcr']
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
- key_list = ['fixed_operating_cost', 'mean_fixed_operating_cost',
248
- 'foc', 'mean_foc']
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
- key_list = ['variable_operating_cost', 'mean_variable_operating_cost',
261
- 'voc', 'mean_voc']
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 = ['raw_lcoe', 'mean_lcoe']
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(self.fcr, self.scaled_capital_cost, self.foc,
304
- self.aep, self.voc)
348
+ return lcoe_fcr(
349
+ self.fcr, self.scaled_capital_cost, self.foc, self.aep, self.voc
350
+ )