Repository: pvlib/pvlib-python

Issue:
ValueError: ModelChain.run_from_effective_irradiance([weather]) when only providing temp_air and wind_speed
**Describe the bug**
According to the (new) docstring for `ModelChain.run_from_effective_irradiance`, cell temperature can be calculated from temperature_model using `'effective_irradiance'`. This is not the case when using one or more arrays 
https://github.com/pvlib/pvlib-python/blame/master/pvlib/modelchain.py#L1589-L1606

**To Reproduce**
```python
from copy import deepcopy
import pandas as pd
from pvlib.location import Location
from pvlib.pvsystem import Array, PVSystem
from pvlib.modelchain import ModelChain


array_params = {
    "surface_tilt": 32.0,
    "surface_azimuth": 180.0,
    "module": "Canadian_Solar_Inc__CS5P_220M",
    "albedo": 0.2,
    "temperature_model_parameters": {
        "u_c": 29.0,
        "u_v": 0.0,
        "eta_m": 0.1,
        "alpha_absorption": 0.9,
    },
    "strings": 5,
    "modules_per_string": 7,
    "module_parameters": {
        "alpha_sc": 0.004539,
        "gamma_ref": 1.2,
        "mu_gamma": -0.003,
        "I_L_ref": 5.11426,
        "I_o_ref": 8.10251e-10,
        "R_sh_ref": 381.254,
        "R_sh_0": 400.0,
        "R_s": 1.06602,
        "cells_in_series": 96,
        "R_sh_exp": 5.5,
        "EgRef": 1.121,
    },
}
inverter_parameters = {
    "Paco": 250.0,
    "Pdco": 259.589,
    "Vdco": 40.0,
    "Pso": 2.08961,
    "C0": -4.1e-05,
    "C1": -9.1e-05,
    "C2": 0.000494,
    "C3": -0.013171,
    "Pnt": 0.075,
}


location = Location(latitude=33.98, longitude=-115.323, altitude=2300)

array_sys = PVSystem(
    arrays=[
        Array(**array_params, name=0),
    ],
    inverter_parameters=inverter_parameters,
)
weather = pd.DataFrame(
    {
        "effective_irradiance": [1100.0, 1101.0],
        "temp_air": [25.0, 26.0],
        "wind_speed": [10.0, 10.0],
    },
    index=pd.DatetimeIndex(
        [pd.Timestamp("2021-01-20T12:00-05:00"), pd.Timestamp("2021-01-20T12:05-05:00")]
    ),
)
mc0 = ModelChain(
    array_sys,
    location,
    aoi_model="no_loss",
    spectral_model="no_loss",
)
mc1 = deepcopy(mc0)

mc0.run_model_from_effective_irradiance(weather)
assert isinstance(mc0.results.cell_temperature, pd.Series)


mc1.run_model_from_effective_irradiance([weather])  # ValueError

```

**Expected behavior**
Running the model with both `weather` and `[weather]` work


**Versions:**
 - ``pvlib.__version__``: 0.9.0-alpha.2+5.gb40df75


Discussion:
@cwhanse we overlooked updating this in #1129:

https://github.com/pvlib/pvlib-python/blob/b40df75ddbc467a113b87643c1faef073cc37b3e/pvlib/modelchain.py#L1594-L1598

One possible solution is 

```python
if any(p is None for p in poa):
    raise ValueError
```

Patch:
diff --git a/pvlib/modelchain.py b/pvlib/modelchain.py
--- a/pvlib/modelchain.py
+++ b/pvlib/modelchain.py
@@ -1572,11 +1572,13 @@ def _prepare_temperature(self, data=None):
         """
         poa = _irrad_for_celltemp(self.results.total_irrad,
                                   self.results.effective_irradiance)
-        if not isinstance(data, tuple) and self.system.num_arrays > 1:
+        # handle simple case first, single array, data not iterable
+        if not isinstance(data, tuple) and self.system.num_arrays == 1:
+            return self._prepare_temperature_single_array(data, poa)
+        if not isinstance(data, tuple):
             # broadcast data to all arrays
             data = (data,) * self.system.num_arrays
-        elif not isinstance(data, tuple):
-            return self._prepare_temperature_single_array(data, poa)
+        # find where cell or module temperature is specified in input data
         given_cell_temperature = tuple(itertools.starmap(
             self._get_cell_temperature,
             zip(data, poa, self.system.temperature_model_parameters)
@@ -1587,23 +1589,7 @@ def _prepare_temperature(self, data=None):
             self.results.cell_temperature = given_cell_temperature
             return self
         # Calculate cell temperature from weather data. If cell_temperature
-        # has not been provided for some arrays then it is computed with
-        # ModelChain.temperature_model(). Because this operates on all Arrays
-        # simultaneously, 'poa_global' must be known for all arrays, including
-        # those that have a known cell temperature.
-        try:
-            self._verify_df(self.results.total_irrad, ['poa_global'])
-        except ValueError:
-            # Provide a more informative error message. Because only
-            # run_model_from_effective_irradiance() can get to this point
-            # without known POA we can suggest a very specific remedy in the
-            # error message.
-            raise ValueError("Incomplete input data. Data must contain "
-                             "'poa_global'. For systems with multiple Arrays "
-                             "if you have provided 'cell_temperature' for "
-                             "only a subset of Arrays you must provide "
-                             "'poa_global' for all Arrays, including those "
-                             "that have a known 'cell_temperature'.")
+        # has not been provided for some arrays then it is computed.
         self.temperature_model()
         # replace calculated cell temperature with temperature given in `data`
         # where available.
@@ -1814,6 +1800,7 @@ def run_model_from_effective_irradiance(self, data=None):
         """
         data = _to_tuple(data)
         self._check_multiple_input(data)
+        self._verify_df(data, required=['effective_irradiance'])
         self._assign_weather(data)
         self._assign_total_irrad(data)
         self.results.effective_irradiance = _tuple_from_dfs(


Test patch:
diff --git a/pvlib/tests/test_modelchain.py b/pvlib/tests/test_modelchain.py
--- a/pvlib/tests/test_modelchain.py
+++ b/pvlib/tests/test_modelchain.py
@@ -985,26 +985,44 @@ def test_run_model_from_poa_tracking(sapm_dc_snl_ac_system, location,
     assert_series_equal(ac, expected)
 
 
+@pytest.mark.parametrize("input_type", [lambda x: x[0], tuple, list])
 def test_run_model_from_effective_irradiance(sapm_dc_snl_ac_system, location,
-                                             weather, total_irrad):
+                                             weather, total_irrad, input_type):
     data = weather.copy()
     data[['poa_global', 'poa_diffuse', 'poa_direct']] = total_irrad
     data['effective_irradiance'] = data['poa_global']
     mc = ModelChain(sapm_dc_snl_ac_system, location, aoi_model='no_loss',
                     spectral_model='no_loss')
-    ac = mc.run_model_from_effective_irradiance(data).results.ac
+    ac = mc.run_model_from_effective_irradiance(input_type((data,))).results.ac
     expected = pd.Series(np.array([149.280238, 96.678385]),
                          index=data.index)
     assert_series_equal(ac, expected)
 
 
+@pytest.mark.parametrize("input_type", [tuple, list])
+def test_run_model_from_effective_irradiance_multi_array(
+        sapm_dc_snl_ac_system_Array, location, weather, total_irrad,
+        input_type):
+    data = weather.copy()
+    data[['poa_global', 'poa_diffuse', 'poa_direct']] = total_irrad
+    data['effective_irradiance'] = data['poa_global']
+    mc = ModelChain(sapm_dc_snl_ac_system_Array, location, aoi_model='no_loss',
+                    spectral_model='no_loss')
+    mc.run_model_from_effective_irradiance(input_type((data, data)))
+    # arrays have different orientation, but should give same dc power
+    # because we are the same passing POA irradiance and air
+    # temperature.
+    assert_frame_equal(mc.results.dc[0], mc.results.dc[1])
+
+
+@pytest.mark.parametrize("input_type", [lambda x: x[0], tuple, list])
 def test_run_model_from_effective_irradiance_no_poa_global(
-        sapm_dc_snl_ac_system, location, weather, total_irrad):
+        sapm_dc_snl_ac_system, location, weather, total_irrad, input_type):
     data = weather.copy()
     data['effective_irradiance'] = total_irrad['poa_global']
     mc = ModelChain(sapm_dc_snl_ac_system, location, aoi_model='no_loss',
                     spectral_model='no_loss')
-    ac = mc.run_model_from_effective_irradiance(data).results.ac
+    ac = mc.run_model_from_effective_irradiance(input_type((data,))).results.ac
     expected = pd.Series(np.array([149.280238, 96.678385]),
                          index=data.index)
     assert_series_equal(ac, expected)
@@ -1087,23 +1105,6 @@ def test_run_model_from_effective_irradiance_minimal_input(
     assert not mc.results.ac.empty
 
 
-def test_run_model_from_effective_irradiance_missing_poa(
-        sapm_dc_snl_ac_system_Array, location, total_irrad):
-    data_incomplete = pd.DataFrame(
-        {'effective_irradiance': total_irrad['poa_global'],
-         'poa_global': total_irrad['poa_global']},
-        index=total_irrad.index)
-    data_complete = pd.DataFrame(
-        {'effective_irradiance': total_irrad['poa_global'],
-         'cell_temperature': 30},
-        index=total_irrad.index)
-    mc = ModelChain(sapm_dc_snl_ac_system_Array, location)
-    with pytest.raises(ValueError,
-                       match="you must provide 'poa_global' for all Arrays"):
-        mc.run_model_from_effective_irradiance(
-            (data_complete, data_incomplete))
-
-
 def test_run_model_singleton_weather_single_array(cec_dc_snl_ac_system,
                                                   location, weather):
     mc = ModelChain(cec_dc_snl_ac_system, location,