-
Notifications
You must be signed in to change notification settings - Fork 1.1k
AttributeError when using ModelChain with multiple Arrays #1759
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
Comments
I'd like to take a look at this but not clear from the email thread how to reproduce (i.e. namely what the systems object contains). |
@matsuobasho the error results when a PVSystem has multiple arrays, but the temperature model parameters are assigned to the PVSystem rather than to each Array. We haven't discussed what an appropriate fix would be. Ideas are welcome. |
@cwhanse , I don't have much experience with multi-array modeling so I tried to recreate the error in the post by piecing together what I saw (since it wasn't a copy-paste reproducible example). In the course of this, I encountered my own error. import numpy as np
import pandas as pd
from pvlib import pvsystem
from pvlib.location import Location
from pvlib.modelchain import ModelChain
from pvlib.temperature import TEMPERATURE_MODEL_PARAMETERS
arr_num = 3
target_mods = cec_modules.iloc[:,np.random.randint(cec_modules.shape[1], size=arr_num)].columns
name = ['arr_'+ str(i) for i in range(arr_num)]
azimuth = [180, 200, 210]
strings = [5, 10, 8]
tilt = [20, 25, 29]
mods_per_string = [12, 14, 16]
arrays = pd.DataFrame({'name': name,
'module': target_mods,
'azimuth': azimuth,
'strings': strings,
'tilt': tilt,
'modules_per_string': mods_per_string})
temp_params = TEMPERATURE_MODEL_PARAMETERS["pvsyst"]["freestanding"]
location = Location(latitude=32.2, longitude=-110.9)
pv_arrays = []
for i in range(len(arrays)):
mount = pvsystem.FixedMount(surface_tilt=arrays.iloc[i]["tilt"], surface_azimuth=arrays.iloc[i]["azimuth"])
module = cec_modules[arrays.iloc[i]["module"]]
pv_arrays.append(
pvsystem.Array(
mount=mount,
module_parameters=module,
strings=arrays.iloc[i]["strings"],
modules_per_string=arrays.iloc[i]["modules_per_string"],
name=arrays.iloc[i]["name"],
)
)
# create the PV system
inverter = cec_inverters.iloc[:,1000] # random inverter for testing
pv_system = pvsystem.PVSystem(
arrays=pv_arrays,
inverter_parameters=inverter,
temperature_model_parameters=temp_params,
name='dummy_name',
)
# create the model chain
modelchain = ModelChain(
pv_system, location, name='dummy_name', aoi_model="physical", temperature_model="sapm"
) The error comes on the I see this error is referenced here, but not really following what role Please help on this one and I'll proceed to investigate possible solutions for the issue at hand. Thanks. |
@matsuobasho same line, different outcome. In your example, The offending logic is in this
Since v0.9 we said we would require If we remove the |
@cwhanse , thanks for the clarification, understood. Is there an issue with passing import numpy as np
import pandas as pd
from pvlib import pvsystem
from pvlib.location import Location
from pvlib.temperature import TEMPERATURE_MODEL_PARAMETERS
from pvlib.modelchain import ModelChain
cec_modules = pvsystem.retrieve_sam('CECMod')
cec_inverters = pvsystem.retrieve_sam('cecinverter')
arr_num = 3
target_mods = cec_modules.iloc[:,np.random.randint(cec_modules.shape[1], size=arr_num)].columns
name = ['arr_'+ str(i) for i in range(arr_num)]
azimuth = [180, 200, 210]
strings = [5, 10, 8]
tilt = [20, 25, 29]
mods_per_string = [12, 14, 16]
arrays = pd.DataFrame({'name': name,
'module': target_mods,
'azimuth': azimuth,
'strings': strings,
'tilt': tilt,
'modules_per_string': mods_per_string})
temp_params = TEMPERATURE_MODEL_PARAMETERS["pvsyst"]["freestanding"]
location = Location(latitude=32.2, longitude=-110.9)
inverter = cec_inverters.iloc[:,1000] # random inverter for testing
pv_arrays = []
for i in range(len(arrays)):
mount = pvsystem.FixedMount(surface_tilt=arrays.iloc[i]["tilt"], surface_azimuth=arrays.iloc[i]["azimuth"])
module = cec_modules[arrays.iloc[i]["module"]]
pv_arrays.append(
pvsystem.Array(
mount=mount,
module_parameters=module,
strings=arrays.iloc[i]["strings"],
modules_per_string=arrays.iloc[i]["modules_per_string"],
name=arrays.iloc[i]["name"],
temperature_model_parameters = temp_params
)
)
pv_system = pvsystem.PVSystem(
arrays=pv_arrays,
inverter_parameters=inverter,
temperature_model_parameters=temp_params,
name='dummy_name',
)
# create the model chain
modelchain = ModelChain(
pv_system, location, name='dummy_name', aoi_model="physical", temperature_model="pvsyst"
) If this is acceptable, then it should be just a matter of specifying in the PVSystem documentation and tutorial that each array requires its own Offering the simplest solution that comes to mind, quite possible I'm not grasping the extent of the problem. |
@matsuobasho the goal is that The short path to a fix is to do as you describe: make the documentation clear that |
I realized that my previous formulation has a redundancy that is confusing to a user. Namely, I assign the def __init__(self,
arrays=None,
surface_tilt=0, surface_azimuth=180,
albedo=None, surface_type=None,
module=None, module_type=None,
module_parameters=None,
temperature_model_parameters=None,
modules_per_string=1, strings_per_inverter=1,
inverter=None, inverter_parameters=None,
racking_model=None, losses_parameters=None, name=None):
if (arrays is not None) & (temperature_model_parameters is not None):
raise ValueError("If using arrays argument, the "
"temperature_model_parameters must be null.") I propose that over checking if both are assigned to make it explicit to the user that the argument's use is mutually exclusive to either passing it to a system, or to arrays when there are multiple arrays. Also, is it fine to just delete the or part of |
I don't think that check is needed. If we did that for |
I vote to simply delete the |
I deleted the When we create an When we pass the arguments to def _infer_temperature_model_params(self):
# try to infer temperature model parameters from racking_model
# and module_type
param_set = f'{self.mount.racking_model}_{self.module_type}'
if param_set in temperature.TEMPERATURE_MODEL_PARAMETERS['sapm']:
return temperature._temperature_model_params('sapm', param_set)
elif 'freestanding' in param_set:
return temperature._temperature_model_params('pvsyst',
'freestanding')
elif 'insulated' in param_set: # after SAPM to avoid confusing keys
return temperature._temperature_model_params('pvsyst',
'insulated')
else:
return {} This leads to returning an empty dictionary, and eventually the error when this |
Regarding
Any other case should result in an error, because there isn't enough information for a We could check if |
Thanks for that breakdown. So something like this in if temperature_model_parameters is None:
self.temperature_model_parameters = \
self._infer_temperature_model_params()
else:
self.temperature_model_parameters = temperature_model_parameters
if temperature_model_parameters is None:
raise ValueError("The `temperature_model_parameters` is empty "
"after an attempt to infer it. "
"Pass the following arguments either to `Array` "
"or to `PVSystem`: "
"`temperature_model_parameters` "
"or `racking_model` and `module_type`.") Also, I think it would be be beneficial to add your breakdown to the PV System and Arrays section of the PVSystem tutorial. Finally, would this modification call for writing new tests? Let me know if the proposed modifications make sense and I'll proceed. |
That message looks fine. I'd put a comma before "or We would want a new test, that simply checks that the error is raised. Here's a pattern to follow. |
As I was writing and testing, I realized the message needs to be more informative to spell out the 3 options explicitly to get rid of the error: if self.temperature_model_parameters is None:
raise ValueError("The `temperature_model_parameters` is empty "
"after an attempt to infer it. "
"Pass either `temperature_model_parameters` to "
"`Array` or `PVSystem` (if not passing arrays), or "
"`racking_module` to the `Array` `mount` "
"object and `module_type` to `Array.") However, the code also runs without fail when we don't pass temp_params = TEMPERATURE_MODEL_PARAMETERS["pvsyst"]["freestanding"]
arr_num = 3
target_mods = cec_modules.iloc[:,np.random.randint(cec_modules.shape[1], size=arr_num)].columns
name = ['arr_'+ str(i) for i in range(arr_num)]
azimuth = [180, 200, 210]
strings = [5, 10, 8]
tilt = [20, 25, 29]
mods_per_string = [12, 14, 16]
arrays = pd.DataFrame({'name': name,
'module': target_mods,
'azimuth': azimuth,
'strings': strings,
'tilt': tilt,
'modules_per_string': mods_per_string})
location = Location(latitude=32.2, longitude=-110.9)
# create the PV system
inverter = cec_inverters.iloc[:,1000]
pv_arrays = []
for i in range(len(arrays)):
mount = pvsystem.FixedMount(surface_tilt=arrays.iloc[i]["tilt"], surface_azimuth=arrays.iloc[i]["azimuth"], racking_model='freestanding')
module = cec_modules[arrays.iloc[i]["module"]]
pv_arrays.append(
pvsystem.Array(
mount=mount,
module_parameters=module,
strings=arrays.iloc[i]["strings"],
modules_per_string=arrays.iloc[i]["modules_per_string"],
name=arrays.iloc[i]["name"],
)
)
pv_system = pvsystem.PVSystem(
arrays=pv_arrays,
inverter_parameters=inverter,
temperature_model_parameters=temp_params,
name='dummy_name'
)
# create the model chain
modelchain = ModelChain(
pv_system, location, name='dummy_name', aoi_model="physical", temperature_model="pvsyst"
) That's because when we get to Seems like different |
|
Ok. Is it sufficient to create the test that the error is raised with one non-pvsyst |
We can parameterize the test so that it loops over combinations of |
I'll give it a shot, will post what I come up with. Thanks. |
Here's my version, which I intend to place right before @pytest.mark.parametrize("racking_model, module_type",
[(None, None),
(None, 'glass_polymer'),
(None, 'glass_glass'),
('open_rack', None),
('close_mount', None),
('close_mount', 'glass_polymer'),
])
def test_Array_temperature_model_params_error(racking_model, module_type):
mount = pvsystem.FixedMount(surface_tilt=20, surface_azimuth=180,
racking_model=racking_model)
with pytest.raises(ValueError, match='`temperature_model_parameters` is empty'):
pvsystem.Array(mount = mount, module_type=module_type) A couple of points:
But
|
The test parameters look OK to me. The combinations of
|
Since we are now requiring
|
Thanks for this effort, more than I anticipated :) I look forward to seeing the PR. |
|
@matsuobasho at this point I think it would be easier if you could submit a PR, then we can see how the code and test are changed. |
@cwhanse , opened a PR as per your suggestion |
Sorry for not chiming in earlier. I was a bit confused about what exactly the problem was here, but I think I have a clear view now, and I think . Sorry in advance for the long comment... There are a two distinct issues here:
This occurs when the user makes a mistake and doesn't supply
I think resolving this is a simple bugfix: fix the error message (and type) being raised. No deprecation period needed or desired.
This was supposed to be removed in v0.9, but it was overlooked. I think resolving this problem is also a simple bugfix, not a breaking change: the behavior in question only applies when no parameters are supplied, and it's not like the model can be run in that situation anyway. Anyway, I think both of these problems can be solved by just removing the second half of that offending
In cases like these, the user shouldn't be burdened and confused by making them specify a parameter set that won't get used anyway. I think what we want is for un-inferrable parameters to only cause an error when the user runs something that actually needs those parameters. |
I think you answer this above, but there quite a few combinations for me to wrap my head around this. I don't specify the temperature model or the racking model in the code below (after removing the import numpy as np
import pandas as pd
from pvlib import pvsystem
from pvlib.location import Location
from pvlib.modelchain import ModelChain
cec_modules = pvsystem.retrieve_sam('CECMod')
cec_inverters = pvsystem.retrieve_sam('cecinverter')
arr_num = 3
target_mods = cec_modules.iloc[:,np.random.randint(cec_modules.shape[1], size=arr_num)].columns
name = ['arr_'+ str(i) for i in range(arr_num)]
azimuth = [180, 200, 210]
strings = [5, 10, 8]
tilt = [20, 25, 29]
mods_per_string = [12, 14, 16]
arrays = pd.DataFrame({'name': name,
'module': target_mods,
'azimuth': azimuth,
'strings': strings,
'tilt': tilt,
'modules_per_string': mods_per_string})
location = Location(latitude=32.2, longitude=-110.9)
# create the PV system
inverter = cec_inverters.iloc[:,1000] # random inverter for testing
pv_arrays = []
for i in range(len(arrays)):
mount = pvsystem.FixedMount(surface_tilt=arrays.iloc[i]["tilt"], surface_azimuth=arrays.iloc[i]["azimuth"])
module = cec_modules[arrays.iloc[i]["module"]]
pv_arrays.append(
pvsystem.Array(
mount=mount,
module_parameters=module,
strings=arrays.iloc[i]["strings"],
modules_per_string=arrays.iloc[i]["modules_per_string"],
name=arrays.iloc[i]["name"],
module_type='glass_glass'
)
)
pv_system = pvsystem.PVSystem(
arrays=pv_arrays,
inverter_parameters=inverter,
name='dummy_name'
)
# create the model chain
modelchain = ModelChain(
pv_system, location, name='dummy_name', aoi_model="physical", temperature_model="sapm"
) I get an error on Also, is the error in the right place here - in other words, |
Yes, this is the desired behavior. Doesn't Of course, suggestions for clarifying that error text are welcome! But that's a separate issue from this one.
IMHO yes, this makes sense. With the |
@kandersolar , that explanation makes sense, thanks. Earlier in this thread, @cwhanse wrote the following:
So note what happens in the following scenario where we're not specifying arrays manually: modules = pvsystem.retrieve_sam('cecmod')
module_parameters = modules['Canadian_Solar_Inc__CS5P_220M']
inverter_parameters = {'pdc0': 5000, 'eta_inv_nom': 0.96}
system = pvsystem.PVSystem(inverter_parameters=inverter_parameters,
module_parameters=module_parameters)
# create the model chain
modelchain = ModelChain(
system, location, name='dummy_name', aoi_model="physical", temperature_model="pvsyst"
) We get the same error as in my previous example: Maybe the behavior is correct but the second sentence in this error message is definitely off since I understand we want to be able to instantiate |
Fair! Want to open a new issue to discuss clarifying that message? Note that this same kind of message is used in several places in ModelChain (open modelchain.py and search for the text "could not infer"), so if we change it in one spot, it probably makes sense to change everywhere. Either way, it's a separate issue from this one. |
Yes, #1946 |
Describe the bug
A clear and concise description of what the bug is.
Not sure this will be either clear or concise, but: an AttributeError is generated when instantiating a ModelChain with a PVSystem that has multiple Arrays.
The AttributeError results from this line which references a
PVSystem.racking_model
attribute. In turn, that line is reached from here where we're trying to ensure consistency betweenModelChain.temperature_model
(a string) and the array temperature models (I think).To Reproduce
See the discussion here which reports the issue.
Expected behavior
I don't see that this user made a mistake. I would not expect the AttributeError.
Versions:
pvlib.__version__
: 0.9.5The text was updated successfully, but these errors were encountered: