-
Notifications
You must be signed in to change notification settings - Fork 43
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
Add detailed PV model (pvsamv1) #99
Merged
Merged
Changes from 5 commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
d27518d
Improve value assignment in power_source
Matthew-Boyd 5ba2820
Add detailed PV model (pvsamv1)
Matthew-Boyd 5c3a671
Add custom PV model capability
Matthew-Boyd 99a1ca2
Fix test interaction
Matthew-Boyd 67f9690
Properly enforce coherence between detailed plant attributes
Matthew-Boyd 83ee966
Verify capacity from electrical parameters
Matthew-Boyd 65e81ec
Explicitly specify use of detailed pv model (pvsamv1)
Matthew-Boyd 8390997
Refactor find_strings_per_inverter into size_electrical_parameters
Matthew-Boyd 09a38e7
Refactor find_modules_per_string and add test
Matthew-Boyd bbcdc5a
Demonstrate use of autosizing electrical parameters
Matthew-Boyd 312df60
Move electrical sizing test to test_layout file
Matthew-Boyd 307bf18
Add documentation of new PV model options and their default behavior
Matthew-Boyd 32c5949
Add detailed pv model getters and setters
Matthew-Boyd 216011b
Utilize system capacity property setter
Matthew-Boyd e1729d7
Fix docstrings in pv_design_utils.py
Matthew-Boyd ba4e32c
Fix subarray designation
Matthew-Boyd 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 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,103 @@ | ||
from typing import Union, Optional, Sequence, Any | ||
|
||
import PySAM.Pvsamv1 as Pvsam | ||
import PySAM.Singleowner as Singleowner | ||
|
||
from hybrid.power_source import * | ||
from hybrid.layout.pv_design_utils import * | ||
from hybrid.layout.pv_layout import PVLayout, PVGridParameters | ||
from hybrid.dispatch.power_sources.pv_dispatch import PvDispatch | ||
|
||
|
||
class DetailedPVPlant(PowerSource): | ||
_system_model: Pvsam.Pvsamv1 | ||
_financial_model: Union[Any, Singleowner.Singleowner] | ||
_layout: Union[Any, PVLayout] | ||
_dispatch: PvDispatch | ||
|
||
def __init__(self, | ||
site: SiteInfo, | ||
pv_config: dict): | ||
""" | ||
|
||
:param pv_config: dict, with following keys: | ||
'tech_config': dict, contains parameters for pvsamv1 technology model | ||
'fin_config': dict, contains `model_type` and any inputs for chosen financial model type | ||
'layout_params': optional DetailedPVParameters, the design vector w/ values. Required for layout modeling | ||
'layout_config': optional dict, contains all keys for PVLayoutConfig dataclass. Required for layout modeling | ||
""" | ||
if 'tech_config' not in pv_config.keys(): | ||
raise ValueError | ||
|
||
system_model = Pvsam.default("FlatPlatePVSingleOwner") | ||
financial_model = Singleowner.from_existing(system_model, "FlatPlatePVSingleOwner") | ||
|
||
super().__init__("SolarPlant", site, system_model, financial_model) | ||
|
||
self._system_model.SolarResource.solar_resource_data = self.site.solar_resource.data | ||
|
||
self.dc_degradation = [0] | ||
|
||
params: Optional[PVGridParameters] = None | ||
if 'layout_params' in pv_config.keys(): | ||
params: PVGridParameters = pv_config['layout_params'] | ||
self._layout = PVLayout(site, system_model, params) | ||
|
||
self._dispatch: PvDispatch = None | ||
|
||
self.processed_assign(pv_config['tech_config']) | ||
|
||
def processed_assign(self, params): | ||
""" | ||
Assign attributes from dictionary with additional processing | ||
to enforce coherence between attributes | ||
""" | ||
self.assign(params) | ||
n_strings, system_capacity, n_inverters = align_from_capacity( | ||
system_capacity_target=self.value('system_capacity'), | ||
modules_per_string=self.value('subarray1_modules_per_string'), | ||
module_power=get_module_power(self._system_model) * 1e-3, | ||
inverter_power=get_inverter_attribs(self._system_model)['P_ac'] * 1e-3, | ||
n_inverters_orig=self.value('inverter_count') | ||
) | ||
self._system_model.SystemDesign.subarray1_nstrings = n_strings | ||
self._system_model.SystemDesign.system_capacity = system_capacity | ||
self._system_model.SystemDesign.inverter_count = n_inverters | ||
|
||
@property | ||
def system_capacity(self) -> float: | ||
"""pass through to established name property""" | ||
return self.system_capacity_kw | ||
|
||
@system_capacity.setter | ||
def system_capacity(self, size_kw: float): | ||
"""pass through to established name setter""" | ||
self.system_capacity_kw = size_kw | ||
|
||
@property | ||
def system_capacity_kw(self) -> float: | ||
return self._system_model.value('system_capacity') # [kW] DC | ||
|
||
@system_capacity_kw.setter | ||
def system_capacity_kw(self, size_kw: float): | ||
""" | ||
Sets the system capacity and updates the system, cost and financial model | ||
:param size_kw: DC system size in kW | ||
:return: | ||
""" | ||
self._system_model.value('system_capacity', size_kw) | ||
self._layout.set_system_capacity(size_kw) | ||
|
||
@property | ||
def dc_degradation(self) -> float: | ||
"""Annual DC degradation for lifetime simulations [%/year]""" | ||
return self._system_model.Lifetime.dc_degradation | ||
|
||
@dc_degradation.setter | ||
def dc_degradation(self, dc_deg_per_year: Sequence): | ||
self._system_model.Lifetime.dc_degradation = dc_deg_per_year | ||
|
||
@property | ||
def dc_ac_ratio(self) -> float: | ||
return self.system_capacity * 1e3 / \ | ||
(self.value('inverter_count') * get_inverter_power(self._system_model)) | ||
dguittet marked this conversation as resolved.
Show resolved
Hide resolved
|
This file contains 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 |
---|---|---|
|
@@ -12,6 +12,7 @@ | |
from tools.analysis import create_cost_calculator | ||
from hybrid.sites import SiteInfo | ||
from hybrid.pv_source import PVPlant | ||
from hybrid.detailed_pv_plant import DetailedPVPlant | ||
from hybrid.wind_source import WindPlant | ||
from hybrid.tower_source import TowerPlant | ||
from hybrid.trough_source import TroughPlant | ||
|
@@ -148,7 +149,12 @@ def __init__(self, | |
power_sources[k.lower()] = power_sources.pop(k) | ||
|
||
if 'pv' in power_sources.keys(): | ||
self.pv = PVPlant(self.site, power_sources['pv']) | ||
if 'pv_plant' in power_sources['pv']: | ||
self.pv = power_sources['pv']['pv_plant'] # User instantiated plant | ||
elif 'tech_config' in power_sources['pv']: | ||
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 wonder if there's a more explicit way to let users choose between creating a DetailedPVPlant vs a PVPlant? 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 was wondering this too... let's discuss. |
||
self.pv = DetailedPVPlant(self.site, power_sources['pv']) # PVSAMv1 plant | ||
dguittet marked this conversation as resolved.
Show resolved
Hide resolved
|
||
else: | ||
self.pv = PVPlant(self.site, power_sources['pv']) # PVWatts plant | ||
self.power_sources['pv'] = self.pv | ||
logger.info("Created HybridSystem.pv with system size {} mW".format(power_sources['pv'])) | ||
if 'wind' in power_sources.keys(): | ||
|
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.
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.
How I've tried to handle this issue of interdependent variables is shown in the wind source. For instance, in the
__init__
, the turbine rating is assigned from the config, and the rating then changes the system capacity:HOPP/hybrid/wind_source.py
Line 71 in c4e2a55
The
turb_rating
is actually a Python class property with custom-defined getters and setters, as you can see here:HOPP/hybrid/wind_source.py
Line 130 in c4e2a55
Whenever the setter
self.turb_rating = x
is called, we end up going to that custom function. That custom function's job is to make everything in PySAM consistent to the best of its ability.When you have these new Pvsamv1 variables that users are supposed to use, you will need to add these class properties to control how they are accessed and modified. We want to enable the ability for the user to do:
and have the end resulting Pvsamv1 model consistent.
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've been running into trouble with the way some properties are set up in HOPP, where you get caught in loops, and that's why I'm setting these directly. In this case, the actual system_capacity has been calculated (without designing a new layout) and should be set in the pysam model as-is, where the property is set up to assume it's been given the target system_capacity and it then calculates a new layout and sets a different system_capacity.