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/SAM/SAM.py CHANGED
@@ -3,25 +3,38 @@
3
3
 
4
4
  Wraps the NREL-PySAM library with additional reV features.
5
5
  """
6
+
6
7
  import copy
7
8
  import json
8
9
  import logging
9
- import numpy as np
10
10
  import os
11
- import pandas as pd
12
11
  from warnings import warn
13
- import PySAM.GenericSystem as generic
14
12
 
15
- from reV.utilities.exceptions import (SAMInputWarning, SAMInputError,
16
- SAMExecutionError, ResourceError)
17
-
18
- from rex.multi_file_resource import (MultiFileResource, MultiFileNSRDB,
19
- MultiFileWTK)
20
- from rex.renewable_resource import (WindResource, SolarResource, NSRDB,
21
- WaveResource, GeothermalResource)
13
+ import numpy as np
14
+ import pandas as pd
15
+ import PySAM.GenericSystem as generic
16
+ from rex.multi_file_resource import (
17
+ MultiFileNSRDB,
18
+ MultiFileResource,
19
+ MultiFileWTK,
20
+ )
22
21
  from rex.multi_res_resource import MultiResolutionResource
22
+ from rex.renewable_resource import (
23
+ NSRDB,
24
+ GeothermalResource,
25
+ SolarResource,
26
+ WaveResource,
27
+ WindResource,
28
+ )
23
29
  from rex.utilities.utilities import check_res_file
24
30
 
31
+ from reV.utilities import ResourceMetaField
32
+ from reV.utilities.exceptions import (
33
+ ResourceError,
34
+ SAMExecutionError,
35
+ SAMInputError,
36
+ SAMInputWarning,
37
+ )
25
38
 
26
39
  logger = logging.getLogger(__name__)
27
40
 
@@ -31,18 +44,19 @@ class SamResourceRetriever:
31
44
 
32
45
  # Mapping for reV technology and SAM module to h5 resource handler type
33
46
  # SolarResource is swapped for NSRDB if the res_file contains "nsrdb"
34
- RESOURCE_TYPES = {'geothermal': GeothermalResource,
35
- 'pvwattsv5': SolarResource,
36
- 'pvwattsv7': SolarResource,
37
- 'pvwattsv8': SolarResource,
38
- 'pvsamv1': SolarResource,
39
- 'tcsmoltensalt': SolarResource,
40
- 'solarwaterheat': SolarResource,
41
- 'troughphysicalheat': SolarResource,
42
- 'lineardirectsteam': SolarResource,
43
- 'windpower': WindResource,
44
- 'mhkwave': WaveResource
45
- }
47
+ RESOURCE_TYPES = {
48
+ "geothermal": GeothermalResource,
49
+ "pvwattsv5": SolarResource,
50
+ "pvwattsv7": SolarResource,
51
+ "pvwattsv8": SolarResource,
52
+ "pvsamv1": SolarResource,
53
+ "tcsmoltensalt": SolarResource,
54
+ "solarwaterheat": SolarResource,
55
+ "troughphysicalheat": SolarResource,
56
+ "lineardirectsteam": SolarResource,
57
+ "windpower": WindResource,
58
+ "mhkwave": WaveResource,
59
+ }
46
60
 
47
61
  @staticmethod
48
62
  def _get_base_handler(res_file, module):
@@ -69,15 +83,17 @@ class SamResourceRetriever:
69
83
  res_handler = SamResourceRetriever.RESOURCE_TYPES[module.lower()]
70
84
 
71
85
  except KeyError as e:
72
- msg = ('Cannot interpret what kind of resource handler the SAM '
73
- 'module or reV technology "{}" requires. Expecting one of '
74
- 'the following SAM modules or reV technologies: {}'
75
- .format(module,
76
- list(SamResourceRetriever.RESOURCE_TYPES.keys())))
86
+ msg = (
87
+ "Cannot interpret what kind of resource handler the SAM "
88
+ 'module or reV technology "{}" requires. Expecting one of '
89
+ "the following SAM modules or reV technologies: {}".format(
90
+ module, list(SamResourceRetriever.RESOURCE_TYPES.keys())
91
+ )
92
+ )
77
93
  logger.exception(msg)
78
94
  raise SAMExecutionError(msg) from e
79
95
 
80
- if res_handler == SolarResource and 'nsrdb' in res_file.lower():
96
+ if res_handler == SolarResource and "nsrdb" in res_file.lower():
81
97
  # Use NSRDB handler if definitely an NSRDB file
82
98
  res_handler = NSRDB
83
99
 
@@ -113,8 +129,9 @@ class SamResourceRetriever:
113
129
  return res_gids
114
130
 
115
131
  @classmethod
116
- def _make_res_kwargs(cls, res_handler, project_points, output_request,
117
- gid_map):
132
+ def _make_res_kwargs(
133
+ cls, res_handler, project_points, output_request, gid_map
134
+ ):
118
135
  """
119
136
  Make Resource.preloadSam args and kwargs
120
137
 
@@ -146,9 +163,9 @@ class SamResourceRetriever:
146
163
  kwargs = {}
147
164
  if res_handler in (SolarResource, NSRDB):
148
165
  # check for clearsky irradiation analysis for NSRDB
149
- kwargs['clearsky'] = project_points.sam_config_obj.clearsky
150
- kwargs['bifacial'] = project_points.sam_config_obj.bifacial
151
- kwargs['tech'] = project_points.tech
166
+ kwargs["clearsky"] = project_points.sam_config_obj.clearsky
167
+ kwargs["bifacial"] = project_points.sam_config_obj.bifacial
168
+ kwargs["tech"] = project_points.tech
152
169
 
153
170
  downscale = project_points.sam_config_obj.downscale
154
171
  # check for downscaling request
@@ -156,30 +173,34 @@ class SamResourceRetriever:
156
173
  # make sure that downscaling is only requested for NSRDB
157
174
  # resource
158
175
  if res_handler != NSRDB:
159
- msg = ('Downscaling was requested for a non-NSRDB '
160
- 'resource file. reV does not have this capability '
161
- 'at the current time. Please contact a developer '
162
- 'for more information on this feature.')
176
+ msg = (
177
+ "Downscaling was requested for a non-NSRDB "
178
+ "resource file. reV does not have this capability "
179
+ "at the current time. Please contact a developer "
180
+ "for more information on this feature."
181
+ )
163
182
  logger.warning(msg)
164
183
  warn(msg, SAMInputWarning)
165
184
  else:
166
185
  # pass through the downscaling request
167
- kwargs['downscale'] = downscale
186
+ kwargs["downscale"] = downscale
168
187
 
169
188
  elif res_handler == WindResource:
170
- args += (project_points.h, )
171
- kwargs['icing'] = project_points.sam_config_obj.icing
172
- if project_points.curtailment is not None:
173
- if project_points.curtailment.precipitation:
174
- # make precip rate available for curtailment analysis
175
- kwargs['precip_rate'] = True
189
+ args += (project_points.h,)
190
+ kwargs["icing"] = project_points.sam_config_obj.icing
191
+ if (
192
+ project_points.curtailment is not None
193
+ and project_points.curtailment.precipitation
194
+ ):
195
+ # make precip rate available for curtailment analysis
196
+ kwargs["precip_rate"] = True
176
197
 
177
198
  elif res_handler == GeothermalResource:
178
- args += (project_points.d, )
199
+ args += (project_points.d,)
179
200
 
180
201
  # Check for resource means
181
- if any(req.endswith('_mean') for req in output_request):
182
- kwargs['means'] = True
202
+ if any(req.endswith("_mean") for req in output_request):
203
+ kwargs["means"] = True
183
204
 
184
205
  return kwargs, args
185
206
 
@@ -218,9 +239,17 @@ class SamResourceRetriever:
218
239
  return res_handler, kwargs, res_file
219
240
 
220
241
  @classmethod
221
- def get(cls, res_file, project_points, module,
222
- output_request=('cf_mean', ), gid_map=None,
223
- lr_res_file=None, nn_map=None, bias_correct=None):
242
+ def get(
243
+ cls,
244
+ res_file,
245
+ project_points,
246
+ module,
247
+ output_request=("cf_mean",),
248
+ gid_map=None,
249
+ lr_res_file=None,
250
+ nn_map=None,
251
+ bias_correct=None,
252
+ ):
224
253
  """Get the SAM resource iterator object (single year, single file).
225
254
 
226
255
  Parameters
@@ -280,27 +309,30 @@ class SamResourceRetriever:
280
309
  """
281
310
 
282
311
  res_handler = cls._get_base_handler(res_file, module)
283
- kwargs, args = cls._make_res_kwargs(res_handler, project_points,
284
- output_request, gid_map)
312
+ kwargs, args = cls._make_res_kwargs(
313
+ res_handler, project_points, output_request, gid_map
314
+ )
285
315
 
286
316
  multi_h5_res, hsds = check_res_file(res_file)
287
317
  if multi_h5_res:
288
- res_handler, kwargs, res_file = cls._multi_file_mods(res_handler,
289
- kwargs,
290
- res_file)
318
+ res_handler, kwargs, res_file = cls._multi_file_mods(
319
+ res_handler, kwargs, res_file
320
+ )
291
321
  else:
292
- kwargs['hsds'] = hsds
322
+ kwargs["hsds"] = hsds
293
323
 
294
- kwargs['time_index_step'] = \
324
+ kwargs["time_index_step"] = (
295
325
  project_points.sam_config_obj.time_index_step
326
+ )
296
327
 
297
328
  if lr_res_file is None:
298
329
  res = res_handler.preload_SAM(res_file, *args, **kwargs)
299
330
  else:
300
- kwargs['handler_class'] = res_handler
301
- kwargs['nn_map'] = nn_map
302
- res = MultiResolutionResource.preload_SAM(res_file, lr_res_file,
303
- *args, **kwargs)
331
+ kwargs["handler_class"] = res_handler
332
+ kwargs["nn_map"] = nn_map
333
+ res = MultiResolutionResource.preload_SAM(
334
+ res_file, lr_res_file, *args, **kwargs
335
+ )
304
336
 
305
337
  if bias_correct is not None:
306
338
  res.bias_correct(bias_correct)
@@ -315,15 +347,15 @@ class Sam:
315
347
  PYSAM = generic
316
348
 
317
349
  # callable attributes to be ignored in the get/set logic
318
- IGNORE_ATTRS = ['assign', 'execute', 'export']
350
+ IGNORE_ATTRS = ["assign", "execute", "export"]
319
351
 
320
352
  def __init__(self):
321
353
  self._pysam = self.PYSAM.new()
322
354
  self._attr_dict = None
323
355
  self._inputs = []
324
356
  self.sam_sys_inputs = {}
325
- if 'constant' in self.input_list:
326
- self['constant'] = 0.0
357
+ if "constant" in self.input_list:
358
+ self["constant"] = 0.0
327
359
 
328
360
  def __getitem__(self, key):
329
361
  """Get the value of a PySAM attribute (either input or output).
@@ -359,24 +391,27 @@ class Sam:
359
391
  """
360
392
 
361
393
  if key not in self.input_list:
362
- msg = ('Could not set input key "{}". Attribute not '
363
- 'found in PySAM object: "{}"'
364
- .format(key, self.pysam))
394
+ msg = (
395
+ 'Could not set input key "{}". Attribute not '
396
+ 'found in PySAM object: "{}"'.format(key, self.pysam)
397
+ )
365
398
  logger.exception(msg)
366
399
  raise SAMInputError(msg)
367
- else:
368
- self.sam_sys_inputs[key] = value
369
- group = self._get_group(key, outputs=False)
370
- try:
371
- setattr(getattr(self.pysam, group), key, value)
372
- except Exception as e:
373
- msg = ('Could not set input key "{}" to '
374
- 'group "{}" in "{}".\n'
375
- 'Data is: {} ({})\n'
376
- 'Received the following error: "{}"'
377
- .format(key, group, self.pysam, value, type(value), e))
378
- logger.exception(msg)
379
- raise SAMInputError(msg) from e
400
+ self.sam_sys_inputs[key] = value
401
+ group = self._get_group(key, outputs=False)
402
+ try:
403
+ setattr(getattr(self.pysam, group), key, value)
404
+ except Exception as e:
405
+ msg = (
406
+ 'Could not set input key "{}" to '
407
+ 'group "{}" in "{}".\n'
408
+ "Data is: {} ({})\n"
409
+ 'Received the following error: "{}"'.format(
410
+ key, group, self.pysam, value, type(value), e
411
+ )
412
+ )
413
+ logger.exception(msg)
414
+ raise SAMInputError(msg) from e
380
415
 
381
416
  @property
382
417
  def pysam(self):
@@ -391,7 +426,7 @@ class Sam:
391
426
  -------
392
427
  PySAM.GenericSystem
393
428
  """
394
- obj = cls.PYSAM.default('GenericSystemNone')
429
+ obj = cls.PYSAM.default("GenericSystemNone")
395
430
  obj.execute()
396
431
 
397
432
  return obj
@@ -409,8 +444,9 @@ class Sam:
409
444
  """
410
445
  if self._attr_dict is None:
411
446
  keys = self._get_pysam_attrs(self.pysam)
412
- self._attr_dict = {k: self._get_pysam_attrs(getattr(self.pysam, k))
413
- for k in keys}
447
+ self._attr_dict = {
448
+ k: self._get_pysam_attrs(getattr(self.pysam, k)) for k in keys
449
+ }
414
450
 
415
451
  return self._attr_dict
416
452
 
@@ -425,7 +461,7 @@ class Sam:
425
461
  """
426
462
  if not any(self._inputs):
427
463
  for k, v in self.attr_dict.items():
428
- if k.lower() != 'outputs':
464
+ if k.lower() != "outputs":
429
465
  self._inputs += v
430
466
 
431
467
  return self._inputs
@@ -450,8 +486,7 @@ class Sam:
450
486
 
451
487
  temp = self.attr_dict
452
488
  if not outputs:
453
- temp = {k: v for (k, v) in temp.items()
454
- if k.lower() != 'outputs'}
489
+ temp = {k: v for (k, v) in temp.items() if k.lower() != "outputs"}
455
490
 
456
491
  for k, v in temp.items():
457
492
  if key in v:
@@ -474,8 +509,11 @@ class Sam:
474
509
  List of attrs belonging to obj with dunder attrs and IGNORE_ATTRS
475
510
  not included.
476
511
  """
477
- attrs = [a for a in dir(obj) if not a.startswith('__')
478
- and a not in self.IGNORE_ATTRS]
512
+ attrs = [
513
+ a
514
+ for a in dir(obj)
515
+ if not a.startswith("__") and a not in self.IGNORE_ATTRS
516
+ ]
479
517
  return attrs
480
518
 
481
519
  def execute(self):
@@ -506,19 +544,21 @@ class Sam:
506
544
  Filtered Input value associated with key.
507
545
  """
508
546
 
509
- if '.' in key:
510
- key = key.replace('.', '_')
547
+ if "." in key:
548
+ key = key.replace(".", "_")
511
549
 
512
- if ':constant' in key and 'adjust:' in key:
513
- key = key.replace('adjust:', '')
550
+ if ":constant" in key and "adjust:" in key:
551
+ key = key.replace("adjust:", "")
514
552
 
515
- if isinstance(value, str) and '[' in value and ']' in value:
553
+ if isinstance(value, str) and "[" in value and "]" in value:
516
554
  try:
517
555
  value = json.loads(value)
518
556
  except json.JSONDecodeError:
519
- msg = ('Found a weird SAM config input for "{}" that looks '
520
- 'like a stringified-list but could not run through '
521
- 'json.loads() so skipping: {}'.format(key, value))
557
+ msg = (
558
+ 'Found a weird SAM config input for "{}" that looks '
559
+ "like a stringified-list but could not run through "
560
+ "json.loads() so skipping: {}".format(key, value)
561
+ )
522
562
  logger.warning(msg)
523
563
  warn(msg)
524
564
 
@@ -541,8 +581,7 @@ class Sam:
541
581
  if k in self.input_list and v is not None:
542
582
  self[k] = v
543
583
  elif raise_warning:
544
- wmsg = ('Not setting input "{}" to: {}.'
545
- .format(k, v))
584
+ wmsg = 'Not setting input "{}" to: {}.'.format(k, v)
546
585
  warn(wmsg, SAMInputWarning)
547
586
  logger.warning(wmsg)
548
587
 
@@ -553,8 +592,9 @@ class RevPySam(Sam):
553
592
  DIR = os.path.dirname(os.path.realpath(__file__))
554
593
  MODULE = None
555
594
 
556
- def __init__(self, meta, sam_sys_inputs, output_request,
557
- site_sys_inputs=None):
595
+ def __init__(
596
+ self, meta, sam_sys_inputs, output_request, site_sys_inputs=None
597
+ ):
558
598
  """Initialize a SAM object.
559
599
 
