Skip to content

port pvl_adrinverter #288

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

Merged
merged 18 commits into from
Feb 14, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/sphinx/source/whatsnew/v0.4.4.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ v0.4.4 (January xx, 2017)
Enhancements
~~~~~~~~~~~~

* Added Anton Driesse Inverter database and made compatible with
pvsystem.retrieve_sam. (:issue:`169`)
* Ported Anton Driesse Inverter model from PV_LIB Toolbox. (:issue:`160`)
* Added Kasten pyrheliometric formula to calculate Linke turbidity factors with
improvements by Ineichen and Perez to extend range of air mass (:issue:`278`)

Expand All @@ -31,4 +34,5 @@ Contributors
~~~~~~~~~~~~

* Will Holmgren
* Volker Beutner
* Mark Mikofski
1,762 changes: 1,762 additions & 0 deletions pvlib/data/adr-library-2013-10-01.csv

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions pvlib/modelchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -434,7 +434,7 @@ def ac_model(self, model):
if model == 'snlinverter':
self._ac_model = self.snlinverter
elif model == 'adrinverter':
raise NotImplementedError
self._ac_model = self.adrinverter
elif model == 'pvwatts':
self._ac_model = self.pvwatts_inverter
else:
Expand All @@ -447,6 +447,8 @@ def infer_ac_model(self):
module_params = set(self.system.module_parameters.keys())
if set(['C0', 'C1', 'C2']) <= inverter_params:
return self.snlinverter
elif set(['ADRCoefficients']) <= inverter_params:
return self.adrinverter
elif set(['pdc0']) <= module_params:
return self.pvwatts_inverter
else:
Expand All @@ -458,7 +460,7 @@ def snlinverter(self):
return self

def adrinverter(self):
raise NotImplementedError
self.ac = self.system.adrinverter(self.dc['v_mp'], self.dc['p_mp'])
return self

def pvwatts_inverter(self):
Expand Down
128 changes: 128 additions & 0 deletions pvlib/pvsystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,9 @@ def snlinverter(self, v_dc, p_dc):
"""
return snlinverter(v_dc, p_dc, self.inverter_parameters)

def adrinverter(self, v_dc, p_dc):
return adrinverter(v_dc, p_dc, self.inverter_parameters)

def scale_voltage_current_power(self, data):
"""
Scales the voltage, current, and power of the DataFrames
Expand Down Expand Up @@ -1056,6 +1059,7 @@ def retrieve_sam(name=None, path=None):
* CEC module database
* Sandia Module database
* CEC Inverter database
* Anton Driesse Inverter database

and return it as a pandas DataFrame.

Expand All @@ -1070,6 +1074,7 @@ def retrieve_sam(name=None, path=None):
(CEC is only current inverter db available; tag kept for
backwards compatibility)
* 'SandiaMod' - returns the Sandia Module database
* 'ADRInverter' - returns the ADR Inverter database

path : None or string
Path to the SAM file. May also be a URL.
Expand Down Expand Up @@ -1122,6 +1127,8 @@ def retrieve_sam(name=None, path=None):
elif name == 'sandiamod':
csvdata = os.path.join(
data_path, 'sam-library-sandia-modules-2015-6-30.csv')
elif name == 'adrinverter':
csvdata = os.path.join(data_path, 'adr-library-2013-10-01.csv')
elif name in ['cecinverter', 'sandiainverter']:
# Allowing either, to provide for old code,
# while aligning with current expectations
Expand Down Expand Up @@ -1171,6 +1178,13 @@ def _parse_raw_sam_df(csvdata):

df.index = parsedindex
df = df.transpose()
if 'ADRCoefficients' in df.index:
ad_ce = 'ADRCoefficients'
# for each inverter, parses a string of coefficients like
# ' 1.33, 2.11, 3.12' into a list containing floats:
# [1.33, 2.11, 3.12]
df.loc[ad_ce] = df.loc[ad_ce].map(lambda x: list(
map(float, x.strip(' []').split())))

return df

Expand Down Expand Up @@ -2037,6 +2051,120 @@ def snlinverter(v_dc, p_dc, inverter):
return ac_power


def adrinverter(v_dc, p_dc, inverter, vtol=0.10):
r'''
Converts DC power and voltage to AC power using Anton Driesse's
Grid-Connected PV Inverter efficiency model

Parameters
----------
v_dc : numeric
A scalar or pandas series of DC voltages, in volts, which are provided
as input to the inverter. If Vdc and Pdc are vectors, they must be
of the same size. v_dc must be >= 0. (V)

p_dc : numeric
A scalar or pandas series of DC powers, in watts, which are provided
as input to the inverter. If Vdc and Pdc are vectors, they must be
of the same size. p_dc must be >= 0. (W)

inverter : dict-like
A dict-like object defining the inverter to be used, giving the
inverter performance parameters according to the model
developed by Anton Driesse [1].
A set of inverter performance parameters may be loaded from the
supplied data table using retrievesam.
See Notes for required keys.

vtol : numeric
A unit-less fraction that determines how far the efficiency model is
allowed to extrapolate beyond the inverter's normal input voltage
operating range. 0.0 <= vtol <= 1.0

Returns
-------
ac_power : numeric
A numpy array or pandas series of modeled AC power output given the
input DC voltage, v_dc, and input DC power, p_dc. When ac_power would
be greater than pac_max, it is set to p_max to represent inverter
"clipping". When ac_power would be less than -p_nt (energy consumed
rather than produced) then ac_power is set to -p_nt to represent
nightly power losses. ac_power is not adjusted for maximum power point
tracking (MPPT) voltage windows or maximum current limits of the
inverter.

Notes
-----

Required inverter keys are:

======= ============================================================
Column Description
======= ============================================================
p_nom The nominal power value used to normalize all power values,
typically the DC power needed to produce maximum AC power
output, (W).

v_nom The nominal DC voltage value used to normalize DC voltage
values, typically the level at which the highest efficiency
is achieved, (V).

pac_max The maximum AC output power value, used to clip the output
if needed, (W).

ce_list This is a list of 9 coefficients that capture the influence
of input voltage and power on inverter losses, and thereby
efficiency.

p_nt ac-power consumed by inverter at night (night tare) to
maintain circuitry required to sense PV array voltage, (W).
======= ============================================================

References
----------
[1] Beyond the Curves: Modeling the Electrical Efficiency
of Photovoltaic Inverters, PVSC 2008, Anton Driesse et. al.

See also
--------
sapm
singlediode
'''

p_nom = inverter['Pnom']
v_nom = inverter['Vnom']
pac_max = inverter['Pacmax']
p_nt = inverter['Pnt']
ce_list = inverter['ADRCoefficients']
v_max = inverter['Vmax']
v_min = inverter['Vmin']
vdc_max = inverter['Vdcmax']
mppt_hi = inverter['MPPTHi']
mppt_low = inverter['MPPTLow']

v_lim_upper = np.nanmax([v_max, vdc_max, mppt_hi])*(1+vtol)
v_lim_lower = np.nanmax([v_min, mppt_low])*(1-vtol)

pdc = p_dc/p_nom
vdc = v_dc/v_nom
poly = np.array([pdc**0, pdc, pdc**2, vdc-1, pdc*(vdc-1),
pdc**2*(vdc-1), 1/vdc-1, pdc*(1./vdc-1),
pdc**2*(1./vdc-1)])
p_loss = np.dot(np.array(ce_list), poly)
ac_power = p_nom * (pdc-p_loss)
p_nt = -1*np.absolute(p_nt)

ac_power = np.where((v_lim_upper < v_dc) | (v_dc < v_lim_lower),
np.nan, ac_power)
ac_power = np.where((ac_power < p_nt) | (vdc == 0), p_nt, ac_power)
ac_power = np.where(ac_power > pac_max, pac_max, ac_power)

if isinstance(p_dc, pd.Series):
ac_power = pd.Series(ac_power, index=pdc.index)

return ac_power


