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

port pvl_adrinverter #288

merged 18 commits into from
Feb 14, 2017

Conversation

VolkrB
Copy link
Contributor

@VolkrB VolkrB commented Jan 4, 2017

#160 In short:

  • Ported mat -> csv adrinverter data file

pvsystem.py

  • included import of csv in pvsystem.py retrieve_sam function
  • added adrinverter function (two lines are a bit ugly, because i am storing coefficients in a string instead of making a column for each)

modelchain.py

  • added adrinverter function to wrapper

test_modelchain.py

  • removed xfail for adrinverter
  • added tests using csv data (Zigor inverter)
    (had to also change the single diode test, since i replaced cecinverter with adrinverter)

test_pvsystem.py

  • added test for adrinverter function

Need to document after everything looks good

I've compared inverter data from the adrinverter function to the sandiainverter function to see if, at least, the results are of the same magnitude and follow a similar trend. They do, however would be nice to see results from the matlab version to test against.

let me know what i can change/improve etc

Copy link
Member

@wholmgren wholmgren left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@VolkrB thanks for this great PR. Please see the line comments for more.

@@ -1122,6 +1125,9 @@ 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(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like this might fit on a single line that's less than 80 characters.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure

def adrinverter(v_dc, p_dc, inverter):

ce_clean = [','.join(inverter['ADRCoefficients'][1:-1].split())]
ce_list = np.array([float(s) for s in ce_clean[0].split(',')])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

inverter['ADRCoefficients'] should be parsed at import time

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, seems like that's where it should be

@@ -33,8 +33,8 @@ def cec_dc_snl_ac_system(sam_data):
module_parameters['b'] = 0.05
module_parameters['EgRef'] = 1.121
module_parameters['dEgdT'] = -0.0002677
inverters = sam_data['cecinverter']
inverter = inverters['ABB__MICRO_0_25_I_OUTD_US_208_208V__CEC_2014_'].copy()
inverters = sam_data['adrinverter']
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe I'm forgetting something, but don't we want a new test here instead of changing the existing test?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

on a second read, maybe this is ok because all of the methods are still being tested in some way. Is that right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea they are, I think the coverage is the same- this was something I was debating as well.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok with me to leave it alone then

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm only now realizing that this change to the cec_dc_snl_ac_system fixture makes the name no longer correct. I do think it would be worthwhile to make a second test called cec_dc_adr_ac_system or something like it. Then you can revert the change to the test_dc_models fixture, while also updating the test_ac_models ac_systems dictionary.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch

@@ -0,0 +1,1762 @@
Name,Manufacturer,Model,Source,Vac,Vintage,Pacmax,Pnom,Vnom,Vmin,Vmax,ADRCoefficients,Pnt,Vdcmax,Idcmax,MPPTLow,MPPTHi,TambLow,TambHi,Weight
0,0,0,0,0.0,0.0,0.0,0,0,0,0,0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are these 2 lines of 0s here because of the _parse_raw_sam_df function's skip_rows argument? I think we should keep this file clean and make the pvlib parser smarter. I don't remember the logic for that parser (I don't even remember if I wrote it).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea exactly. I could write a conditional statement in _parse_raw_sam_df to only skip rows for certain csvs, but maybe you can think of something better than that?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm struggling to think of a good solution for this and the array parsing issue. One relatively simple option is to replace the 0 lines with metadata lines like in the sam files. So, we'd want a units line and then I guess a copy of the column names. Or maybe just empty cells instead of 0s would be better? The end of the parser function could have a conditional on the presence of 'ADRCoefficients' for the array parsing issue discussed above.

@wholmgren wholmgren added this to the 0.4.4 milestone Jan 5, 2017
@VolkrB
Copy link
Contributor Author

VolkrB commented Jan 5, 2017

Updated the csv and moved inverter['ADRCoefficients'] parsing to pvsystem._parse_raw_sam_df

np.array([float(s) for s in
[','.join(x[1:-1]
.split())]
[0].split(',')]))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realize this is a pretty hackie, ugly, line of code. Trying to see if I can get this to be more elegant.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this works: lambda x: list(map(float, x.strip(' []').split(' ')))

not sure if there are pros/cons to adding np.array around the list.

also, you don't have to stay justified with the opening parenthesis. you can make only a 4 space indent on the line below the opening parenthesis. that alone would help readability!

I'd get rid of the empty else block, too.

other than that, I think you only need to add documentation and then we'll be set.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a code comment should go here to explain what this line is supposed to do. Does is really all have to be done in one line?

pdc**2*(1./vdc-1)])

