-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Deprecate/raise error for PVSystem "pass-through" properties #1196
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
Changes from 7 commits
1ee16e1
4d27feb
f5494aa
9852c16
49635f4
7588a81
1824bd6
8fba8a5
01099ed
a29acae
c775bc0
7b96395
71b07da
53ea45d
4619a1e
62a549e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -327,29 +327,32 @@ class ModelChain: | |
| Passed to location.get_airmass. | ||
|
|
||
| dc_model: None, str, or function, default None | ||
| If None, the model will be inferred from the contents of | ||
| system.module_parameters. Valid strings are 'sapm', | ||
| 'desoto', 'cec', 'pvsyst', 'pvwatts'. The ModelChain instance will | ||
| be passed as the first argument to a user-defined function. | ||
| If None, the model will be inferred from the parameters that | ||
| are common to all of system.arrays[i].module_parameters. | ||
| Valid strings are 'sapm', 'desoto', 'cec', 'pvsyst', 'pvwatts'. | ||
| The ModelChain instance will be passed as the first argument | ||
| to a user-defined function. | ||
|
|
||
| ac_model: None, str, or function, default None | ||
| If None, the model will be inferred from the contents of | ||
| system.inverter_parameters and system.module_parameters. Valid | ||
| strings are 'sandia', 'adr', 'pvwatts'. The | ||
| If None, the model will be inferred from the parameters that | ||
| are common to all of system.inverter_parameters. | ||
| Valid strings are 'sandia', 'adr', 'pvwatts'. The | ||
| ModelChain instance will be passed as the first argument to a | ||
| user-defined function. | ||
|
|
||
| aoi_model: None, str, or function, default None | ||
| If None, the model will be inferred from the contents of | ||
| system.module_parameters. Valid strings are 'physical', | ||
| 'ashrae', 'sapm', 'martin_ruiz', 'no_loss'. The ModelChain instance | ||
| will be passed as the first argument to a user-defined function. | ||
| If None, the model will be inferred from the parameters that | ||
| are common to all of system.arrays[i].module_parameters. | ||
| Valid strings are 'physical', 'ashrae', 'sapm', 'martin_ruiz', | ||
| 'no_loss'. The ModelChain instance will be passed as the | ||
| first argument to a user-defined function. | ||
|
|
||
| spectral_model: None, str, or function, default None | ||
| If None, the model will be inferred from the contents of | ||
| system.module_parameters. Valid strings are 'sapm', | ||
| 'first_solar', 'no_loss'. The ModelChain instance will be passed | ||
| as the first argument to a user-defined function. | ||
| If None, the model will be inferred from the parameters that | ||
| are common to all of system.arrays[i].module_parameters. | ||
| Valid strings are 'sapm', 'first_solar', 'no_loss'. | ||
| The ModelChain instance will be passed as the first argument to | ||
| a user-defined function. | ||
|
|
||
| temperature_model: None, str or function, default None | ||
| Valid strings are 'sapm', 'pvsyst', 'faiman', and 'fuentes'. | ||
|
|
@@ -605,9 +608,10 @@ def dc_model(self, model): | |
| model = model.lower() | ||
| if model in _DC_MODEL_PARAMS.keys(): | ||
| # validate module parameters | ||
| module_parameters = tuple( | ||
| array.module_parameters for array in self.system.arrays) | ||
| missing_params = ( | ||
| _DC_MODEL_PARAMS[model] - | ||
| _common_keys(self.system.module_parameters)) | ||
| _DC_MODEL_PARAMS[model] - _common_keys(module_parameters)) | ||
| if missing_params: # some parameters are not in module.keys() | ||
| raise ValueError(model + ' selected for the DC model but ' | ||
| 'one or more Arrays are missing ' | ||
|
|
@@ -630,7 +634,8 @@ def dc_model(self, model): | |
|
|
||
| def infer_dc_model(self): | ||
| """Infer DC power model from Array module parameters.""" | ||
| params = _common_keys(self.system.module_parameters) | ||
| params = _common_keys( | ||
| tuple(array.module_parameters for array in self.system.arrays)) | ||
| if {'A0', 'A1', 'C7'} <= params: | ||
| return self.sapm, 'sapm' | ||
| elif {'a_ref', 'I_L_ref', 'I_o_ref', 'R_sh_ref', 'R_s', | ||
|
|
@@ -645,9 +650,10 @@ def infer_dc_model(self): | |
| return self.pvwatts_dc, 'pvwatts' | ||
| else: | ||
| raise ValueError('could not infer DC model from ' | ||
| 'system.module_parameters. Check ' | ||
| 'system.module_parameters or explicitly ' | ||
| 'set the model with the dc_model kwarg.') | ||
| 'system.arrays[i].module_parameters. Check ' | ||
| 'system.arrays[i].module_parameters or ' | ||
|
||
| 'explicitly set the model with the dc_model ' | ||
| 'kwarg.') | ||
|
|
||
| def sapm(self): | ||
| dc = self.system.sapm(self.results.effective_irradiance, | ||
|
|
@@ -696,7 +702,7 @@ def pvwatts_dc(self): | |
| """Calculate DC power using the PVWatts model. | ||
|
|
||
| Results are stored in ModelChain.results.dc. DC power is computed | ||
| from PVSystem.module_parameters['pdc0'] and then scaled by | ||
| from PVSystem.arrays[i].module_parameters['pdc0'] and then scaled by | ||
| PVSystem.modules_per_string and PVSystem.strings_per_inverter. | ||
|
|
||
| Returns | ||
|
|
@@ -805,7 +811,9 @@ def aoi_model(self, model): | |
| self._aoi_model = partial(model, self) | ||
|
|
||
| def infer_aoi_model(self): | ||
| params = _common_keys(self.system.module_parameters) | ||
| module_parameters = tuple( | ||
| array.module_parameters for array in self.system.arrays) | ||
| params = _common_keys(module_parameters) | ||
| if {'K', 'L', 'n'} <= params: | ||
| return self.physical_aoi_loss | ||
| elif {'B5', 'B4', 'B3', 'B2', 'B1', 'B0'} <= params: | ||
|
|
@@ -816,8 +824,8 @@ def infer_aoi_model(self): | |
| return self.martin_ruiz_aoi_loss | ||
| else: | ||
| raise ValueError('could not infer AOI model from ' | ||
| 'system.module_parameters. Check that the ' | ||
| 'module_parameters for all Arrays in ' | ||
| 'system.arrays[i].module_parameters. Check that ' | ||
| 'the module_parameters for all Arrays in ' | ||
| 'system.arrays contain parameters for ' | ||
| 'the physical, aoi, ashrae or martin_ruiz model; ' | ||
| 'explicitly set the model with the aoi_model ' | ||
|
|
@@ -880,7 +888,9 @@ def spectral_model(self, model): | |
|
|
||
| def infer_spectral_model(self): | ||
| """Infer spectral model from system attributes.""" | ||
| params = _common_keys(self.system.module_parameters) | ||
| module_parameters = tuple( | ||
| array.module_parameters for array in self.system.arrays) | ||
| params = _common_keys(module_parameters) | ||
| if {'A4', 'A3', 'A2', 'A1', 'A0'} <= params: | ||
| return self.sapm_spectral_loss | ||
| elif ((('Technology' in params or | ||
|
|
@@ -890,8 +900,8 @@ def infer_spectral_model(self): | |
| return self.first_solar_spectral_loss | ||
| else: | ||
| raise ValueError('could not infer spectral model from ' | ||
| 'system.module_parameters. Check that the ' | ||
| 'module_parameters for all Arrays in ' | ||
| 'system.arrays[i].module_parameters. Check that ' | ||
| 'the module_parameters for all Arrays in ' | ||
| 'system.arrays contain valid ' | ||
| 'first_solar_spectral_coefficients, a valid ' | ||
| 'Material or Technology value, or set ' | ||
|
|
@@ -940,20 +950,24 @@ def temperature_model(self, model): | |
| # check system.temperature_model_parameters for consistency | ||
| name_from_params = self.infer_temperature_model().__name__ | ||
| if self._temperature_model.__name__ != name_from_params: | ||
| common_params = _common_keys(tuple( | ||
| array.temperature_model_parameters | ||
| for array in self.system.arrays)) | ||
| raise ValueError( | ||
| f'Temperature model {self._temperature_model.__name__} is ' | ||
| f'inconsistent with PVSystem temperature model ' | ||
| f'parameters. All Arrays in system.arrays must have ' | ||
| f'consistent parameters. Common temperature model ' | ||
| f'parameters: ' | ||
| f'{_common_keys(self.system.temperature_model_parameters)}' | ||
| f'parameters: {common_params}' | ||
| ) | ||
| else: | ||
| self._temperature_model = partial(model, self) | ||
|
|
||
| def infer_temperature_model(self): | ||
| """Infer temperature model from system attributes.""" | ||
| params = _common_keys(self.system.temperature_model_parameters) | ||
| temperature_model_parameters = tuple( | ||
| array.temperature_model_parameters for array in self.system.arrays) | ||
| params = _common_keys(temperature_model_parameters) | ||
| # remove or statement in v0.9 | ||
| if {'a', 'b', 'deltaT'} <= params or ( | ||
| not params and self.system.racking_model is None | ||
|
|
@@ -1057,7 +1071,7 @@ def _eff_irrad(module_parameters, total_irrad, spect_mod, aoi_mod): | |
| self.results.spectral_modifier, self.results.aoi_modifier)) | ||
| else: | ||
| self.results.effective_irradiance = _eff_irrad( | ||
| self.system.module_parameters, | ||
| self.system.arrays[0].module_parameters, | ||
| self.results.total_irrad, | ||
| self.results.spectral_modifier, | ||
| self.results.aoi_modifier | ||
|
|
@@ -1481,7 +1495,7 @@ def _prepare_temperature_single_array(self, data, poa): | |
| self.results.cell_temperature = self._get_cell_temperature( | ||
| data, | ||
| poa, | ||
| self.system.temperature_model_parameters | ||
| self.system.arrays[0].temperature_model_parameters | ||
| ) | ||
| if self.results.cell_temperature is None: | ||
| self.temperature_model() | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -64,6 +64,36 @@ def f(*args, **kwargs): | |||||||||||||||||||||||||||||||||||||||
| return f | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| def _check_deprecated_passthrough(func): | ||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||
| Decorator to warn or error when getting and setting the "pass-through" | ||||||||||||||||||||||||||||||||||||||||
| PVSystem properties that have been moved to Array. Emits a warning for | ||||||||||||||||||||||||||||||||||||||||
| PVSystems with only one Array and raises an error for PVSystems with | ||||||||||||||||||||||||||||||||||||||||
| more than one Array. | ||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| @functools.wraps(func) | ||||||||||||||||||||||||||||||||||||||||
| def wrapper(self, *args, **kwargs): | ||||||||||||||||||||||||||||||||||||||||
| pvsystem_attr = func.__name__ | ||||||||||||||||||||||||||||||||||||||||
| class_name = self.__class__.__name__ # PVSystem or SingleAxisTracker | ||||||||||||||||||||||||||||||||||||||||
| overrides = { # some Array attrs aren't the same as PVSystem | ||||||||||||||||||||||||||||||||||||||||
| 'strings_per_inverter': 'strings', | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| array_attr = overrides.get(pvsystem_attr, pvsystem_attr) | ||||||||||||||||||||||||||||||||||||||||
| alternative = f'{class_name}.arrays[i].{array_attr}' | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| if len(self.arrays) > 1: | ||||||||||||||||||||||||||||||||||||||||
| raise AttributeError( | ||||||||||||||||||||||||||||||||||||||||
| f'{class_name}.{pvsystem_attr} not supported for multi-array ' | ||||||||||||||||||||||||||||||||||||||||
| f'systems. Use {alternative} instead.') | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| wrapped = deprecated('0.9', alternative=alternative, removal='0.10', | ||||||||||||||||||||||||||||||||||||||||
| name=f"{class_name}.{pvsystem_attr}")(func) | ||||||||||||||||||||||||||||||||||||||||
| return wrapped(self, *args, **kwargs) | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| return wrapper | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| # not sure if this belongs in the pvsystem module. | ||||||||||||||||||||||||||||||||||||||||
| # maybe something more like core.py? It may eventually grow to | ||||||||||||||||||||||||||||||||||||||||
| # import a lot more functionality from other modules. | ||||||||||||||||||||||||||||||||||||||||
|
|
@@ -1019,72 +1049,86 @@ def pvwatts_ac(self, pdc): | |||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| @property | ||||||||||||||||||||||||||||||||||||||||
| @_unwrap_single_value | ||||||||||||||||||||||||||||||||||||||||
| @_check_deprecated_passthrough | ||||||||||||||||||||||||||||||||||||||||
| def module_parameters(self): | ||||||||||||||||||||||||||||||||||||||||
| return tuple(array.module_parameters for array in self.arrays) | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| @property | ||||||||||||||||||||||||||||||||||||||||
| @_unwrap_single_value | ||||||||||||||||||||||||||||||||||||||||
| @_check_deprecated_passthrough | ||||||||||||||||||||||||||||||||||||||||
| def module(self): | ||||||||||||||||||||||||||||||||||||||||
| return tuple(array.module for array in self.arrays) | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| @property | ||||||||||||||||||||||||||||||||||||||||
| @_unwrap_single_value | ||||||||||||||||||||||||||||||||||||||||
| @_check_deprecated_passthrough | ||||||||||||||||||||||||||||||||||||||||
| def module_type(self): | ||||||||||||||||||||||||||||||||||||||||
| return tuple(array.module_type for array in self.arrays) | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| @property | ||||||||||||||||||||||||||||||||||||||||
| @_unwrap_single_value | ||||||||||||||||||||||||||||||||||||||||
| @_check_deprecated_passthrough | ||||||||||||||||||||||||||||||||||||||||
| def temperature_model_parameters(self): | ||||||||||||||||||||||||||||||||||||||||
| return tuple(array.temperature_model_parameters | ||||||||||||||||||||||||||||||||||||||||
| for array in self.arrays) | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| @temperature_model_parameters.setter | ||||||||||||||||||||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The pvlib-python/pvlib/modelchain.py Lines 503 to 510 in 7eae1fc
For that matter we could implement a pvlib-python/pvlib/modelchain.py Lines 491 to 501 in 7eae1fc
I don't know that one approach is better than another.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not following the issue here, can you expand on what's different about the
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. After trying to come up with an example, I'm realizing the problem is a little different than I first described. There are currently setters for only 4 properties:
The remaining properties do not contain setters. At first I thought this meant that for all of the other properties, a user could set something on a In [5]: system = PVSystem([Array()])
In [6]: system.albedo = 0.3
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-6-d89b04b86f95> in <module>
----> 1 system.albedo = 0.3
AttributeError: can't set attributeSo the issue is that the user can set some but not all of the deprecated attributes. I'd prefer if they could set none or all. I brought up the
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I see, and agree it should be consistent. I think omitting the setters was a (probably unintentional?) breaking change, so I think being able to set all (with a deprecation warning) makes sense. I've added setters where they were missing. |
||||||||||||||||||||||||||||||||||||||||
| @_check_deprecated_passthrough | ||||||||||||||||||||||||||||||||||||||||
| def temperature_model_parameters(self, value): | ||||||||||||||||||||||||||||||||||||||||
| for array in self.arrays: | ||||||||||||||||||||||||||||||||||||||||
| array.temperature_model_parameters = value | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| @property | ||||||||||||||||||||||||||||||||||||||||
| @_unwrap_single_value | ||||||||||||||||||||||||||||||||||||||||
| @_check_deprecated_passthrough | ||||||||||||||||||||||||||||||||||||||||
| def surface_tilt(self): | ||||||||||||||||||||||||||||||||||||||||
| return tuple(array.surface_tilt for array in self.arrays) | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| @surface_tilt.setter | ||||||||||||||||||||||||||||||||||||||||
| @_check_deprecated_passthrough | ||||||||||||||||||||||||||||||||||||||||
| def surface_tilt(self, value): | ||||||||||||||||||||||||||||||||||||||||
| for array in self.arrays: | ||||||||||||||||||||||||||||||||||||||||
| array.surface_tilt = value | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| @property | ||||||||||||||||||||||||||||||||||||||||
| @_unwrap_single_value | ||||||||||||||||||||||||||||||||||||||||
| @_check_deprecated_passthrough | ||||||||||||||||||||||||||||||||||||||||
| def surface_azimuth(self): | ||||||||||||||||||||||||||||||||||||||||
| return tuple(array.surface_azimuth for array in self.arrays) | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| @surface_azimuth.setter | ||||||||||||||||||||||||||||||||||||||||
| @_check_deprecated_passthrough | ||||||||||||||||||||||||||||||||||||||||
| def surface_azimuth(self, value): | ||||||||||||||||||||||||||||||||||||||||
| for array in self.arrays: | ||||||||||||||||||||||||||||||||||||||||
| array.surface_azimuth = value | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| @property | ||||||||||||||||||||||||||||||||||||||||
| @_unwrap_single_value | ||||||||||||||||||||||||||||||||||||||||
| @_check_deprecated_passthrough | ||||||||||||||||||||||||||||||||||||||||
| def albedo(self): | ||||||||||||||||||||||||||||||||||||||||
| return tuple(array.albedo for array in self.arrays) | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| @property | ||||||||||||||||||||||||||||||||||||||||
| @_unwrap_single_value | ||||||||||||||||||||||||||||||||||||||||
| @_check_deprecated_passthrough | ||||||||||||||||||||||||||||||||||||||||
| def racking_model(self): | ||||||||||||||||||||||||||||||||||||||||
| return tuple(array.racking_model for array in self.arrays) | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| @racking_model.setter | ||||||||||||||||||||||||||||||||||||||||
| @_check_deprecated_passthrough | ||||||||||||||||||||||||||||||||||||||||
| def racking_model(self, value): | ||||||||||||||||||||||||||||||||||||||||
| for array in self.arrays: | ||||||||||||||||||||||||||||||||||||||||
| array.racking_model = value | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| @property | ||||||||||||||||||||||||||||||||||||||||
| @_unwrap_single_value | ||||||||||||||||||||||||||||||||||||||||
| @_check_deprecated_passthrough | ||||||||||||||||||||||||||||||||||||||||
| def modules_per_string(self): | ||||||||||||||||||||||||||||||||||||||||
| return tuple(array.modules_per_string for array in self.arrays) | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| @property | ||||||||||||||||||||||||||||||||||||||||
| @_unwrap_single_value | ||||||||||||||||||||||||||||||||||||||||
| @_check_deprecated_passthrough | ||||||||||||||||||||||||||||||||||||||||
| def strings_per_inverter(self): | ||||||||||||||||||||||||||||||||||||||||
| return tuple(array.strings for array in self.arrays) | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the alphabetization of the list preceding this one and would prefer to see that here too.