NREL-reV 0.8.7__py3-none-any.whl → 0.8.9__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.8.9.dist-info}/METADATA +12 -10
- {NREL_reV-0.8.7.dist-info → NREL_reV-0.8.9.dist-info}/RECORD +38 -38
- {NREL_reV-0.8.7.dist-info → NREL_reV-0.8.9.dist-info}/WHEEL +1 -1
- reV/SAM/SAM.py +182 -133
- reV/SAM/econ.py +18 -14
- reV/SAM/generation.py +608 -419
- reV/SAM/windbos.py +93 -79
- reV/bespoke/bespoke.py +690 -445
- reV/bespoke/place_turbines.py +6 -6
- reV/config/project_points.py +220 -140
- reV/econ/econ.py +165 -113
- reV/econ/economies_of_scale.py +57 -34
- reV/generation/base.py +310 -183
- reV/generation/generation.py +298 -190
- reV/handlers/exclusions.py +16 -15
- reV/handlers/multi_year.py +12 -9
- reV/handlers/outputs.py +6 -5
- reV/hybrids/hybrid_methods.py +28 -30
- reV/hybrids/hybrids.py +304 -188
- 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 +536 -309
- reV/supply_curve/sc_aggregation.py +366 -225
- reV/supply_curve/supply_curve.py +505 -308
- reV/supply_curve/tech_mapping.py +144 -82
- reV/utilities/__init__.py +199 -16
- reV/utilities/pytest_utils.py +8 -4
- reV/version.py +1 -1
- {NREL_reV-0.8.7.dist-info → NREL_reV-0.8.9.dist-info}/LICENSE +0 -0
- {NREL_reV-0.8.7.dist-info → NREL_reV-0.8.9.dist-info}/entry_points.txt +0 -0
- {NREL_reV-0.8.7.dist-info → NREL_reV-0.8.9.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
|
-
|
15
|
-
from reV.utilities.exceptions import (SAMInputWarning, SAMInputError,
|
16
|
-
SAMExecutionError, ResourceError)
|
17
12
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
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 = {
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
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 = (
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
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
|
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(
|
117
|
-
|
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[
|
150
|
-
kwargs[
|
151
|
-
kwargs[
|
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 = (
|
160
|
-
|
161
|
-
|
162
|
-
|
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[
|
186
|
+
kwargs["downscale"] = downscale
|
168
187
|
|
169
188
|
elif res_handler == WindResource:
|
170
|
-
args += (project_points.h,
|
171
|
-
kwargs[
|
172
|
-
if
|
173
|
-
|
174
|
-
|
175
|
-
|
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(
|
182
|
-
kwargs[
|
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(
|
222
|
-
|
223
|
-
|
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(
|
284
|
-
|
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(
|
289
|
-
|
290
|
-
|
318
|
+
res_handler, kwargs, res_file = cls._multi_file_mods(
|
319
|
+
res_handler, kwargs, res_file
|
320
|
+
)
|
291
321
|
else:
|
292
|
-
kwargs[
|
322
|
+
kwargs["hsds"] = hsds
|
293
323
|
|
294
|
-
kwargs[
|
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[
|
301
|
-
kwargs[
|
302
|
-
res = MultiResolutionResource.preload_SAM(
|
303
|
-
|
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 = [
|
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
|
326
|
-
self[
|
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 = (
|
363
|
-
|
364
|
-
|
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
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
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(
|
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 = {
|
413
|
-
|
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() !=
|
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 = [
|
478
|
-
|
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
|
510
|
-
key = key.replace(
|
547
|
+
if "." in key:
|
548
|
+
key = key.replace(".", "_")
|
511
549
|
|
512
|
-
if
|
513
|
-
key = key.replace(
|
550
|
+
if ":constant" in key and "adjust:" in key:
|
551
|
+
key = key.replace("adjust:", "")
|
514
552
|
|
515
|
-
if isinstance(value, str) and
|
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 = (
|
520
|
-
|
521
|
-
|
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 =
|
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__(
|
557
|
-
|
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
|
-
|
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
|
@@ -623,11 +663,13 @@ class RevPySam(Sam):
|
|
623
663
|
Resource dataframe with all February 29th timesteps removed.
|
624
664
|
"""
|
625
665
|
|
626
|
-
if hasattr(resource,
|
627
|
-
if
|
628
|
-
|
629
|
-
|
630
|
-
|
666
|
+
if hasattr(resource, "index"):
|
667
|
+
if hasattr(resource.index, "month") and hasattr(
|
668
|
+
resource.index, "day"
|
669
|
+
):
|
670
|
+
leap_day = (resource.index.month == 2) & (
|
671
|
+
resource.index.day == 29
|
672
|
+
)
|
631
673
|
resource = resource.drop(resource.index[leap_day])
|
632
674
|
|
633
675
|
return resource
|
@@ -651,13 +693,15 @@ class RevPySam(Sam):
|
|
651
693
|
arr : ndarray
|
652
694
|
Truncated array of data such that there are 365 days
|
653
695
|
"""
|
654
|
-
msg = (
|
655
|
-
|
656
|
-
|
696
|
+
msg = (
|
697
|
+
"A valid time_index must be supplied to ensure the proper "
|
698
|
+
"resource length! Instead {} was supplied".format(type(time_index))
|
699
|
+
)
|
657
700
|
assert isinstance(time_index, pd.DatetimeIndex)
|
658
701
|
|
659
|
-
msg =
|
660
|
-
|
702
|
+
msg = "arr length {} does not match time_index length {}!".format(
|
703
|
+
len(arr), len(time_index)
|
704
|
+
)
|
661
705
|
assert len(arr) == len(time_index)
|
662
706
|
|
663
707
|
if time_index.is_leap_year.all():
|
@@ -669,7 +713,7 @@ class RevPySam(Sam):
|
|
669
713
|
s = np.where(mask)[0][-1]
|
670
714
|
|
671
715
|
freq = pd.infer_freq(time_index[:s])
|
672
|
-
msg =
|
716
|
+
msg = "frequencies do not match before and after 2/29"
|
673
717
|
assert freq == pd.infer_freq(time_index[s + 1:]), msg
|
674
718
|
else:
|
675
719
|
freq = pd.infer_freq(time_index)
|
@@ -677,8 +721,10 @@ class RevPySam(Sam):
|
|
677
721
|
freq = pd.infer_freq(time_index)
|
678
722
|
|
679
723
|
if freq is None:
|
680
|
-
msg = (
|
681
|
-
|
724
|
+
msg = (
|
725
|
+
"Resource time_index does not have a consistent time-step "
|
726
|
+
"(frequency)!"
|
727
|
+
)
|
682
728
|
logger.error(msg)
|
683
729
|
raise ResourceError(msg)
|
684
730
|
|
@@ -696,7 +742,7 @@ class RevPySam(Sam):
|
|
696
742
|
@staticmethod
|
697
743
|
def make_datetime(series):
|
698
744
|
"""Ensure that pd series is a datetime series with dt accessor"""
|
699
|
-
if not hasattr(series,
|
745
|
+
if not hasattr(series, "dt"):
|
700
746
|
series = pd.to_datetime(pd.Series(series))
|
701
747
|
|
702
748
|
return series
|
@@ -727,7 +773,7 @@ class RevPySam(Sam):
|
|
727
773
|
if t == 1.0:
|
728
774
|
time_interval += 1
|
729
775
|
break
|
730
|
-
|
776
|
+
if t == 0.0:
|
731
777
|
time_interval += 1
|
732
778
|
|
733
779
|
return int(time_interval)
|
@@ -751,10 +797,11 @@ class RevPySam(Sam):
|
|
751
797
|
location. Should include values for latitude, longitude, elevation,
|
752
798
|
and timezone. Can be None for econ runs.
|
753
799
|
"""
|
754
|
-
|
755
800
|
if isinstance(meta, pd.DataFrame):
|
756
|
-
msg = (
|
757
|
-
|
801
|
+
msg = (
|
802
|
+
"Meta data must only be for a single site but received: "
|
803
|
+
f"{meta}"
|
804
|
+
)
|
758
805
|
assert len(meta) == 1, msg
|
759
806
|
meta = meta.iloc[0]
|
760
807
|
|
@@ -785,22 +832,20 @@ class RevPySam(Sam):
|
|
785
832
|
"""Returns true if SAM data is array-like. False if scalar."""
|
786
833
|
if isinstance(val, (int, float, str)):
|
787
834
|
return False
|
835
|
+
try:
|
836
|
+
len(val)
|
837
|
+
except TypeError:
|
838
|
+
return False
|
788
839
|
else:
|
789
|
-
|
790
|
-
len(val)
|
791
|
-
except TypeError:
|
792
|
-
return False
|
793
|
-
else:
|
794
|
-
return True
|
840
|
+
return True
|
795
841
|
|
796
842
|
@classmethod
|
797
843
|
def _is_hourly(cls, val):
|
798
844
|
"""Returns true if SAM data is hourly or sub-hourly. False otherise."""
|
799
845
|
if not cls._is_arr_like(val):
|
800
846
|
return False
|
801
|
-
|
802
|
-
|
803
|
-
return L >= 8760
|
847
|
+
L = len(val)
|
848
|
+
return L >= 8760
|
804
849
|
|
805
850
|
def outputs_to_utc_arr(self):
|
806
851
|
"""Convert array-like SAM outputs to UTC np.ndarrays"""
|
@@ -815,8 +860,11 @@ class RevPySam(Sam):
|
|
815
860
|
output = output.astype(np.int32)
|
816
861
|
|
817
862
|
if self._is_hourly(output):
|
818
|
-
n_roll = int(
|
819
|
-
|
863
|
+
n_roll = int(
|
864
|
+
-1
|
865
|
+
* self.meta[ResourceMetaField.TIMEZONE]
|
866
|
+
* self.time_interval
|
867
|
+
)
|
820
868
|
output = np.roll(output, n_roll)
|
821
869
|
|
822
870
|
self.outputs[key] = output
|
@@ -861,9 +909,10 @@ class RevPySam(Sam):
|
|
861
909
|
try:
|
862
910
|
self.pysam.execute()
|
863
911
|
except Exception as e:
|
864
|
-
msg =
|
865
|
-
|
912
|
+
msg = 'PySAM raised an error while executing: "{}"'.format(
|
913
|
+
self.module
|
914
|
+
)
|
866
915
|
if self.site is not None:
|
867
|
-
msg +=
|
916
|
+
msg += " for site {}".format(self.site)
|
868
917
|
logger.exception(msg)
|
869
918
|
raise SAMExecutionError(msg) from e
|
reV/SAM/econ.py
CHANGED
@@ -4,24 +4,27 @@
|
|
4
4
|
Wraps the NREL-PySAM lcoefcr and singleowner modules with
|
5
5
|
additional reV features.
|
6
6
|
"""
|
7
|
-
from copy import deepcopy
|
8
7
|
import logging
|
9
|
-
|
8
|
+
from copy import deepcopy
|
10
9
|
from warnings import warn
|
10
|
+
|
11
|
+
import numpy as np
|
11
12
|
import PySAM.Lcoefcr as PySamLCOE
|
12
13
|
import PySAM.Singleowner as PySamSingleOwner
|
13
14
|
|
14
|
-
from reV.SAM.defaults import DefaultSingleOwner, DefaultLCOE
|
15
15
|
from reV.handlers.outputs import Outputs
|
16
|
-
from reV.SAM.
|
16
|
+
from reV.SAM.defaults import DefaultLCOE, DefaultSingleOwner
|
17
17
|
from reV.SAM.SAM import RevPySam
|
18
|
+
from reV.SAM.windbos import WindBos
|
18
19
|
from reV.utilities.exceptions import SAMExecutionError
|
20
|
+
from reV.utilities import ResourceMetaField
|
19
21
|
|
20
22
|
logger = logging.getLogger(__name__)
|
21
23
|
|
22
24
|
|
23
25
|
class Economic(RevPySam):
|
24
26
|
"""Base class for SAM economic models."""
|
27
|
+
|
25
28
|
MODULE = None
|
26
29
|
|
27
30
|
def __init__(self, sam_sys_inputs, site_sys_inputs=None,
|
@@ -172,7 +175,7 @@ class Economic(RevPySam):
|
|
172
175
|
with Outputs(cf_file) as cfh:
|
173
176
|
|
174
177
|
# get the index location of the site in question
|
175
|
-
site_gids = list(cfh.get_meta_arr(
|
178
|
+
site_gids = list(cfh.get_meta_arr(ResourceMetaField.GID))
|
176
179
|
isites = [site_gids.index(s) for s in sites]
|
177
180
|
|
178
181
|
# look for the cf_profile dataset
|
@@ -335,6 +338,7 @@ class Economic(RevPySam):
|
|
335
338
|
class LCOE(Economic):
|
336
339
|
"""SAM LCOE model.
|
337
340
|
"""
|
341
|
+
|
338
342
|
MODULE = 'lcoefcr'
|
339
343
|
PYSAM = PySamLCOE
|
340
344
|
|
@@ -375,7 +379,7 @@ class LCOE(Economic):
|
|
375
379
|
|
376
380
|
# get the cf_file meta data gid's to use as indexing tools
|
377
381
|
with Outputs(cf_file) as cfh:
|
378
|
-
site_gids = list(cfh.meta[
|
382
|
+
site_gids = list(cfh.meta[ResourceMetaField.GID])
|
379
383
|
|
380
384
|
calc_aey = False
|
381
385
|
if 'annual_energy' not in site_df:
|
@@ -463,6 +467,7 @@ class LCOE(Economic):
|
|
463
467
|
class SingleOwner(Economic):
|
464
468
|
"""SAM single owner economic model.
|
465
469
|
"""
|
470
|
+
|
466
471
|
MODULE = 'singleowner'
|
467
472
|
PYSAM = PySamSingleOwner
|
468
473
|
|
@@ -497,14 +502,13 @@ class SingleOwner(Economic):
|
|
497
502
|
"""
|
498
503
|
|
499
504
|
outputs = {}
|
500
|
-
if inputs is not None
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
outputs = wb.output
|
505
|
+
if (inputs is not None
|
506
|
+
and 'total_installed_cost' in inputs
|
507
|
+
and isinstance(inputs['total_installed_cost'], str)
|
508
|
+
and inputs['total_installed_cost'].lower() == 'windbos'):
|
509
|
+
wb = WindBos(inputs)
|
510
|
+
inputs['total_installed_cost'] = wb.total_installed_cost
|
511
|
+
outputs = wb.output
|
508
512
|
return inputs, outputs
|
509
513
|
|
510
514
|
@staticmethod
|