p_loss = np.dot(ce_list, poly)
ac_power = p_nom * (pdc-p_loss)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the name "ac_power" seems to be breaking the naming pattern...

@adriesse
Copy link
Member

adriesse commented Jan 9, 2017

I reviewed the code and didn't see any mistakes. Since all the coefficients are normalized, it would pretty easy to create some synthetic test cases. For example, each coefficient with a value of 4% produces a loss of Pnom * 4% at Pdc==Pnom and Vdc==Vnom. Depending on which particular coefficient it is, this loss will vary with Pdc, Vdc, both, or neither.

@VolkrB
Copy link
Contributor Author

VolkrB commented Jan 9, 2017

I cleaned up that big ugly parsing line based on @wholmgren suggestions.

@adriesse: I always struggle with variable naming, so perhaps you, @wholmgren and I can all agree on something. Also, could you review my pushed documentation and suggest proper definition, references, etc? (i guess this would also be a good place to discuss the naming of vars)

@adriesse
Copy link
Member

adriesse commented Jan 9, 2017

The shorter parsing line is definitely easier to parse, but it still wouldn't hurt to throw a comment in there which would be even easier to parse for readers.

I see that the ac_power name came from the other inverter function and is used in matlab. I was anticipating something like pac or p_ac, but there doesn't seem to be a risk of misunderstanding so I don't mind if you leave it.

Can you just copy the documentation from the matlab version?

@VolkrB
Copy link
Contributor Author

VolkrB commented Jan 10, 2017

Updated docs/whatsnew. Let me know if I need to add/change etc.

Copy link
Member

@wholmgren wholmgren left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you double check the indentation and line length of the documentation? flake8 might help here. I think p_dc is a little off.

@@ -1059,6 +1059,7 @@ def retrieve_sam(name=None, path=None):
* CEC module database
* Sandia Module database
* CEC Inverter database
* Antone Driesse Inverter database
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anton

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Sorry Anton)

@@ -2048,30 +2051,40 @@ def snlinverter(v_dc, p_dc, inverter):

def adrinverter(v_dc, p_dc, inverter):
r'''
Converts DC power and voltage to AC power using Sandia's
Grid-Connected PV Inverter model.
PVL_ADRINVERTER Converts DC power and voltage to AC power
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no PVL_ADRINVERTER


inverter : dict-like
A dict-like object defining the inverter to be used.
A struct defining the inverter to be used, giving the
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dict-like

A dict-like object defining the inverter to be used.
A struct defining the inverter to be used, giving the
inverter performance parameters according to the model
developed by Anton Driesse[1].
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

space before [1]

A set of inverter performance parameters are provided with
pvlib, or may be generated from the library using retrievesam.
pvlib, or may be generated from the library using retrievesam.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe something like "loaded from the supplied data table using retrieve_sam" would be more accurate.

See Notes for required keys.

Returns
-------
ac_power : numeric
Modeled AC power output given the input DC voltage, v_dc, and
input DC power, p_dc.
A numpy array of modeled AC power output given the input
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"A numpy array" is not quite right. It returns a Series if the input data is also a Series.

@@ -1178,7 +1180,8 @@ def _parse_raw_sam_df(csvdata):
df = df.transpose()
if 'ADRCoefficients' in df.index:
ad_ce = 'ADRCoefficients'
df.loc[ad_ce] = df.loc[ad_ce].map(lambda x:list(
# parses string into list containing floats:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

# for each inverter, parses a string of coefficients like
# ***insert example***
# into a list containing floats

@@ -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.csv')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parameters in this particular file were generated using the CEC efficiency measurements available to me as of 2013-10. It might be good to add the date suffix. I'm looking into doing an update.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok I will add a date suffix

p_loss = np.dot(np.array(ce_list), poly)
ac_power = p_nom * (pdc-p_loss)

p_nt = -1*np.absolute(p_nt)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Earlier versions of the cec/sam/sandia database were not consistent: most inverters had positive p_nt but many were negative. The latest sam database I looked at had only positve values.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so p_nt = -1*np.absolute(p_nt) should just be -1*p_nt ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was just background info. The absolute() shouldn't be needed, but makes it more robust.

@VolkrB
Copy link
Contributor Author

VolkrB commented Jan 17, 2017

I ran flake8 and added a date suffix

@wholmgren
Copy link
Member

Looks good to me. We can ignore the failing travis builds.

@adriesse -- you get to make the final stamp of approval since your name is on it!


pacs = pvsystem.adrinverter(vdcs, pdcs, inverters[testinv])
assert_series_equal(pacs, pd.Series([-0.25000, 105.635043, 503.769061]))

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test may catch some types of future bugs, but it's not a good PV example. The current, voltage and power ranges are way too small for this Zigor model inverter.

Another idea might be to use some voltage and current combinations found in the pdf file on the CEC/gosolar website and look for the results of the model to be approxiamtely equal.

@adriesse
Copy link
Member

Other than the test everything looks good to me.

@VolkrB
Copy link
Contributor Author

VolkrB commented Jan 17, 2017

I don't mind updating the test. I've found a nice reference on gosolar and working on it now

@VolkrB
Copy link
Contributor Author

VolkrB commented Jan 17, 2017

@adriesse could you comment on the changes to the test when you get a chance?

@adriesse
Copy link
Member

Sure. So this is indeed a more realistic set of test conditions. Did you calculate the expected outputs using an independent method, e.g. Excel?

Unfortunately you dropped the case that tests the night-time tare functionality and this got me thinking that the clipping condition isn't tested either. I guess this wouldn't show up in the code coverage metrics since it is done with a "where" rather than "if", but it still seems like a good idea to include them.

A test case with scalar inputs, like for pvl_snlinverter, would be good as well.

We could stop here, or we could go further--but that might lead to changes to the inverter code. What should happen when Pdc or Vdc is less than zero? There are in fact many realistic and non-realistic combinations of Pdc and Vdc where the model would not produce a realistic output, i.e. it should not be used to extrapolate beyond the normal operating range of the inverter.

What do you think about producing nan's for out-of-range conditions? This behavior could be triggered by an optional parameter "range_check", or even multiple parameters for different checks. Another option would be to return one of more columns of flags.

@VolkrB
Copy link
Contributor Author

VolkrB commented Jan 19, 2017

Made changes based on input from @adriesse

@@ -2051,7 +2051,7 @@ def snlinverter(v_dc, p_dc, inverter):
return ac_power


def adrinverter(v_dc, p_dc, inverter):
def adrinverter(v_dc, p_dc, inverter, vtol=0.10):
Copy link
Member

@adriesse adriesse Jan 19, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated

@adriesse
Copy link
Member

I think this is good, Volker. With the added comment for the optional parameter I think we could stop there.

@adriesse
Copy link
Member

I wonder how many people are actually watching this thread...

@jforbess
Copy link
Contributor

jforbess commented Jan 20, 2017 via email

@wholmgren
Copy link
Member

It's not immediately clear to me if/how the vtol parameter is being tested. Maybe a comment in the test function would be enough. We do need at least one more test that sets the parameter, though.

@adriesse
Copy link
Member

There is one case with v_dc below the threshold determined by vtol, and another with v_dc above. These both produce nan outputs.

I can image tons of additional tests, and garbage in/out scenarios but don't have a good feel for what's appropriate in this context.

@wholmgren
Copy link
Member

Thanks for clarifying, Anton. In that case I recommend one additional test that uses the same input but with vtol set sufficiently large so that the returned values are no longer nan. This way we have a test that actually sets the value of the parameter instead of relying on the default value, thus testing both the presence of the keyword and that its value is respected.

@adriesse
Copy link
Member

That seems a bit like testing the compiler's handling of parameter defaults, doesn't it?!

I have another suggestion. How about putting the inverter parameters in the code rather than reading them from the file? This removes a dependency and also makes clear to the reader what the power and voltage ranges are for the test.

@wholmgren
Copy link
Member

My primary concern is testing that the function can be called when the keyword is used. This ensures that a test will fail if anyone ever tries to remove vtol or change vtol to something else.

Regarding testing the value of the keyword, I am not at all concerned about the interpreter, but rather I am concerned that the value is actually used later in the code. For example, in prototyping the function there could have been a hardcoded vtol = 0.1 line that was not removed. You would be right to say it should be obvious when reviewing the code, but I make these kinds of mistakes all the time. I am pretty sure that this has happened in pvlib python before. I think it was in singlediode, desoto, or maybe the earlier version of the sapm function.

I like the idea of hardcoding the test parameters for the adrinverter function. That makes me realize that we're not explicitly testing the retrieve_sam function for any of the data types. For this PR, we'd only need to worry about testing it with the 'adrinverter' option.

@wholmgren
Copy link
Member

Hi @VolkrB, can you merge/rebase your PR and add one more simple test using vtol as described above? Any test that puts vtol in the function call would be fine with me. Testing using hardcoded values and testing retrieve_sam can be implemented later. Thanks!

@VolkrB
Copy link
Contributor Author

VolkrB commented Feb 7, 2017

Sorry I haven't looked at this in a while. I hope the merge/push looks ok

@wholmgren
Copy link
Member

There are some errors in the test suite that are related to the combination of changing the default ModelChain inverter and the introduction of the vtol/nans.

@VolkrB
Copy link
Contributor Author

VolkrB commented Feb 7, 2017

oops. Seems like that test has non-physical results. I put in the appropriate nans in the params

Copy link
Member

@wholmgren wholmgren left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@VolkrB I noticed a couple of relatively minor things while making one last read through. Sorry to have missed these until now. I think it should be quick to finish, though.

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pac_max and ce_list are wider than this column

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should the column header be longer or the variables shorter?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A longer Column header by one = sign. You'll also need to add one space between all of the Column and the Description sections so that the table stays properly formatted. I guess you should probably also make the Description header longer and/or make the text fit within the existing Description header.

I'm pretty sure that Sphinx would build the table in its present form and spit out a warning. It's not a big deal if it's just one table, but every silly warning makes it slightly harder to identify other issues with the documentation. If you actually build the docs and don't see a warning then we can ignore all this.


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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these lines are a few characters too long

@@ -33,8 +33,8 @@ def cec_dc_snl_ac_system(sam_data):
module_parameters['b'] = 0.05
module_parameters['EgRef'] = 1.121
module_parameters['dEgdT'] = -0.0002677
inverters = sam_data['cecinverter']
inverter = inverters['ABB__MICRO_0_25_I_OUTD_US_208_208V__CEC_2014_'].copy()
inverters = sam_data['adrinverter']
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm only now realizing that this change to the cec_dc_snl_ac_system fixture makes the name no longer correct. I do think it would be worthwhile to make a second test called cec_dc_adr_ac_system or something like it. Then you can revert the change to the test_dc_models fixture, while also updating the test_ac_models ac_systems dictionary.

@VolkrB
Copy link
Contributor Author

VolkrB commented Feb 14, 2017

adrinverter is not being tested in the singlediode dc modelchain, but it's being tested in the ac_model chain. Should I write a new test function for including it in the dc modelchain or maybe there's a better solution?

@wholmgren
Copy link
Member

No need to write a new test function for the dc modelchain. I'm confused about why the 'singlediode' test_dc_models test passes, though. It's supposed to check to the second decimal, but you've (I think inadvertently) changed the value in the first decimal.

@VolkrB
Copy link
Contributor Author

VolkrB commented Feb 14, 2017

failed for check_less_precise = 3, I updated with the correct result

@wholmgren
Copy link
Member

Thanks. The test errors are unrelated to this PR, so I think we're ready to merge. Agreed?

@VolkrB
Copy link
Contributor Author

VolkrB commented Feb 14, 2017

Looks good on my end

@adriesse
Copy link
Member

Agreed.

@wholmgren
Copy link
Member

thanks @VolkrB and @adriesse!

@wholmgren wholmgren merged commit 3d42536 into pvlib:master Feb 14, 2017
@wholmgren wholmgren mentioned this pull request Feb 14, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants