-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Lcoe branch #1687
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
eccoope
wants to merge
50
commits into
pvlib:main
Choose a base branch
from
eccoope:lcoe_branch
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Lcoe branch #1687
Changes from all commits
Commits
Show all changes
50 commits
Select commit
Hold shift + click to select a range
49fd4e8
''
eccoope fb6ff2f
Merge branch 'main' of https://github.com/eccoope/pvlib-python-forked…
eccoope 2204f18
''
eccoope af4b00b
resolved stickler issues
eccoope d7c606d
resolved stickler issues
eccoope 9b86e11
resolved stickler issues
eccoope 9f1e090
updated to be consistent suggested changes
eccoope a4fa101
''
eccoope fdea1b5
''
eccoope 4c7cba8
''
eccoope 7fdb96f
''
eccoope 59df950
''
eccoope effdaa7
''
eccoope 4284990
Resolve stickler failures
eccoope 2bfba2e
Resolved stickler issues in simple_lcoe_calculator.py
eccoope e6f6938
Resolved stickler issues and linked references
eccoope b61a9cb
Resolved stickler failures
eccoope 49f2631
Changed variable names + real/nominal clarification
eccoope 388f9f4
Resolved stickler failure
eccoope a18f861
More stickler corrections
eccoope 582c974
Encourage use of real discount rates
eccoope 4161663
Encourage use of real discount rates
eccoope c0e31dc
Update pvlib/financial.py
eccoope 912969d
Update pvlib/financial.py
eccoope a0a7cf1
Update pvlib/financial.py
eccoope 17898a6
Update pvlib/financial.py
eccoope dce7e5f
Update pvlib/financial.py
eccoope 1ea12b5
Update pvlib/financial.py
eccoope e124027
Update pvlib/financial.py
eccoope 9d17c09
Apply suggestions from code review
eccoope 83d6e02
Update financial.py
eccoope 33c8238
Update test_financial.py
eccoope cd0ae02
Stickler for lcoe_sam_validation.py
eccoope 92d7258
More stickler for lcoe_sam_validation.py
eccoope cae3d7b
more stickler for lcoe_sam_validation.py
eccoope 7fc63e7
One last stickler thing
eccoope 00df4f8
Remove "real" from wacc docstring
eccoope ab47596
Added a line to index.rst
eccoope 7a039b7
Added last two authors to be consistent with version in main branch
eccoope ebec039
Merge branch 'main' into lcoe_branch
eccoope 7136b84
Merge branch 'main' of https://github.com/eccoope/pvlib-python-forked…
eccoope 41bde9c
Resolved conflicts in v0.9.5 and moved changes to v0.9.6
eccoope 8aff9f0
Merge branch 'main' into lcoe_branch
eccoope 925e155
Remove variable O&M caveatp
eccoope 42b7823
Merge branch 'main' into lcoe_branch
eccoope 0541e5b
Added system degradation rate to example
eccoope 1441de6
Delete v0.9.6.rst
eccoope d2205c3
Merge branch 'pvlib:main' into lcoe_branch
eccoope 77fb22c
Updated whatsnew v0.10.2.rst
eccoope b3c272e
Merge branch 'main' into lcoe_branch
eccoope File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
""" | ||
LCOE Calculation | ||
================ | ||
|
||
Example of an LCOE calculation for a utility-scale site in Albuquerque, NM | ||
using the approach implemented by NREL's SAM software | ||
""" | ||
|
||
# This example shows usage of pvlib's lcoe calculation with | ||
# :py:meth:`pvlib.financial.lcoe`, :py:meth:`pvlib.financial.wacc`, | ||
# :py:meth:`pvlib.financial.nominal_to_real`, and | ||
# :py:meth:`pvlib.financial.crf` to generate a Series of annual cost and | ||
# production data, and a real LCOE. TMY GHI, DNI, and DHI irradiance data | ||
# for Albuquerque is loaded from the NSRDB and | ||
# :py:meth:`pvlib.location.get_solarposition` is used with | ||
# :py:meth:`pvlib.irradiance.get_total_irradiance`to calculate POA | ||
# irradiance. DC energy production is calculated with | ||
# :py:meth:`pvlib.pvsystem.pvwatts_dc` to get annual AC power output. | ||
# Capital cost is calculated using the FCR method described here: | ||
# http://samrepo.nrelcloud.org/help/index.html?fin_lcoefcr.htm with real | ||
# discount rates. Construction interest is assumed to be zero. Input values | ||
# with an asterisk were sourced from NREL's ATB projections for a residential | ||
# system in 2022 with moderate | ||
# technological advancement or the set of financial assumptions under which | ||
# NREL produces the ATB. Monthly POA output [kWh/m^2], annual AC output [kWh], | ||
# and LCOE should match values calculated by SAM. | ||
|
||
import numpy as np | ||
import pandas as pd | ||
import datetime | ||
from pvlib import location | ||
from pvlib import irradiance | ||
from pvlib import temperature | ||
from pvlib import pvsystem | ||
from pvlib import inverter | ||
from pvlib import financial | ||
# from .conftest import DATA_DIR | ||
|
||
from pvlib.tests.conftest import DATA_DIR | ||
|
||
# Get annual AC output | ||
|
||
# Installed DC capacity [W] (total is 1 MW) | ||
installed_dc = 1000000 | ||
|
||
# Surface tilt and azimuth | ||
tilt, surface_azimuth = 30, 180 | ||
|
||
# Set Albuquerque as location | ||
lat, lon, elev = 35.054942, -106.540485, 1657.8 | ||
loc = location.Location(lat, lon, altitude=elev) | ||
|
||
# Albuquerque TMY data from NSRDB | ||
data_file = DATA_DIR / 'albuquerque_tmy.csv' | ||
data = pd.read_csv(data_file, skiprows=[0, 1]) | ||
|
||
# Set DatetimeIndex for data | ||
data.set_index(pd.DatetimeIndex(data[['Month', 'Day', 'Hour', 'Minute']] | ||
.apply(lambda x: | ||
datetime.datetime(2022, x['Month'], | ||
x['Day'], | ||
x['Hour'], | ||
x['Minute']), | ||
axis=1)), inplace=True) | ||
|
||
# loc.get_solarposition() assumes UTC unless times is localized | ||
# but Albuquerque is in Etc/GMT-7 | ||
temp = loc.get_solarposition(times=pd.date_range(start=data.index[0] | ||
+ pd.Timedelta(7, 'h'), | ||
end=data.index[-1] | ||
+ pd.Timedelta(7, 'h'), | ||
freq='1H')) | ||
# Shift index back to align with Etc/GMT-7 | ||
solar_position = temp.set_index(temp.index.shift(periods=-7, freq='1H')) | ||
|
||
# Get POA and apply AOI modifier to direct and diffuse components | ||
poa_irrad = irradiance.get_total_irradiance( | ||
surface_tilt=tilt, surface_azimuth=surface_azimuth, | ||
dni=data['DNI'], ghi=data['GHI'], dhi=data['DHI'], | ||
solar_zenith=solar_position['zenith'], | ||
solar_azimuth=solar_position['azimuth'], | ||
albedo=data['Surface Albedo'])['poa_global'] | ||
|
||
# Calulate and display daily/monthly stats | ||
daily_ghi = data['GHI'].groupby(data.index.map(lambda x: x.date())).sum().\ | ||
mean()/1000 | ||
daily_dhi = data['DHI'].groupby(data.index.map(lambda x: x.date())).sum().\ | ||
mean()/1000 | ||
daily_dni = data['DNI'].groupby(data.index.map(lambda x: x.date())).sum().\ | ||
mean()/1000 | ||
monthly_poa = poa_irrad.groupby(poa_irrad.index.map(lambda x: | ||
x.date().month)).\ | ||
sum()/1000 | ||
|
||
print('Daily average GHI is ' + str(np.round(daily_ghi, 3)) + ' kWh/m^2') | ||
print('Daily average DHI is ' + str(np.round(daily_dhi, 3)) + ' kWh/m^2') | ||
print('Daily average DNI is ' + str(np.round(daily_dni, 2)) + ' kWh/m^2') | ||
print('Monthly POA averages [kWh/m^2]:') | ||
print(monthly_poa) | ||
|
||
# Get system losses | ||
losses = pvsystem.pvwatts_losses()/100 | ||
|
||
# Get cell temperature | ||
cell_temp = temperature.pvsyst_cell(poa_irrad, data['Temperature'], | ||
data['Wind Speed']) | ||
|
||
# Get hourly DC output using PVWatts [W DC] | ||
dc_power = pvsystem.pvwatts_dc(poa_irrad, cell_temp, installed_dc, | ||
-0.0037) * (1 - losses) | ||
|
||
# Get hourly AC output using PVWatts [W AC] | ||
ac_power = inverter.pvwatts(dc_power, installed_dc/1.1) | ||
|
||
# Check that AC power data is evenly spaced over hour increments | ||
if ~np.all(np.unique(np.diff(ac_power.index)/np.timedelta64(1, 'h')) == 1): | ||
raise ValueError | ||
|
||
# Riemann-sum to get annual AC output [kWh] | ||
annual_ac_output = ac_power.sum()/1000 | ||
print('Annual AC output is ' + str(np.round(annual_ac_output, 2)) + ' kWh') | ||
|
||
# Period of financial analysis | ||
n = 20 | ||
|
||
# Assume system degradation rate is 1% | ||
sdr = 0.01 | ||
|
||
# Apply degradation rate to AC production over analysis period | ||
production = np.array([annual_ac_output*(1 - sdr)**i for i in range(n)]) | ||
|
||
# Total installed capital costs [$] * | ||
capex = 1119.82*installed_dc/1000 | ||
|
||
# Fixed O&M costs [$] * | ||
fixed_op_cost = np.full(n, 19.95*installed_dc/1000) | ||
|
||
# Inflation rate * | ||
inflation_r = 0.025 | ||
|
||
# Nominal interest rate * | ||
interest_r = 0.04 | ||
|
||
# Real interest rate | ||
rint = financial.nominal_to_real(interest_r, inflation_r) | ||
|
||
# Nominal internal rate of return * | ||
irr = 0.0775 | ||
|
||
# Real internal rate of return | ||
rroe = financial.nominal_to_real(irr, inflation_r) | ||
|
||
# Fraction of capital cost covered by a loan * | ||
loan_frac = 0.518577595078774 | ||
|
||
# Effective tax rate * | ||
tax_r = 0.2574 | ||
|
||
# Real weighted average cost of capital | ||
my_wacc = financial.wacc(loan_frac, rroe, rint, inflation_r, tax_r) | ||
print('Real weighted average cost of capital is ' + str(np.round(my_wacc, 5))) | ||
|
||
# Depreciation schedule | ||
dep_sch = pd.Series([20, 32, 19.2, 11.52, 11.52, 5.76])/100 | ||
|
||
# Present value of depreciation | ||
pvdep = np.sum([dep_sch.at[j]/((1 + my_wacc)*(1 + inflation_r))**(j+1) | ||
for j in range(len(dep_sch))]) | ||
|
||
# Project financing factor | ||
pff = (1 - (tax_r*pvdep))/(1 - tax_r) | ||
|
||
# Construction financing factor | ||
cff = 1 | ||
|
||
# Capital recovery factor | ||
my_crf = financial.crf(my_wacc, n) | ||
|
||
# Fixed charge rate | ||
fcr = my_crf*pff*cff | ||
print('Fixed charge rate is ' + str(np.round(fcr, 5))) | ||
|
||
debt_tenor = n | ||
|
||
# Annuity (annual payment) on total capital cost [$] | ||
cap_cost = np.full(n, capex*fcr) | ||
print('Annual payment on capital cost is $' + str(np.round(cap_cost[0], 2))) | ||
|
||
# Call lcoe() | ||
my_lcoe = financial.lcoe(production=production, cap_cost=cap_cost, | ||
fixed_om=fixed_op_cost) | ||
|
||
print('Real LCOE = ' + str(my_lcoe) + str(' cents/kWh')) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
""" | ||
LCOE Calculation | ||
================ | ||
|
||
Example of an LCOE calculation using an approach implemented in NREL's "Simple | ||
LCOE Calculator", accessible at http://www.nrel.gov/analysis/tech-lcoe.html | ||
""" | ||
# %% | ||
# This example shows basic usage of pvlib's lcoe calculation with | ||
# :py:meth:`pvlib.financial.lcoe` and :py:meth:`pvlib.financial.crf`. | ||
# The example shown here will generate a Series of annual cost and production | ||
# data, and a numerical LCOE. To be comparable with NREL's implemenation, | ||
# this example adheres to the following assumptions: that energy production | ||
# and O&M costs are constant and that the entire project is financed with a | ||
# loan. Input values for CAPEX, capacity factor, and O&M were sourced from | ||
# NREL's ATB for a residential system in 2022 located in Resource Class 5 | ||
# with moderate technological advancement. The discount rate is set to the | ||
# value recommended in NREL's implementation. | ||
|
||
import numpy as np | ||
import pandas as pd | ||
from pvlib import financial | ||
|
||
# Analysis period | ||
n = 20 | ||
|
||
# Capacity factor | ||
cf = 0.15357857 | ||
|
||
# Constant annual energy production | ||
energy = np.full(n, cf*8760) | ||
|
||
# Real discount rate | ||
discount_rate = 0.03 | ||
|
||
# Capital recovery factor | ||
my_crf = financial.crf(discount_rate, n) | ||
|
||
# CAPEX | ||
capex = 2443.45 | ||
|
||
# Fraction of capital cost | ||
loan_frac = 1 | ||
|
||
# Annual capital costs | ||
cap_cost = np.array([capex*loan_frac*my_crf for i in range(n)]) | ||
|
||
# Constant annual O&M | ||
fixed_om = pd.Series(data=[26.98 for j in range(n)]) | ||
|
||
# Put data in table and display | ||
table = pd.DataFrame(columns=['Production [kWh/kW]', 'Capital cost [$/kW]', | ||
'O&M [$/kW]']) | ||
table['Production [kWh/kW]'] = energy | ||
table['Capital cost [$/kW]'] = cap_cost | ||
table['O&M [$/kW]'] = fixed_om | ||
table.index.name = 'Year' | ||
table | ||
|
||
# %% | ||
# Get LCOE | ||
|
||
my_lcoe = financial.lcoe(production=energy, cap_cost=cap_cost, | ||
fixed_om=fixed_om) | ||
print('LCOE = ' + str(my_lcoe) + str(' cents/kWh')) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
.. currentmodule:: pvlib | ||
|
||
Financial | ||
========= | ||
|
||
.. autosummary:: | ||
:toctree: generated/ | ||
|
||
financial.lcoe | ||
financial.crf | ||
financial.nominal_to_real | ||
financial.real_to_nominal | ||
financial.wacc |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,3 +20,4 @@ API reference | |
bifacial | ||
scaling | ||
location | ||
financial |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.