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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: NREL-reV
3
- Version: 0.13.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~=6.0.1
26
- Requires-Dist: NREL-rex<0.4,>=0.3.1
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.13.0.dist-info/licenses/LICENSE,sha256=hDwoTANtan2ZpufBlXm5C3W_PJ-mCqItvlcobgjxL7k,1526
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=jfGGOr6QlLz8ghA7vBgx5-VgNsy4bBQo5DdHk42-q9A,1601
4
- reV/version.py,sha256=hIUvlRrRzHirY4NJU-6xMqe2V_yRaRSnk6a9V3HAi6s,51
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=2konhn_hV0YXd_h2cbBYbGPUb9oafKY0SvQhBzmOMBE,112661
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=GfM0vj4LnwsthzhSsUnmLt78PAUHrxZI5ghWaxvoF7A,10568
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=5RSlr8yO9zlB-1FzFZGClqsxqsrbr9rcS_8PlOjVx2s,4435
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=w25YIXi7ykkeP9FgB_6vpjNm1MNOYNRjM-dqqVQJvGA,27276
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=k_OrtRF4G_BYl4wky-AolCOBgOAktN4_ZCV1LLYyZl8,88549
84
- reV/supply_curve/sc_aggregation.py,sha256=SokYyLXdjQwgvdmgiWhLSSTssnkX6ZdCSwO0WzC8SYw,63911
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=rsCyv6YBIbHwCXOrYdaVbuKjphi24QsjaWA7F8m8KCY,18567
87
- reV/utilities/__init__.py,sha256=Z77DeuW_HtMFr7ssywUEgOLikWkDTtbaEdEWiSxwO4Y,10592
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.13.0.dist-info/METADATA,sha256=XfYD0aNmttfLYLma2YZm9fCFZDaOVaTvVtAywz-xB3g,11021
95
- nrel_rev-0.13.0.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
96
- nrel_rev-0.13.0.dist-info/entry_points.txt,sha256=4IfJtZm2iMJwrbC8J0Or7VjZWnFpvCaHYVpvSWfIwDA,616
97
- nrel_rev-0.13.0.dist-info/top_level.txt,sha256=S6YF2ZYgXUB6n28SY0K2H8YB9tMJdXQ9CyQbo6VC89M,4
98
- nrel_rev-0.13.0.dist-info/RECORD,,
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (78.1.0)
2
+ Generator: setuptools (80.3.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
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.COST_SITE_OCC_USD_PER_AC_MW] = (
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.COST_BASE_OCC_USD_PER_AC_MW] = (
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.COST_SITE_VOC_USD_PER_AC_MW] = (
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.COST_BASE_VOC_USD_PER_AC_MW] = (
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
- / capacity_ac_mw
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, rep_profiles_command,
28
- hybrids_command, nrwal_command, qa_qc_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)
@@ -34,31 +34,54 @@ class EconomiesOfScale:
34
34
  lcoe : $/MWh
35
35
  """
36
36
 
37
- def __init__(self, eqn, data):
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
- if self._eqn is not None:
61
- check_eval_str(str(self._eqn))
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._eqn)
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
- var_names = []
115
- if self._eqn is not None:
116
- delimiters = (">", "<", ">=", "<=", "==", ",", "*", "/", "+", "-",
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
- var_names = []
120
- for sub in re.split(regex_pattern, str(self._eqn)):
121
- if sub and not self.is_num(sub) and not self.is_method(sub):
122
- var_names.append(sub)
123
- var_names = sorted(set(var_names))
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
- return var_names
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. Should be
135
- numeric scalars to apply directly to the capital cost.
179
+ Evaluated output of the EconomiesOfScale equation.
136
180
  """
137
- out = 1
138
- if self._eqn is not None:
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
- return out
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. Should be
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.COST_SITE_OCC_USD_PER_AC_MW
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 evaluated
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 foc(self):
269
- """Fixed operating cost from input data arg
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
- Fixed operating cost from input data arg
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 voc(self):
288
- """Variable operating cost from input data arg
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
- Variable operating cost from input data arg
365
+ Fixed operating cost ($/year) found in the data input arg scaled
366
+ by the evaluated EconomiesOfScale equation.
294
367
  """
295
- voc_from_cap = self._cost_from_cap(
296
- SupplyCurveField.COST_SITE_VOC_USD_PER_AC_MW
297
- )
298
- if voc_from_cap is not None:
299
- return voc_from_cap
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 + foc) / raw_lcoe
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
- aep = (self.fcr * self.raw_capital_cost + self.foc) / self.raw_lcoe
317
- aep *= 1000 # convert MWh to KWh
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 capital cost based on the
432
+ """LCOE ($/MWh) calculated with the scaled costs based on the
334
433
  EconomiesOfScale input equation.
335
434
 
336
- LCOE = (FCR * scaled_capital_cost + FOC) / AEP + VOC
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 capital cost based on the
440
+ LCOE calculated with the scaled costs based on the
342
441
  EconomiesOfScale input equation.
343
442
  """
344
- return lcoe_fcr(
345
- self.fcr, self.scaled_capital_cost, self.foc, self.aep, self.voc
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
- # need to make list of res files for each year
71
- res_fps = [res_fps.format(year) for year in analysis_years]
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 ``adjust_hourly`` key to signify which
528
- hourly capacity factors should be adjusted with outage losses.
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 ``adjust_hourly``
547
- key to signify which hourly capacity factors should be adjusted
548
- with outage losses. If the user specifies other hourly
549
- adjustment factors via the ``adjust_hourly`` key, the effect is
550
- combined. For example, if the user inputs a 33% hourly
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 hour, then the resulting adjustment
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['adjust_hourly'])))
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
- user_hourly_input = self.sam_sys_inputs.pop('adjust_hourly',
608
- [0] * 8760)
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['adjust_hourly'] = (1 - final_hourly_mult) * 100
613
- self.sam_sys_inputs['adjust_en_hourly'] = 1
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
-