You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
ModelChain._run_from_effective_irrad (and the dc_ohmic_model step it calls, dc_ohms_from_percent) determines whether self.results.dc should be treated as a tuple based solely on whether the input data passed to run_model_from_poa() / run_model_from_effective_irradiance() was a list/tuple, rather than on PVSystem.num_arrays.
Meanwhile, PVSystem.dc_ohms_from_percent() is decorated with the _unwrap_single_value helper, which returns a scalar float (not a tuple) whenever the PVSystem has exactly one Array, regardless of how the input data was shaped.
When a user passes a length-1 list/tuple of POA (or effective irradiance) DataFrames into a ModelChain built from a PVSystem with a single Array, this mismatch causes:
self.results.dc to be stored internally as a 1-element tuple (because the input was a list), while
Rw = self.system.dc_ohms_from_percent() to be returned as a plain float (because num_arrays == 1).
dc_ohms_from_percent() in modelchain.py then does:
if isinstance(self.results.dc, tuple):
self.results.dc_ohmic_losses = tuple(
pvsystem.dc_ohmic_losses(Rw, df['i_mp'])
for Rw, df in zip(Rw, self.results.dc)
)
Since Rw is a float, zip(Rw, self.results.dc) fails with:
TypeError: 'float' object is not iterable
This only reproduces for single-Array PVSystems whose POA/effective irradiance data happens to be passed as a list/tuple rather than a bare DataFrame; with multiple Arrays it works fine because both code paths agree on "tuple," and with a bare DataFrame input on a single-Array system it also works fine because both code paths agree on "scalar."
To Reproduce
Steps to reproduce the behavior:
Build a PVSystem with a single Array using a Sandia module from pvlib.pvsystem.retrieve_sam, and set array_losses_parameters={"dc_ohmic_percent": <some value>} on that Array.
Build a ModelChain for this system with dc_ohmic_model="dc_ohms_from_percent".
Prepare a single POA DataFrame poa_df, then wrap it in a one-element list: poa_input = [poa_df].
Call mc.run_model_from_poa(poa_input).
See error:
TypeError: 'float' object is not iterable
raised from ModelChain.dc_ohms_from_percent at the zip(Rw, self.results.dc)
line in modelchain.py.
ModelChain.run_model_from_poa (and run_model_from_effective_irradiance) should produce consistent, correct results regardless of whether the caller wraps a single Array's data in a one-element list/tuple or passes it as a bare DataFrame. Either:
(a) ModelChain should normalize/unwrap single-element list input to match num_arrays == 1 before running the rest of the chain, or
(b) dc_ohms_from_percent's tuple/scalar decision should be based on self.results.dc's actual type consistently with how Rw is computed, e.g. by wrapping Rw in a tuple whenever self.results.dc is a tuple (and vice versa), so that no TypeError occurs and ohmic losses are computed correctly either way.
Screenshots
N/A (traceback only):
pv_farm_sim/simulate.py:59: in run_system_model
mc.run_model_from_poa(poa_list)
.venv/lib/python3.14/site-packages/pvlib/modelchain.py:1807: in run_model_from_poa
self._run_from_effective_irrad(data)
.venv/lib/python3.14/site-packages/pvlib/modelchain.py:1834: in _run_from_effective_irrad
self.dc_ohmic_model()
.venv/lib/python3.14/site-packages/pvlib/modelchain.py: in dc_ohms_from_percent
for Rw, df in zip(Rw, self.results.dc)
TypeError: 'float' object is not iterable
Versions:
pvlib.__version__: 0.15.2
pandas.__version__: 3.0.3
python: 3.14
Additional context
Workaround: only pass a list/tuple to run_model_from_poa/ run_model_from_effective_irradiance when len(pv_system.arrays) > 1; pass the bare DataFrame otherwise. This avoids the mismatch entirely.
The bug is specific to the combination of (single Array) + (list/tuple input) + (dc_ohmic_model="dc_ohms_from_percent"). Other DC/loss models in the chain don't perform this tuple-vs-scalar cross-check, so they don't
surface the inconsistency — which is why removing dc_ohmic_model="dc_ohms_from_percent" "fixes" the symptom without fixing
the underlying shape mismatch.
Describe the bug
ModelChain._run_from_effective_irrad(and thedc_ohmic_modelstep it calls,dc_ohms_from_percent) determines whetherself.results.dcshould be treated as a tuple based solely on whether the input data passed torun_model_from_poa()/run_model_from_effective_irradiance()was a list/tuple, rather than onPVSystem.num_arrays.Meanwhile,
PVSystem.dc_ohms_from_percent()is decorated with the_unwrap_single_valuehelper, which returns a scalar float (not a tuple) whenever thePVSystemhas exactly oneArray, regardless of how the input data was shaped.When a user passes a length-1 list/tuple of POA (or effective irradiance) DataFrames into a ModelChain built from a PVSystem with a single Array, this mismatch causes:
self.results.dcto be stored internally as a 1-element tuple (because the input was a list), whileRw = self.system.dc_ohms_from_percent()to be returned as a plain float (becausenum_arrays == 1).dc_ohms_from_percent()inmodelchain.pythen does:Since
Rwis a float,zip(Rw, self.results.dc)fails with:This only reproduces for single-Array PVSystems whose POA/effective irradiance data happens to be passed as a list/tuple rather than a bare DataFrame; with multiple Arrays it works fine because both code paths agree on "tuple," and with a bare DataFrame input on a single-Array system it also works fine because both code paths agree on "scalar."
To Reproduce
Steps to reproduce the behavior:
Build a
PVSystemwith a singleArrayusing a Sandia module frompvlib.pvsystem.retrieve_sam, and setarray_losses_parameters={"dc_ohmic_percent": <some value>}on that Array.Build a
ModelChainfor this system withdc_ohmic_model="dc_ohms_from_percent".Prepare a single POA DataFrame
poa_df, then wrap it in a one-element list:poa_input = [poa_df].Call
mc.run_model_from_poa(poa_input).See error:
TypeError: 'float' object is not iterable
raised from
ModelChain.dc_ohms_from_percentat thezip(Rw, self.results.dc)line in
modelchain.py.Minimal, runnable reproduction:
Expected behavior
ModelChain.run_model_from_poa(andrun_model_from_effective_irradiance) should produce consistent, correct results regardless of whether the caller wraps a single Array's data in a one-element list/tuple or passes it as a bare DataFrame. Either:(a)
ModelChainshould normalize/unwrap single-element list input to matchnum_arrays == 1before running the rest of the chain, or(b)
dc_ohms_from_percent's tuple/scalar decision should be based onself.results.dc's actual type consistently with howRwis computed, e.g. by wrappingRwin a tuple wheneverself.results.dcis a tuple (and vice versa), so that noTypeErroroccurs and ohmic losses are computed correctly either way.Screenshots
N/A (traceback only):
Versions:
pvlib.__version__: 0.15.2pandas.__version__: 3.0.3Additional context
run_model_from_poa/run_model_from_effective_irradiancewhenlen(pv_system.arrays) > 1; pass the bare DataFrame otherwise. This avoids the mismatch entirely.dc_ohmic_model="dc_ohms_from_percent"). Other DC/loss models in the chain don't perform this tuple-vs-scalar cross-check, so they don'tsurface the inconsistency — which is why removing
dc_ohmic_model="dc_ohms_from_percent""fixes" the symptom without fixingthe underlying shape mismatch.