Skip to content
Closed
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.16.2.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ Deprecations

Bug fixes
~~~~~~~~~
* :py:class:`pvlib.pvsystem.Array` now infers First Solar modules listed
as "Thin Film" in the CEC database as CdTe instead of CIGS; "Thin Film"
is otherwise mapped to CIGS. (:issue:`1984`)
* Correct the Series name in ``ModelChain.results.dc_ohmic_losses`` from
``i_mp`` to ``p_dc_ohmic_loss`` when using ``dc_ohms_from_percent``.
(:issue:`1959`, :pull:`2912`)
Expand Down Expand Up @@ -75,6 +78,7 @@ Contributors
* Rajiv Daxini (:ghuser:`rdaxini`)
* jason-writer (:ghuser:`jason-writer`)
* :ghuser:`mmporong`
* :ghuser:`DresdenGman`
* :ghuser:`bfs2021`
* :ghuser:`xiajiahang`
* Jakub Krasuski (:ghuser:`Blizzeq`)
34 changes: 33 additions & 1 deletion pvlib/pvsystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -1109,7 +1109,12 @@ def _infer_cell_type(self):

if 'Technology' in self.module_parameters.keys():
# CEC module parameter set
cell_type = _cell_type_dict[self.module_parameters['Technology']]
technology = self.module_parameters['Technology']
cell_type = _cell_type_dict[technology]
if technology == 'Thin Film' and self._is_first_solar_module():
# First Solar modules labeled "Thin Film" are CdTe,
# but that technology maps to CIGS above
cell_type = 'cdte'
elif 'Material' in self.module_parameters.keys():
# Sandia module parameter set
cell_type = _cell_type_dict[self.module_parameters['Material']]
Expand All @@ -1118,6 +1123,33 @@ def _infer_cell_type(self):

return cell_type

def _is_first_solar_module(self):
"""
Whether the module is made by First Solar, based on the module
name. The name is taken from the ``module`` attribute or, if
``module_parameters`` is a Series, from its name (e.g. the column
name when selected from a database retrieved with
:py:func:`retrieve_sam`). The normalized name must start with
the manufacturer's name, followed by a non-letter boundary, so
that names merely containing "First Solar" do not match.

Returns
-------
bool

"""
names = [self.module,
getattr(self.module_parameters, 'name', None)]
for name in names:
if name is None:
continue
normalized = str(name).lower().replace('_', ' ').strip()
if normalized.startswith('first solar'):
rest = normalized[len('first solar'):]
if not rest or not rest[0].isalpha():
return True
return False

def get_aoi(self, solar_zenith, solar_azimuth):
"""
Get the angle of incidence on the array.
Expand Down
55 changes: 55 additions & 0 deletions tests/test_pvsystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -844,6 +844,61 @@ def test_Array__infer_cell_type():
assert array._infer_cell_type() is None


@pytest.mark.parametrize('module, module_parameters, expected', [
# First Solar modules labeled "Thin Film" are CdTe, but that
# technology is otherwise mapped to CIGS. GH 1984
(None, pd.Series({'Technology': 'Thin Film'},
name='First_Solar__Inc__FS_4117_3'), 'cdte'),
('First Solar, Inc. FS 4117-3', {'Technology': 'Thin Film'}, 'cdte'),
# the name in one channel is enough, even if the other channel
# holds an unrelated name
('Some_Other_Brand_Module', pd.Series(
{'Technology': 'Thin Film'},
name='First_Solar__Inc__FS_4117_3'), 'cdte'),
# "Thin Film" modules from other manufacturers are still CIGS, as
# are "Thin Film" modules with no name to identify them by
(None, pd.Series({'Technology': 'Thin Film'},
name='Auria_Solar_M115000'), 'cigs'),
(None, pd.Series({'Technology': 'Thin Film'}), 'cigs'),
(None, {'Technology': 'Thin Film'}, 'cigs'),
# names merely containing "First Solar" are not First Solar modules
(None, pd.Series({'Technology': 'Thin Film'},
name='NotFirst_Solar_Model'), 'cigs'),
(None, pd.Series({'Technology': 'Thin Film'},
name='First_Solaris_Model'), 'cigs'),
(None, pd.Series({'Technology': 'Thin Film'},
name='OtherBrand_First_Solar_Compatible'), 'cigs'),
# First Solar's crystalline silicon (TetraSun) modules are not CdTe
(None, pd.Series({'Technology': 'Mono-c-Si'},
name='First_Solar__Inc__TS_295_R11_02'), 'monosi'),
# the pre-existing mappings are unchanged
(None, {'Technology': 'CdTe'}, 'cdte'),
(None, {'Technology': 'Multi-c-Si'}, 'multisi'),
(None, {'Material': 'CdTe'}, 'cdte'),
(None, {'Material': 'mc-Si'}, 'multisi'),
])
def test_Array__infer_cell_type_mappings(module, module_parameters,
expected):
array = pvsystem.Array(mount=pvsystem.FixedMount(0, 180),
module=module,
module_parameters=module_parameters)
assert array._infer_cell_type() == expected


def test_Array__infer_cell_type_first_solar_cecmod():
# GH 1984: First Solar modules labeled "Thin Film" in the CEC
# database are CdTe modules
cec_modules = pvsystem.retrieve_sam('cecmod')
module_parameters = cec_modules['First_Solar__Inc__FS_4117_3']
# the override only applies to the "Thin Film" rows; assert the
# premise so a future correction of the database entry cannot
# silently bypass this test's coverage
assert module_parameters['Technology'] == 'Thin Film'
array = pvsystem.Array(mount=pvsystem.FixedMount(0, 180),
module_parameters=module_parameters)
assert array._infer_cell_type() == 'cdte'


def _calcparams_correct_Python_type_numeric_type_cases():
"""
An auxilary function used in the unit tests named
Expand Down
Loading