def scale_voltage_current_power(data, voltage=1, current=1):
"""
Scales the voltage, current, and power of the DataFrames
Expand Down
21 changes: 17 additions & 4 deletions pvlib/test/test_modelchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,20 @@ def cec_dc_snl_ac_system(sam_data):
return system


@pytest.fixture
def cec_dc_adr_ac_system(sam_data):
modules = sam_data['cecmod']
module_parameters = modules['Canadian_Solar_CS5P_220M'].copy()
module_parameters['b'] = 0.05
module_parameters['EgRef'] = 1.121
module_parameters['dEgdT'] = -0.0002677
inverters = sam_data['adrinverter']
inverter = inverters['Zigor__Sunzet_3_TL_US_240V__CEC_2011_'].copy()
system = PVSystem(module_parameters=module_parameters,
inverter_parameters=inverter)
return system


@pytest.fixture
def pvwatts_dc_snl_ac_system(sam_data):
module_parameters = {'pdc0': 220, 'gamma_pdc': -0.003}
Expand Down Expand Up @@ -201,15 +215,14 @@ def acdc(mc):
@requires_scipy
@pytest.mark.parametrize('ac_model, expected', [
('snlinverter', [181.604438144, -2.00000000e-02]),
pytest.mark.xfail(raises=NotImplementedError)
(('adrinverter', [179.7178188, -2.00000000e-02])),
('adrinverter', [np.nan, -25.00000000e-02]),
('pvwatts', [190.028186986, 0]),
(acdc, [199.845296258, 0]) # user supplied function
])
def test_ac_models(system, cec_dc_snl_ac_system, pvwatts_dc_pvwatts_ac_system,
def test_ac_models(system, cec_dc_adr_ac_system, pvwatts_dc_pvwatts_ac_system,
location, ac_model, expected):

ac_systems = {'snlinverter': system, 'adrinverter': cec_dc_snl_ac_system,
ac_systems = {'snlinverter': system, 'adrinverter': cec_dc_adr_ac_system,
'pvwatts': pvwatts_dc_pvwatts_ac_system,
acdc: pvwatts_dc_pvwatts_ac_system}

Expand Down
36 changes: 36 additions & 0 deletions pvlib/test/test_pvsystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ def sam_data():
data['cecmod'] = pvsystem.retrieve_sam('cecmod')
data['sandiamod'] = pvsystem.retrieve_sam('sandiamod')
data['cecinverter'] = pvsystem.retrieve_sam('cecinverter')
data['adrinverter'] = pvsystem.retrieve_sam('adrinverter')
return data


Expand Down Expand Up @@ -567,6 +568,41 @@ def test_PVSystem_sapm_celltemp():
assert_frame_equal(expected, pvtemps)


def test_adrinverter(sam_data):
inverters = sam_data['adrinverter']
testinv = 'Ablerex_Electronics_Co___Ltd___' + \
'ES_2200_US_240__240_Vac__240V__CEC_2011_'
vdcs = pd.Series([135, 154, 390, 420, 551])
pdcs = pd.Series([135, 1232, 1170, 420, 551])

pacs = pvsystem.adrinverter(vdcs, pdcs, inverters[testinv])
assert_series_equal(pacs, pd.Series([np.nan, 1161.5745, 1116.4459,
382.6679, np.nan]))


def test_adrinverter_vtol(sam_data):
inverters = sam_data['adrinverter']
testinv = 'Ablerex_Electronics_Co___Ltd___' + \
'ES_2200_US_240__240_Vac__240V__CEC_2011_'
vdcs = pd.Series([135, 154, 390, 420, 551])
pdcs = pd.Series([135, 1232, 1170, 420, 551])

pacs = pvsystem.adrinverter(vdcs, pdcs, inverters[testinv], vtol=0.20)
assert_series_equal(pacs, pd.Series([104.8223, 1161.5745, 1116.4459,
382.6679, 513.3385]))


def test_adrinverter_float(sam_data):
inverters = sam_data['adrinverter']
testinv = 'Ablerex_Electronics_Co___Ltd___' + \
'ES_2200_US_240__240_Vac__240V__CEC_2011_'
vdcs = 154.
pdcs = 1232.

pacs = pvsystem.adrinverter(vdcs, pdcs, inverters[testinv])
assert_allclose(pacs, 1161.5745)


def test_snlinverter(sam_data):
inverters = sam_data['cecinverter']
testinv = 'ABB__MICRO_0_25_I_OUTD_US_208_208V__CEC_2014_'
Expand Down