560
600
  Parameters
@@ -567,7 +607,7 @@ class RevPySam(Sam):
567
607
  Site-agnostic SAM system model inputs arguments.
568
608
  output_request : list
569
609
  Requested SAM outputs (e.g., 'cf_mean', 'annual_energy',
570
- 'cf_profile', 'gen_profile', 'energy_yield', 'ppa_price',
610
+ , 'gen_profile', 'energy_yield', 'ppa_price',
571
611
  'lcoe_fcr').
572
612
  site_sys_inputs : dict
573
613
  Optional set of site-specific SAM system inputs to complement the
@@ -586,6 +626,8 @@ class RevPySam(Sam):
586
626
 
587
627
  self._meta = self._parse_meta(meta)
588
628
  self._parse_site_sys_inputs(site_sys_inputs)
629
+ _add_cost_defaults(self.sam_sys_inputs)
630
+ _add_sys_capacity(self.sam_sys_inputs)
589
631
 
590
632
  @property
591
633
  def meta(self):
@@ -623,11 +665,13 @@ class RevPySam(Sam):
623
665
  Resource dataframe with all February 29th timesteps removed.
624
666
  """
625
667
 
626
- if hasattr(resource, 'index'):
627
- if (hasattr(resource.index, 'month')
628
- and hasattr(resource.index, 'day')):
629
- leap_day = ((resource.index.month == 2)
630
- & (resource.index.day == 29))
668
+ if hasattr(resource, "index"):
669
+ if hasattr(resource.index, "month") and hasattr(
670
+ resource.index, "day"
671
+ ):
672
+ leap_day = (resource.index.month == 2) & (
673
+ resource.index.day == 29
674
+ )
631
675
  resource = resource.drop(resource.index[leap_day])
632
676
 
633
677
  return resource
@@ -651,13 +695,15 @@ class RevPySam(Sam):
651
695
  arr : ndarray
652
696
  Truncated array of data such that there are 365 days
653
697
  """
654
- msg = ('A valid time_index must be supplied to ensure the proper '
655
- 'resource length! Instead {} was supplied'
656
- .format(type(time_index)))
698
+ msg = (
699
+ "A valid time_index must be supplied to ensure the proper "
700
+ "resource length! Instead {} was supplied".format(type(time_index))
701
+ )
657
702
  assert isinstance(time_index, pd.DatetimeIndex)
658
703
 
659
- msg = ('arr length {} does not match time_index length {}!'
660
- .format(len(arr), len(time_index)))
704
+ msg = "arr length {} does not match time_index length {}!".format(
705
+ len(arr), len(time_index)
706
+ )
661
707
  assert len(arr) == len(time_index)
662
708
 
663
709
  if time_index.is_leap_year.all():
@@ -669,7 +715,7 @@ class RevPySam(Sam):
669
715
  s = np.where(mask)[0][-1]
670
716
 
671
717
  freq = pd.infer_freq(time_index[:s])
672
- msg = 'frequencies do not match before and after 2/29'
718
+ msg = "frequencies do not match before and after 2/29"
673
719
  assert freq == pd.infer_freq(time_index[s + 1:]), msg
674
720
  else:
675
721
  freq = pd.infer_freq(time_index)
@@ -677,8 +723,10 @@ class RevPySam(Sam):
677
723
  freq = pd.infer_freq(time_index)
678
724
 
679
725
  if freq is None:
680
- msg = ('Resource time_index does not have a consistent time-step '
681
- '(frequency)!')
726
+ msg = (
727
+ "Resource time_index does not have a consistent time-step "
728
+ "(frequency)!"
729
+ )
682
730
  logger.error(msg)
683
731
  raise ResourceError(msg)
684
732
 
@@ -696,7 +744,7 @@ class RevPySam(Sam):
696
744
  @staticmethod
697
745
  def make_datetime(series):
698
746
  """Ensure that pd series is a datetime series with dt accessor"""
699
- if not hasattr(series, 'dt'):
747
+ if not hasattr(series, "dt"):
700
748
  series = pd.to_datetime(pd.Series(series))
701
749
 
702
750
  return series
@@ -727,7 +775,7 @@ class RevPySam(Sam):
727
775
  if t == 1.0:
728
776
  time_interval += 1
729
777
  break
730
- elif t == 0.0:
778
+ if t == 0.0:
731
779
  time_interval += 1
