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/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
|
-
|
16
|
-
|
17
|
-
|
18
|
-
from rex.multi_file_resource import (
|
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
|
@@ -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,
|
627
|
-
if
|
628
|
-
|
629
|
-
|
630
|
-
|
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 = (
|
655
|
-
|
656
|
-
|
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 =
|
660
|
-
|
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 =
|
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 = (
|
681
|
-
|
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,
|
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
|
-
|
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 = (
|
757
|
-
|
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
|
-
|
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
|
-
|
802
|
-
|
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(
|
819
|
-
|
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 =
|
865
|
-
|
914
|
+
msg = 'PySAM raised an error while executing: "{}"'.format(
|
915
|
+
self.module
|
916
|
+
)
|
866
917
|
if self.site is not None:
|
867
|
-
msg +=
|
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
|