NREL-reV 0.13.0__py3-none-any.whl → 0.14.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.13.0.dist-info → nrel_rev-0.14.0.dist-info}/METADATA +3 -6
- {nrel_rev-0.13.0.dist-info → nrel_rev-0.14.0.dist-info}/RECORD +17 -16
- {nrel_rev-0.13.0.dist-info → nrel_rev-0.14.0.dist-info}/WHEEL +1 -1
- reV/bespoke/bespoke.py +9 -9
- reV/cli.py +4 -2
- reV/econ/economies_of_scale.py +160 -61
- reV/generation/cli_gen.py +4 -2
- reV/losses/scheduled.py +18 -31
- reV/supply_curve/cli_tech_mapping.py +66 -0
- reV/supply_curve/points.py +111 -24
- reV/supply_curve/sc_aggregation.py +79 -14
- reV/supply_curve/tech_mapping.py +174 -214
- reV/utilities/__init__.py +9 -4
- reV/version.py +1 -1
- {nrel_rev-0.13.0.dist-info → nrel_rev-0.14.0.dist-info}/entry_points.txt +0 -0
- {nrel_rev-0.13.0.dist-info → nrel_rev-0.14.0.dist-info}/licenses/LICENSE +0 -0
- {nrel_rev-0.13.0.dist-info → nrel_rev-0.14.0.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: NREL-reV
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.14.0
|
4
4
|
Summary: National Renewable Energy Laboratory's (NREL's) Renewable Energy Potential(V) Model: reV
|
5
5
|
Author-email: Galen Maclaurin <galen.maclaurin@nrel.gov>
|
6
6
|
Maintainer-email: Grant Buster <gbuster@nrel.gov>, Paul Pinchuk <ppinchuk@nrel.gov>
|
@@ -22,8 +22,8 @@ Description-Content-Type: text/x-rst
|
|
22
22
|
License-File: LICENSE
|
23
23
|
Requires-Dist: NREL-gaps<0.9,>=0.8.0
|
24
24
|
Requires-Dist: NREL-NRWAL<0.1,>=0.0.11
|
25
|
-
Requires-Dist: NREL-PySAM~=
|
26
|
-
Requires-Dist: NREL-rex<0.4,>=0.3.
|
25
|
+
Requires-Dist: NREL-PySAM~=7.0.0
|
26
|
+
Requires-Dist: NREL-rex<0.4,>=0.3.2
|
27
27
|
Requires-Dist: numpy<3,>=2.0.2
|
28
28
|
Requires-Dist: packaging<25,>=24.2
|
29
29
|
Requires-Dist: plotly<7,>=6.0.1
|
@@ -156,9 +156,6 @@ Option 1: Install from PIP (recommended for analysts):
|
|
156
156
|
3. Install reV:
|
157
157
|
1) ``pip install NREL-reV`` or
|
158
158
|
|
159
|
-
- NOTE: If you install using conda and want to run from files directly on S3 like in the `running reV locally example <https://nrel.github.io/reV/misc/examples.running_locally.html>`_
|
160
|
-
you will also need to install S3 filesystem dependencies: ``pip install NREL-reV[s3]``
|
161
|
-
|
162
159
|
- NOTE: If you install using conda and want to use `HSDS <https://github.com/NREL/hsds-examples>`_
|
163
160
|
you will also need to install HSDS dependencies: ``pip install NREL-reV[hsds]``
|
164
161
|
|
@@ -1,7 +1,7 @@
|
|
1
|
-
nrel_rev-0.
|
1
|
+
nrel_rev-0.14.0.dist-info/licenses/LICENSE,sha256=hDwoTANtan2ZpufBlXm5C3W_PJ-mCqItvlcobgjxL7k,1526
|
2
2
|
reV/__init__.py,sha256=tXTpWu_qVo3uotfSw_TJ-gNbidGaIPPfUTwBlpCMJ-g,856
|
3
|
-
reV/cli.py,sha256=
|
4
|
-
reV/version.py,sha256=
|
3
|
+
reV/cli.py,sha256=u7G5M5moA7q8fCgC_1MB30Z7R14GNcngVf6eVNkfQU8,1682
|
4
|
+
reV/version.py,sha256=MW2Xrj6zWXWLE73quVP27irsAbTU85t5oflVIQzGMVE,51
|
5
5
|
reV/SAM/SAM.py,sha256=3NK9rRaJzqH6wz7CU_5XguKCRhmoilOpDdFFsFgOaxQ,33163
|
6
6
|
reV/SAM/__init__.py,sha256=LJqoncyKDY5ZP5WA4kboh561bce11F9Ge645Izah0EY,240
|
7
7
|
reV/SAM/defaults.py,sha256=JQMJomX7wsbMzxKjx_IMnA_9QFsV2yWCyl_JToDVSJo,6703
|
@@ -16,7 +16,7 @@ reV/SAM/defaults/WY Southern-Flat Lands.srw,sha256=oSlFI7nnycW7SMnTXEXBXYkoh8rhV
|
|
16
16
|
reV/SAM/defaults/geothermal.json,sha256=gnlxOBxxkbDDacIw6B9yP9iRkfmwj0WTWs8Zb71ytCo,7119
|
17
17
|
reV/SAM/defaults/i_pvwattsv5.json,sha256=sF8rSe1bcqsKLXchvRxlp25dXOVe_q7rPJTf_vWR20Y,312
|
18
18
|
reV/bespoke/__init__.py,sha256=vpXbyBUrUsTgK8UP_LafMjLiDg2CRG9WZLHPsOJoxek,109
|
19
|
-
reV/bespoke/bespoke.py,sha256=
|
19
|
+
reV/bespoke/bespoke.py,sha256=573mIoJx8HXq5RH8HufjIhX20XjMJZbbR1kDGKpMzSw,112723
|
20
20
|
reV/bespoke/cli_bespoke.py,sha256=b6Xu0GKpXqPX3qVJ6-z0FrO97uCsH_1dVOa4r6IvesQ,2911
|
21
21
|
reV/bespoke/gradient_free.py,sha256=URWV1yiO2jyWk3_GOpfpLV_wlgJhXXGmTUwCB3WTV0Y,12015
|
22
22
|
reV/bespoke/pack_turbs.py,sha256=Dcd9F8obF8LPztzeycB5kxa5hXKSCiz3jC1WeAFUx28,3508
|
@@ -33,11 +33,11 @@ reV/config/sam_config.py,sha256=xvvx2FTuliq0Sk-BjRE3I9zdDmIdwHVBnWtXCcsoc40,7998
|
|
33
33
|
reV/econ/__init__.py,sha256=UId1LNaAP9lErCEXVce6JZf0qVRUvwNFOPrajdRevGo,130
|
34
34
|
reV/econ/cli_econ.py,sha256=2KNy3JQD0EKjStaoD2r6nv3ELFw88h2E_up-UKj_sfE,4286
|
35
35
|
reV/econ/econ.py,sha256=edsNPZg2amhC_GZeotkY2MU3u17NgWeCpS6okZNrwAA,23637
|
36
|
-
reV/econ/economies_of_scale.py,sha256=
|
36
|
+
reV/econ/economies_of_scale.py,sha256=UXBmHFDK-pngDGaIoKe90TQgW2LlNKxcjMQHMxSxky8,15044
|
37
37
|
reV/econ/utilities.py,sha256=ZOA49S1jfsvdenvlL7m-BAJIrAvpAHSix5-wrSW0uLc,1232
|
38
38
|
reV/generation/__init__.py,sha256=LBecrbpL69tROol_OwVHTKBrTqgSSxJW59fs0k3o0jQ,75
|
39
39
|
reV/generation/base.py,sha256=D--WPQ8ZWElE06HtZF9rQYK1cAa4MWBDEqkOZ2hmFEE,49482
|
40
|
-
reV/generation/cli_gen.py,sha256=
|
40
|
+
reV/generation/cli_gen.py,sha256=JYcTX9bM1hb_HQX-9yUwvE5fXNxRMINttlGo4Pvo138,4530
|
41
41
|
reV/generation/generation.py,sha256=FrNN3p-WScRtnVHhaqxpTAgvVbE0cIF9_2Or_qDlJ8g,49029
|
42
42
|
reV/generation/output_attributes/generation.json,sha256=cy8efVCJgNSYCsR3x3_VpMCtynYS3oaQG4M2MwcdzFY,4954
|
43
43
|
reV/generation/output_attributes/lcoe_fcr.json,sha256=d_TGMkZsmsfDXQiWfhYjFkmwqi-wKF8JdHmiMCj2h38,136
|
@@ -61,7 +61,7 @@ reV/hybrids/hybrid_methods.py,sha256=5nGb2sadp8zT9atZJNVigpYm_JYJxt4gWCPo56-5Vjk
|
|
61
61
|
reV/hybrids/hybrids.py,sha256=7DFaqkNdAUVJxd_YDoQI-psKcJAlWgOGapYb_kIv3kw,46601
|
62
62
|
reV/losses/__init__.py,sha256=DMmJo9oOVrF1npUcjuSHo_yslf94am8xdvYrvwjEsjM,141
|
63
63
|
reV/losses/power_curve.py,sha256=P7xhRlTDqVoz5m3BJMwqYbrSKBkZfeWVEzFVmFARVGE,45289
|
64
|
-
reV/losses/scheduled.py,sha256=
|
64
|
+
reV/losses/scheduled.py,sha256=JKOVKh6jNOQx2Dj-uSPx1kpICcoHuYGPpievxkVqOQM,26878
|
65
65
|
reV/losses/utils.py,sha256=XyM38JVlBNad9BEMJqRLuK6xba_DfFZUxNSC7EcRelM,7511
|
66
66
|
reV/nrwal/__init__.py,sha256=HD-QXvKRM5s6Z24zfKw31yhBttrkTe8hzUnx0FZP_P0,114
|
67
67
|
reV/nrwal/cli_nrwal.py,sha256=sa2gI86sNGk3ZJuQPxy_xsxle4KWDyPNrM11hlq6Ask,1825
|
@@ -77,22 +77,23 @@ reV/supply_curve/__init__.py,sha256=dbf0cO0qmt1VhV8dnDddztrcpnwua9T2HBhM6tLKaP8,
|
|
77
77
|
reV/supply_curve/aggregation.py,sha256=dnnuDG9BEGEgY17uA8nSF3s6L6Q6Lxe94aS7AIKSlm0,40471
|
78
78
|
reV/supply_curve/cli_sc_aggregation.py,sha256=qGfJyZjg4tFARG2dl-wAlHv9KjEp2u4wg1Boxz18Gf0,3347
|
79
79
|
reV/supply_curve/cli_supply_curve.py,sha256=e-XrHQIe4OqWTL6u-TUAyHrw7Alk7vkXQ2HoLbE3zTM,2163
|
80
|
+
reV/supply_curve/cli_tech_mapping.py,sha256=ztNJhgk3WlSD5Gn7Rh3qBLMvX_pFkjuQT7qUK4s0P2w,1731
|
80
81
|
reV/supply_curve/competitive_wind_farms.py,sha256=eOjM72-4oWtsqxB7Wh2gnB2zVAt4LY3iPE_DqWdXbQ4,15795
|
81
82
|
reV/supply_curve/exclusions.py,sha256=4-ZxTO5Vlu03vie0V_74uvdajQfCuC8FE96Pg8I4U_c,42950
|
82
83
|
reV/supply_curve/extent.py,sha256=a31po753hXSxQ8lfcCvpE8hoKc4bY7MmYq0NO0jtdqA,17414
|
83
|
-
reV/supply_curve/points.py,sha256=
|
84
|
-
reV/supply_curve/sc_aggregation.py,sha256=
|
84
|
+
reV/supply_curve/points.py,sha256=tWL_9hO5xBzDygk9TfDGotSTOgLNp9hEY7-2DJ01TrI,93336
|
85
|
+
reV/supply_curve/sc_aggregation.py,sha256=0rxNPJgaK1eilSeRVF-IBRVAqE0UcM8gKo_SkLHdZl0,67738
|
85
86
|
reV/supply_curve/supply_curve.py,sha256=9zhAA_9XSxE18j1Z9FuC71Wr3I0VuakfR5mt1_gHYuU,69931
|
86
|
-
reV/supply_curve/tech_mapping.py,sha256=
|
87
|
-
reV/utilities/__init__.py,sha256=
|
87
|
+
reV/supply_curve/tech_mapping.py,sha256=JAinwbh6UPzBX2qwf0EBDDYCanEfe-F6RgiE23oNBcU,18149
|
88
|
+
reV/utilities/__init__.py,sha256=HVb1P-ee-z2P6UTjyc0s0gYFK7XYM71BQLEMxj26apA,10834
|
88
89
|
reV/utilities/_clean_readme.py,sha256=IFI9wGPX5nnLTNVLJzH8IOHq9unQlAlHRu4Namib0LA,709
|
89
90
|
reV/utilities/cli_functions.py,sha256=1_T_sXz0Ct8lW-vOk3mMRcpD6NYsc9cGI7dEujIi9z4,3864
|
90
91
|
reV/utilities/curtailment.py,sha256=As902-2aLGnCiVEutYfAFIOwuV--_rCQhxGNOY9RB-4,5241
|
91
92
|
reV/utilities/exceptions.py,sha256=f7sRGsbFLpmL6Caq_H1cD4GfVhnLMyvYUsLPA1UVDDE,3974
|
92
93
|
reV/utilities/pytest_utils.py,sha256=spCw9yQ8KEYOkQZpCi9IEmaWIvIqHqbUPDXXNQJJ68U,3241
|
93
94
|
reV/utilities/slots.py,sha256=xsw-JuUVZ0YeoCNuwP_HxGNxFMA4xRs1tuImXHIJqaU,2618
|
94
|
-
nrel_rev-0.
|
95
|
-
nrel_rev-0.
|
96
|
-
nrel_rev-0.
|
97
|
-
nrel_rev-0.
|
98
|
-
nrel_rev-0.
|
95
|
+
nrel_rev-0.14.0.dist-info/METADATA,sha256=wZN_Wb9S2ZCiKDgYXrPAmEiywSA_-1o12-anLqwQvHs,10733
|
96
|
+
nrel_rev-0.14.0.dist-info/WHEEL,sha256=0CuiUZ_p9E4cD6NyLD6UG80LBXYyiSYZOKDm5lp32xk,91
|
97
|
+
nrel_rev-0.14.0.dist-info/entry_points.txt,sha256=4IfJtZm2iMJwrbC8J0Or7VjZWnFpvCaHYVpvSWfIwDA,616
|
98
|
+
nrel_rev-0.14.0.dist-info/top_level.txt,sha256=S6YF2ZYgXUB6n28SY0K2H8YB9tMJdXQ9CyQbo6VC89M,4
|
99
|
+
nrel_rev-0.14.0.dist-info/RECORD,,
|
reV/bespoke/bespoke.py
CHANGED
@@ -1087,8 +1087,8 @@ class BespokeSinglePlant:
|
|
1087
1087
|
fcr = lcoe_kwargs['fixed_charge_rate']
|
1088
1088
|
cc = lcoe_kwargs['capital_cost']
|
1089
1089
|
foc = lcoe_kwargs['fixed_operating_cost']
|
1090
|
-
voc = lcoe_kwargs['variable_operating_cost']
|
1091
|
-
aep = self.outputs['annual_energy-means']
|
1090
|
+
voc = lcoe_kwargs['variable_operating_cost'] # $/kWh
|
1091
|
+
aep = self.outputs['annual_energy-means'] # kWh
|
1092
1092
|
|
1093
1093
|
my_mean_lcoe = lcoe_fcr(fcr, cc, foc, aep, voc)
|
1094
1094
|
|
@@ -1184,6 +1184,7 @@ class BespokeSinglePlant:
|
|
1184
1184
|
'wind_resource_data',
|
1185
1185
|
'wind_turbine_powercurve_powerout',
|
1186
1186
|
'adjust_hourly',
|
1187
|
+
'adjust_timeindex',
|
1187
1188
|
'capital_cost',
|
1188
1189
|
'fixed_operating_cost',
|
1189
1190
|
'variable_operating_cost',
|
@@ -1387,12 +1388,12 @@ class BespokeSinglePlant:
|
|
1387
1388
|
self._meta[SupplyCurveField.EOS_MULT] = eos_mult
|
1388
1389
|
self._meta[SupplyCurveField.REG_MULT] = reg_mult_cc
|
1389
1390
|
|
1390
|
-
self._meta[SupplyCurveField.
|
1391
|
+
self._meta[SupplyCurveField.COST_SITE_CC_USD_PER_AC_MW] = (
|
1391
1392
|
(self.plant_optimizer.capital_cost
|
1392
1393
|
+ self.plant_optimizer.balance_of_system_cost)
|
1393
1394
|
/ capacity_ac_mw
|
1394
1395
|
)
|
1395
|
-
self._meta[SupplyCurveField.
|
1396
|
+
self._meta[SupplyCurveField.COST_BASE_CC_USD_PER_AC_MW] = (
|
1396
1397
|
(self.plant_optimizer.capital_cost / eos_mult / reg_mult_cc
|
1397
1398
|
+ self.plant_optimizer.balance_of_system_cost / reg_mult_bos)
|
1398
1399
|
/ capacity_ac_mw
|
@@ -1406,14 +1407,13 @@ class BespokeSinglePlant:
|
|
1406
1407
|
/ reg_mult_foc
|
1407
1408
|
/ capacity_ac_mw
|
1408
1409
|
)
|
1409
|
-
self._meta[SupplyCurveField.
|
1410
|
-
self.plant_optimizer.variable_operating_cost
|
1411
|
-
/ capacity_ac_mw
|
1410
|
+
self._meta[SupplyCurveField.COST_SITE_VOC_USD_PER_AC_MWH] = (
|
1411
|
+
self.plant_optimizer.variable_operating_cost * 1000 # to $/MWh
|
1412
1412
|
)
|
1413
|
-
self._meta[SupplyCurveField.
|
1413
|
+
self._meta[SupplyCurveField.COST_BASE_VOC_USD_PER_AC_MWH] = (
|
1414
1414
|
self.plant_optimizer.variable_operating_cost
|
1415
1415
|
/ reg_mult_voc
|
1416
|
-
|
1416
|
+
* 1000 # to $/MWh
|
1417
1417
|
)
|
1418
1418
|
self._meta[SupplyCurveField.FIXED_CHARGE_RATE] = (
|
1419
1419
|
self.plant_optimizer.fixed_charge_rate
|
reV/cli.py
CHANGED
@@ -12,6 +12,7 @@ from reV.handlers.cli_collect import collect_command
|
|
12
12
|
from reV.handlers.cli_multi_year import my_command
|
13
13
|
from reV.supply_curve.cli_sc_aggregation import sc_agg_command
|
14
14
|
from reV.supply_curve.cli_supply_curve import sc_command
|
15
|
+
from reV.supply_curve.cli_tech_mapping import tm_command
|
15
16
|
from reV.rep_profiles.cli_rep_profiles import rep_profiles_command
|
16
17
|
from reV.hybrids.cli_hybrids import hybrids_command
|
17
18
|
from reV.nrwal.cli_nrwal import nrwal_command
|
@@ -24,8 +25,9 @@ logger = logging.getLogger(__name__)
|
|
24
25
|
|
25
26
|
|
26
27
|
commands = [bespoke_command, gen_command, econ_command, collect_command,
|
27
|
-
my_command, sc_agg_command, sc_command,
|
28
|
-
hybrids_command, nrwal_command,
|
28
|
+
my_command, tm_command, sc_agg_command, sc_command,
|
29
|
+
rep_profiles_command, hybrids_command, nrwal_command,
|
30
|
+
qa_qc_command]
|
29
31
|
main = make_cli(commands, info={"name": "reV", "version": __version__})
|
30
32
|
main.add_command(qa_qc_extra)
|
31
33
|
main.add_command(project_points)
|
reV/econ/economies_of_scale.py
CHANGED
@@ -34,31 +34,54 @@ class EconomiesOfScale:
|
|
34
34
|
lcoe : $/MWh
|
35
35
|
"""
|
36
36
|
|
37
|
-
def __init__(self,
|
37
|
+
def __init__(self, data, cap_eqn=None, fixed_eqn=None, var_eqn=None):
|
38
38
|
"""
|
39
|
+
|
39
40
|
Parameters
|
40
41
|
----------
|
41
|
-
eqn : str
|
42
|
-
LCOE scaling equation to implement "economies of scale".
|
43
|
-
Equation must be in python string format and return a scalar
|
44
|
-
value to multiply the capital cost by. Independent variables in
|
45
|
-
the equation should match the keys in the data input arg. This
|
46
|
-
equation may use numpy functions with the package prefix "np".
|
47
42
|
data : dict | pd.DataFrame
|
48
43
|
Namespace of econ data to use to calculate economies of scale. Keys
|
49
44
|
in dict or column labels in dataframe should match the Independent
|
50
45
|
variables in the eqn input. Should also include variables required
|
51
46
|
to calculate LCOE.
|
47
|
+
cap_eqn : str, optional
|
48
|
+
LCOE scaling equation to implement "economies of scale".
|
49
|
+
Equation must be in python string format and return a scalar
|
50
|
+
value to multiply the capital cost by. Independent variables in
|
51
|
+
the equation should match the keys in the data input arg. This
|
52
|
+
equation may use numpy functions with the package prefix "np". If
|
53
|
+
``None``, no economies of scale are applied to the capital cost.
|
54
|
+
By default, ``None``.
|
55
|
+
fixed_eqn : str, optional
|
56
|
+
LCOE scaling equation to implement "economies of scale".
|
57
|
+
Equation must be in python string format and return a scalar
|
58
|
+
value to multiply the fixed operating cost by. Independent
|
59
|
+
variables in the equation should match the keys in the data input
|
60
|
+
arg. This equation may use numpy functions with the package prefix
|
61
|
+
"np". If ``None``, no economies of scale are applied to the
|
62
|
+
fixed operating cost. By default, ``None``.
|
63
|
+
var_eqn : str, optional
|
64
|
+
LCOE scaling equation to implement "economies of scale".
|
65
|
+
Equation must be in python string format and return a scalar
|
66
|
+
value to multiply the variable operating cost by. Independent
|
67
|
+
variables in the equation should match the keys in the data input
|
68
|
+
arg. This equation may use numpy functions with the package prefix
|
69
|
+
"np". If ``None``, no economies of scale are applied to the
|
70
|
+
variable operating cost. By default, ``None``.
|
52
71
|
"""
|
53
|
-
self._eqn = eqn
|
54
72
|
self._data = data
|
73
|
+
self._cap_eqn = cap_eqn
|
74
|
+
self._fixed_eqn = fixed_eqn
|
75
|
+
self._var_eqn = var_eqn
|
76
|
+
self._vars = None
|
55
77
|
self._preflight()
|
56
78
|
|
57
79
|
def _preflight(self):
|
58
80
|
"""Run checks to validate EconomiesOfScale equation and input data."""
|
59
81
|
|
60
|
-
|
61
|
-
|
82
|
+
for eq in self._all_equations:
|
83
|
+
if eq is not None:
|
84
|
+
check_eval_str(str(eq))
|
62
85
|
|
63
86
|
if isinstance(self._data, pd.DataFrame):
|
64
87
|
self._data = {
|
@@ -79,11 +102,16 @@ class EconomiesOfScale:
|
|
79
102
|
if any(missing):
|
80
103
|
e = (
|
81
104
|
"Cannot evaluate EconomiesOfScale, missing data for variables"
|
82
|
-
": {} for equation: {}".format(missing, self.
|
105
|
+
": {} for equation: {}".format(missing, self._cap_eqn)
|
83
106
|
)
|
84
107
|
logger.error(e)
|
85
108
|
raise KeyError(e)
|
86
109
|
|
110
|
+
@property
|
111
|
+
def _all_equations(self):
|
112
|
+
"""gen: All EOS equations"""
|
113
|
+
yield from (self._cap_eqn, self._fixed_eqn, self._var_eqn)
|
114
|
+
|
87
115
|
@staticmethod
|
88
116
|
def is_num(s):
|
89
117
|
"""Check if a string is a number"""
|
@@ -111,36 +139,51 @@ class EconomiesOfScale:
|
|
111
139
|
the equation string. This will return an empty list if the equation
|
112
140
|
has no variables.
|
113
141
|
"""
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
142
|
+
if self._vars is not None:
|
143
|
+
return self._vars
|
144
|
+
|
145
|
+
self._vars = []
|
146
|
+
for eq in self._all_equations:
|
147
|
+
if eq is None:
|
148
|
+
continue
|
149
|
+
|
150
|
+
delimiters = (">", "<", ">=", "<=", "==", ",", "*", "/", "+",
|
151
|
+
"-", " ", "(", ")", "[", "]")
|
118
152
|
regex_pattern = "|".join(map(re.escape, delimiters))
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
153
|
+
for sub_str in re.split(regex_pattern, str(eq)):
|
154
|
+
is_valid_var_name = (sub_str and not self.is_num(sub_str)
|
155
|
+
and not self.is_method(sub_str))
|
156
|
+
if is_valid_var_name:
|
157
|
+
self._vars.append(sub_str)
|
124
158
|
|
125
|
-
|
159
|
+
self._vars = sorted(set(self._vars))
|
160
|
+
return self._vars
|
126
161
|
|
127
|
-
def _evaluate(self):
|
162
|
+
def _evaluate(self, eqn):
|
128
163
|
"""Evaluate the EconomiesOfScale equation with Independent variables
|
129
164
|
parsed into a kwargs dictionary input.
|
130
165
|
|
166
|
+
Parameters
|
167
|
+
----------
|
168
|
+
eqn : str
|
169
|
+
LCOE scaling equation to implement "economies of scale".
|
170
|
+
Equation must be in python string format and return a scalar
|
171
|
+
multiplier. Independent variables in the equation should match the
|
172
|
+
keys in the data input arg. This equation may use numpy functions
|
173
|
+
with the package prefix "np". If ``None``, this function returns
|
174
|
+
``1``.
|
175
|
+
|
131
176
|
Returns
|
132
177
|
-------
|
133
178
|
out : float | np.ndarray
|
134
|
-
Evaluated output of the EconomiesOfScale equation.
|
135
|
-
numeric scalars to apply directly to the capital cost.
|
179
|
+
Evaluated output of the EconomiesOfScale equation.
|
136
180
|
"""
|
137
|
-
|
138
|
-
|
139
|
-
kwargs = {k: self._data[k] for k in self.vars}
|
140
|
-
# pylint: disable=eval-used
|
141
|
-
out = eval(str(self._eqn), globals(), kwargs)
|
181
|
+
if eqn is None:
|
182
|
+
return 1
|
142
183
|
|
143
|
-
|
184
|
+
kwargs = {k: self._data[k] for k in self.vars}
|
185
|
+
# pylint: disable=eval-used
|
186
|
+
return eval(str(eqn), globals(), kwargs)
|
144
187
|
|
145
188
|
@staticmethod
|
146
189
|
def _get_prioritized_keys(input_dict, key_list):
|
@@ -180,8 +223,8 @@ class EconomiesOfScale:
|
|
180
223
|
|
181
224
|
@property
|
182
225
|
def capital_cost_scalar(self):
|
183
|
-
"""Evaluated output of the EconomiesOfScale equation.
|
184
|
-
numeric scalars to apply directly to the capital cost.
|
226
|
+
"""Evaluated output of the EconomiesOfScale capital cost equation.
|
227
|
+
Should be numeric scalars to apply directly to the capital cost.
|
185
228
|
|
186
229
|
Returns
|
187
230
|
-------
|
@@ -189,7 +232,35 @@ class EconomiesOfScale:
|
|
189
232
|
Evaluated output of the EconomiesOfScale equation. Should be
|
190
233
|
numeric scalars to apply directly to the capital cost.
|
191
234
|
"""
|
192
|
-
return self._evaluate()
|
235
|
+
return self._evaluate(self._cap_eqn)
|
236
|
+
|
237
|
+
@property
|
238
|
+
def fixed_operating_cost_scalar(self):
|
239
|
+
"""Evaluated output of the EconomiesOfScale fixed operating cost
|
240
|
+
equation. Should be numeric scalars to apply directly to the fixed
|
241
|
+
operating cost.
|
242
|
+
|
243
|
+
Returns
|
244
|
+
-------
|
245
|
+
out : float | np.ndarray
|
246
|
+
Evaluated output of the EconomiesOfScale equation. Should be
|
247
|
+
numeric scalars to apply directly to the fixed operating cost.
|
248
|
+
"""
|
249
|
+
return self._evaluate(self._fixed_eqn)
|
250
|
+
|
251
|
+
@property
|
252
|
+
def variable_operating_cost_scalar(self):
|
253
|
+
"""Evaluated output of the EconomiesOfScale equation variable
|
254
|
+
operating cost. Should be numeric scalars to apply directly to the
|
255
|
+
variable operating cost.
|
256
|
+
|
257
|
+
Returns
|
258
|
+
-------
|
259
|
+
out : float | np.ndarray
|
260
|
+
Evaluated output of the EconomiesOfScale equation. Should be
|
261
|
+
numeric scalars to apply directly to the variable operating cost.
|
262
|
+
"""
|
263
|
+
return self._evaluate(self._var_eqn)
|
193
264
|
|
194
265
|
def _cost_from_cap(self, col_name):
|
195
266
|
"""Get full cost value from cost per mw in data.
|
@@ -221,10 +292,10 @@ class EconomiesOfScale:
|
|
221
292
|
Returns
|
222
293
|
-------
|
223
294
|
out : float | np.ndarray
|
224
|
-
Unscaled (raw) capital_cost found in the data input arg.
|
295
|
+
Unscaled (raw) capital_cost ($) found in the data input arg.
|
225
296
|
"""
|
226
297
|
raw_capital_cost_from_cap = self._cost_from_cap(
|
227
|
-
SupplyCurveField.
|
298
|
+
SupplyCurveField.COST_SITE_CC_USD_PER_AC_MW
|
228
299
|
)
|
229
300
|
if raw_capital_cost_from_cap is not None:
|
230
301
|
return raw_capital_cost_from_cap
|
@@ -240,8 +311,8 @@ class EconomiesOfScale:
|
|
240
311
|
Returns
|
241
312
|
-------
|
242
313
|
out : float | np.ndarray
|
243
|
-
Capital cost found in the data input arg scaled by the
|
244
|
-
EconomiesOfScale equation.
|
314
|
+
Capital cost ($) found in the data input arg scaled by the
|
315
|
+
evaluated EconomiesOfScale equation.
|
245
316
|
"""
|
246
317
|
cc = copy.deepcopy(self.raw_capital_cost)
|
247
318
|
cc *= self.capital_cost_scalar
|
@@ -265,13 +336,13 @@ class EconomiesOfScale:
|
|
265
336
|
return self._get_prioritized_keys(self._data, key_list)
|
266
337
|
|
267
338
|
@property
|
268
|
-
def
|
269
|
-
"""
|
339
|
+
def raw_fixed_operating_cost(self):
|
340
|
+
"""Unscaled (raw) fixed operating cost from input data arg
|
270
341
|
|
271
342
|
Returns
|
272
343
|
-------
|
273
344
|
out : float | np.ndarray
|
274
|
-
|
345
|
+
Unscaled (raw) fixed operating cost ($/year) from input data arg
|
275
346
|
"""
|
276
347
|
foc_from_cap = self._cost_from_cap(
|
277
348
|
SupplyCurveField.COST_SITE_FOC_USD_PER_AC_MW
|
@@ -284,42 +355,70 @@ class EconomiesOfScale:
|
|
284
355
|
return self._get_prioritized_keys(self._data, key_list)
|
285
356
|
|
286
357
|
@property
|
287
|
-
def
|
288
|
-
"""
|
358
|
+
def scaled_fixed_operating_cost(self):
|
359
|
+
"""Fixed operating cost found in the data input arg scaled by the
|
360
|
+
evaluated EconomiesOfScale input equation.
|
289
361
|
|
290
362
|
Returns
|
291
363
|
-------
|
292
364
|
out : float | np.ndarray
|
293
|
-
|
365
|
+
Fixed operating cost ($/year) found in the data input arg scaled
|
366
|
+
by the evaluated EconomiesOfScale equation.
|
294
367
|
"""
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
368
|
+
foc = copy.deepcopy(self.raw_fixed_operating_cost)
|
369
|
+
foc *= self.fixed_operating_cost_scalar
|
370
|
+
return foc
|
371
|
+
|
372
|
+
@property
|
373
|
+
def raw_variable_operating_cost(self):
|
374
|
+
"""Unscaled (raw) variable operating cost from input data arg
|
375
|
+
|
376
|
+
Returns
|
377
|
+
-------
|
378
|
+
out : float | np.ndarray
|
379
|
+
Unscaled (raw) variable operating cost ($/kWh) from input
|
380
|
+
data arg
|
381
|
+
"""
|
382
|
+
voc_mwh = self._data.get(SupplyCurveField.COST_SITE_VOC_USD_PER_AC_MWH)
|
383
|
+
if voc_mwh is not None:
|
384
|
+
return voc_mwh / 1000 # convert to $/kWh
|
300
385
|
|
301
386
|
key_list = ["variable_operating_cost", "mean_variable_operating_cost",
|
302
387
|
"voc", "mean_voc"]
|
303
388
|
return self._get_prioritized_keys(self._data, key_list)
|
304
389
|
|
390
|
+
@property
|
391
|
+
def scaled_variable_operating_cost(self):
|
392
|
+
"""Variable operating cost found in the data input arg scaled by the
|
393
|
+
evaluated EconomiesOfScale input equation.
|
394
|
+
|
395
|
+
Returns
|
396
|
+
-------
|
397
|
+
out : float | np.ndarray
|
398
|
+
Variable operating cost ($/kWh) found in the data input arg
|
399
|
+
scaled by the evaluated EconomiesOfScale equation.
|
400
|
+
"""
|
401
|
+
voc = copy.deepcopy(self.raw_variable_operating_cost)
|
402
|
+
voc *= self.variable_operating_cost_scalar
|
403
|
+
return voc
|
404
|
+
|
305
405
|
@property
|
306
406
|
def aep(self):
|
307
|
-
"""Annual energy production back-calculated from the raw LCOE:
|
407
|
+
"""Annual energy production (kWh) back-calculated from the raw LCOE:
|
308
408
|
|
309
|
-
AEP = (fcr * raw_cap_cost +
|
409
|
+
AEP = (fcr * raw_cap_cost + raw_foc) / (raw_lcoe - raw_voc)
|
310
410
|
|
311
411
|
Returns
|
312
412
|
-------
|
313
413
|
out : float | np.ndarray
|
314
414
|
"""
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
return aep
|
415
|
+
num = self.fcr * self.raw_capital_cost + self.raw_fixed_operating_cost
|
416
|
+
denom = self.raw_lcoe - (self.raw_variable_operating_cost * 1000)
|
417
|
+
return num / denom * 1000 # convert MWh to KWh
|
319
418
|
|
320
419
|
@property
|
321
420
|
def raw_lcoe(self):
|
322
|
-
"""Raw LCOE taken from the input data
|
421
|
+
"""Raw LCOE ($/MWh) taken from the input data
|
323
422
|
|
324
423
|
Returns
|
325
424
|
-------
|
@@ -330,17 +429,17 @@ class EconomiesOfScale:
|
|
330
429
|
|
331
430
|
@property
|
332
431
|
def scaled_lcoe(self):
|
333
|
-
"""LCOE calculated with the scaled
|
432
|
+
"""LCOE ($/MWh) calculated with the scaled costs based on the
|
334
433
|
EconomiesOfScale input equation.
|
335
434
|
|
336
|
-
LCOE = (FCR * scaled_capital_cost +
|
435
|
+
LCOE = (FCR * scaled_capital_cost + scaled_FOC) / AEP + scaled_VOC
|
337
436
|
|
338
437
|
Returns
|
339
438
|
-------
|
340
439
|
lcoe : float | np.ndarray
|
341
|
-
LCOE calculated with the scaled
|
440
|
+
LCOE calculated with the scaled costs based on the
|
342
441
|
EconomiesOfScale input equation.
|
343
442
|
"""
|
344
|
-
return lcoe_fcr(
|
345
|
-
|
346
|
-
|
443
|
+
return lcoe_fcr(self.fcr, self.scaled_capital_cost,
|
444
|
+
self.scaled_fixed_operating_cost, self.aep,
|
445
|
+
self.scaled_variable_operating_cost)
|
reV/generation/cli_gen.py
CHANGED
@@ -67,8 +67,10 @@ def _parse_res_files(res_fps, analysis_years):
|
|
67
67
|
|
68
68
|
# get base filename, may have {} for year format
|
69
69
|
if isinstance(res_fps, str) and '{}' in res_fps:
|
70
|
-
#
|
71
|
-
|
70
|
+
# make list of res files for each year
|
71
|
+
# .replace("{}", "{0}") used for multiple "{}" in path
|
72
|
+
res_fps = [res_fps.replace("{}", "{0}").format(year)
|
73
|
+
for year in analysis_years]
|
72
74
|
elif isinstance(res_fps, str):
|
73
75
|
# only one resource file request, still put in list
|
74
76
|
res_fps = [res_fps]
|
reV/losses/scheduled.py
CHANGED
@@ -7,7 +7,6 @@ import warnings
|
|
7
7
|
import json
|
8
8
|
|
9
9
|
import numpy as np
|
10
|
-
import PySAM
|
11
10
|
|
12
11
|
from reV.losses.utils import (convert_to_full_month_names,
|
13
12
|
filter_unknown_month_names,
|
@@ -524,8 +523,8 @@ class ScheduledLossesMixin:
|
|
524
523
|
information is expected to be a list of dictionaries containing
|
525
524
|
outage specifications. See :class:`Outage` for a description of
|
526
525
|
the specifications allowed for each outage. The scheduled losses
|
527
|
-
are passed to SAM via the ``
|
528
|
-
|
526
|
+
are passed to SAM via the ``adjust_timeindex`` key to signify
|
527
|
+
which capacity factors should be adjusted with outage losses.
|
529
528
|
If no outage info is specified in ``sam_sys_inputs``, no
|
530
529
|
scheduled losses are added.
|
531
530
|
|
@@ -543,13 +542,13 @@ class ScheduledLossesMixin:
|
|
543
542
|
|
544
543
|
Notes
|
545
544
|
-----
|
546
|
-
The scheduled losses are passed to SAM via the
|
547
|
-
key to signify which
|
548
|
-
with outage losses. If the user specifies
|
549
|
-
adjustment factors via the ``
|
550
|
-
combined. For example, if the user inputs a 33%
|
545
|
+
The scheduled losses are passed to SAM via the
|
546
|
+
``adjust_timeindex`` key to signify which capacity factors
|
547
|
+
should be adjusted with outage losses. If the user specifies
|
548
|
+
other adjustment factors via the ``adjust_timeindex`` key, the
|
549
|
+
effect is combined. For example, if the user inputs a 33%
|
551
550
|
adjustment factor and reV schedules an outage for 70% of the
|
552
|
-
farm down for the same
|
551
|
+
farm down for the same time step, then the resulting adjustment
|
553
552
|
factor is
|
554
553
|
|
555
554
|
.. math: 1 - [(1 - 70/100) * (1 - 33/100)] = 0.799
|
@@ -575,7 +574,7 @@ class ScheduledLossesMixin:
|
|
575
574
|
self._add_outages_to_sam_inputs(hourly_outages)
|
576
575
|
|
577
576
|
logger.debug("Hourly adjustment factors after scheduled outages: {}"
|
578
|
-
.format(list(self.sam_sys_inputs['
|
577
|
+
.format(list(self.sam_sys_inputs['adjust_timeindex'])))
|
579
578
|
|
580
579
|
def _user_outage_input(self):
|
581
580
|
"""Get outage and seed info from config. """
|
@@ -601,16 +600,20 @@ class ScheduledLossesMixin:
|
|
601
600
|
def _add_outages_to_sam_inputs(self, outages):
|
602
601
|
"""Add the hourly adjustment factors to config, checking user input."""
|
603
602
|
|
603
|
+
if self.time_interval > 1:
|
604
|
+
outages = np.array([outage
|
605
|
+
for _ in range(self.time_interval)
|
606
|
+
for outage in outages])
|
604
607
|
hourly_mult = 1 - outages / 100
|
605
|
-
hourly_mult = self._fix_pysam_bug(hourly_mult)
|
606
608
|
|
607
|
-
|
608
|
-
|
609
|
+
default_user_input = [0] * 8760 * self.time_interval
|
610
|
+
user_hourly_input = self.sam_sys_inputs.pop('adjust_timeindex',
|
611
|
+
default_user_input)
|
609
612
|
user_hourly_mult = 1 - np.array(user_hourly_input) / 100
|
610
613
|
|
611
614
|
final_hourly_mult = hourly_mult * user_hourly_mult
|
612
|
-
self.sam_sys_inputs['
|
613
|
-
self.sam_sys_inputs['
|
615
|
+
self.sam_sys_inputs['adjust_timeindex'] = (1 - final_hourly_mult) * 100
|
616
|
+
self.sam_sys_inputs['adjust_en_timeindex'] = 1
|
614
617
|
|
615
618
|
@property
|
616
619
|
def outage_seed(self):
|
@@ -631,19 +634,3 @@ class ScheduledLossesMixin:
|
|
631
634
|
pass
|
632
635
|
|
633
636
|
return self.__base_seed
|
634
|
-
|
635
|
-
def _fix_pysam_bug(self, hourly_mult):
|
636
|
-
"""Fix PySAM bug that squares HAF user input"""
|
637
|
-
if getattr(self, "MODULE", "").casefold() != "windpower":
|
638
|
-
return hourly_mult
|
639
|
-
|
640
|
-
bugged_pysam_version = (PySAM.__version__.startswith("5")
|
641
|
-
or PySAM.__version__.startswith("6"))
|
642
|
-
if not bugged_pysam_version:
|
643
|
-
return hourly_mult
|
644
|
-
|
645
|
-
# Bug in PySAM windpower module that applies HAF twice (i.e.
|
646
|
-
# squares the input values), so we sqrt the desired loss value
|
647
|
-
return np.sqrt(hourly_mult)
|
648
|
-
|
649
|
-
|