732
780
 
733
781
  return int(time_interval)
@@ -751,10 +799,11 @@ class RevPySam(Sam):
751
799
  location. Should include values for latitude, longitude, elevation,
752
800
  and timezone. Can be None for econ runs.
753
801
  """
754
-
755
802
  if isinstance(meta, pd.DataFrame):
756
- msg = ('Meta data must only be for a single site but received: {}'
757
- .format(meta))
803
+ msg = (
804
+ "Meta data must only be for a single site but received: "
805
+ f"{meta}"
806
+ )
758
807
  assert len(meta) == 1, msg
759
808
  meta = meta.iloc[0]
760
809
 
@@ -785,22 +834,20 @@ class RevPySam(Sam):
785
834
  """Returns true if SAM data is array-like. False if scalar."""
786
835
  if isinstance(val, (int, float, str)):
787
836
  return False
837
+ try:
838
+ len(val)
839
+ except TypeError:
840
+ return False
788
841
  else:
789
- try:
790
- len(val)
791
- except TypeError:
792
- return False
793
- else:
794
- return True
842
+ return True
795
843
 
796
844
  @classmethod
797
845
  def _is_hourly(cls, val):
798
846
  """Returns true if SAM data is hourly or sub-hourly. False otherise."""
799
847
  if not cls._is_arr_like(val):
800
848
  return False
801
- else:
802
- L = len(val)
803
- return L >= 8760
849
+ L = len(val)
850
+ return L >= 8760
804
851
 
805
852
  def outputs_to_utc_arr(self):
806
853
  """Convert array-like SAM outputs to UTC np.ndarrays"""
@@ -815,8 +862,11 @@ class RevPySam(Sam):
815
862
  output = output.astype(np.int32)
816
863
 
817
864
  if self._is_hourly(output):
818
- n_roll = int(-1 * self.meta['timezone']
819
- * self.time_interval)
865
+ n_roll = int(
866
+ -1
867
+ * self.meta[ResourceMetaField.TIMEZONE]
868
+ * self.time_interval
869
+ )
820
870
  output = np.roll(output, n_roll)
821
871
 
822
872
  self.outputs[key] = output
@@ -861,9 +911,43 @@ class RevPySam(Sam):
861
911
  try:
862
912
  self.pysam.execute()
863
913
  except Exception as e:
864
- msg = ('PySAM raised an error while executing: "{}"'
865
- .format(self.module))
914
+ msg = 'PySAM raised an error while executing: "{}"'.format(
915
+ self.module
916
+ )
866
917
  if self.site is not None:
867
- msg += ' for site {}'.format(self.site)
918
+ msg += " for site {}".format(self.site)
868
919
  logger.exception(msg)
869
920
  raise SAMExecutionError(msg) from e
921
+
922
+
923
+ def _add_cost_defaults(sam_inputs):
924
+ """Add default values for required cost outputs if they are missing. """
925
+ sam_inputs.setdefault("fixed_charge_rate", None)
926
+
927
+ reg_mult = sam_inputs.setdefault("capital_cost_multiplier", 1)
928
+ capital_cost = sam_inputs.setdefault("capital_cost", None)
929
+ fixed_operating_cost = sam_inputs.setdefault("fixed_operating_cost", None)
930
+ variable_operating_cost = sam_inputs.setdefault(
931
+ "variable_operating_cost", None)
932
+
933
+ sam_inputs["base_capital_cost"] = capital_cost
934
+ sam_inputs["base_fixed_operating_cost"] = fixed_operating_cost
935
+ sam_inputs["base_variable_operating_cost"] = variable_operating_cost
936
+ if capital_cost is not None:
937
+ sam_inputs["capital_cost"] = capital_cost * reg_mult
938
+ else:
939
+ sam_inputs["capital_cost"] = None
940
+
941
+
942
+ def _add_sys_capacity(sam_inputs):
943
+ """Add system capacity SAM input if it is missing. """
944
+ cap = sam_inputs.get("system_capacity")
945
+ if cap is None:
946
+ cap = sam_inputs.get("turbine_capacity")
947
+
948
+ if cap is None:
949
+ cap = sam_inputs.get("wind_turbine_powercurve_powerout")
950
+ if cap is not None:
951
+ cap = max(cap)
952
+
953
+ sam_inputs["system_capacity"] = cap