Repository: pvlib/pvlib-python (python)

Issue:
adopt NEP-29
**Is your feature request related to a problem? Please describe.**
the travis-ci test fails for #823 because the minimum NumPy version for py35-min is version 1.10.4 - the `np.roll()` function used was updated to allow multiple axes in NumPy-1.12

**Describe the solution you'd like**
[NEP-29](https://numpy.org/neps/nep-0029-deprecation_policy.html) is a proposal by maintainers of IPython, Matplotlib, SciPy, and NumPy that proposes the support window for Python and Numpy.

TL;DR: in the immediate future, drop support for Python-3.5 and Numpy<1.14

**Describe alternatives you've considered**
- statically create the 3 rotation matrices, removing the use of `np.roll` completely
- use the pre-NumPy-1.12 version of `np.roll`

**Additional context**
Since many other major packages that are used by pvlib are conforming to NEP-29, it makes a lot of sense for pvlib to also conform.

Pull request:
update to numpy-1.12.0
<!-- Thank you for your contribution! The following items must be addressed before the code can be merged. Please don't hesitate to ask for help if you're unsure of how to accomplish any of the items. Feel free to remove checklist items that are not relevant to your change. -->

 - [x] Closes #828
 - [x] I am familiar with the [contributing guidelines](https://pvlib-python.readthedocs.io/en/latest/contributing.html)
 - [ ] Tests added
 - [ ] Updates entries to [`docs/sphinx/source/api.rst`](https://github.com/pvlib/pvlib-python/blob/master/docs/sphinx/source/api.rst) for API changes.
 - [x] Adds description and name entries in the appropriate "what's new" file in [`docs/sphinx/source/whatsnew`](https://github.com/pvlib/pvlib-python/tree/master/docs/sphinx/source/whatsnew) for all changes. Includes link to the GitHub Issue with `` :issue:`num` `` or this Pull Request with `` :pull:`num` ``. Includes contributor name and/or GitHub username (link with `` :ghuser:`user` ``).
 - [ ] New code is fully documented. Includes [numpydoc](https://numpydoc.readthedocs.io/en/latest/format.html) compliant docstrings, examples, and comments where necessary.
 - [x] Pull request is nearly complete and ready for detailed review.
 - [ ] Maintainer: Appropriate GitHub Labels and Milestone are assigned to the Pull Request and linked Issue.

update to numpy-1.12.0


Patch:
diff --git a/ci/requirements-py35-min.yml b/ci/requirements-py35-min.yml
index 3250834..fc39936 100644
--- a/ci/requirements-py35-min.yml
+++ b/ci/requirements-py35-min.yml
@@ -13,5 +13,5 @@ dependencies:
     - pytz
     - requests
     - pip:
-        - numpy==1.10.4
+        - numpy==1.12.0
         - pandas==0.18.1
diff --git a/docs/sphinx/source/api.rst b/docs/sphinx/source/api.rst
index b9c7a82..002e694 100644
--- a/docs/sphinx/source/api.rst
+++ b/docs/sphinx/source/api.rst
@@ -233,6 +233,7 @@ PV temperature models
    temperature.sapm_cell
    temperature.sapm_module
    temperature.pvsyst_cell
+   temperature.faiman
 
 Single diode models
 -------------------
@@ -376,6 +377,7 @@ in some files.
    :toctree: generated/
 
    location.Location.from_tmy
+   location.Location.from_epw
 
 
 TMY
diff --git a/docs/sphinx/source/whatsnew/v0.7.0.rst b/docs/sphinx/source/whatsnew/v0.7.0.rst
index 6de109c..0d9fa02 100644
--- a/docs/sphinx/source/whatsnew/v0.7.0.rst
+++ b/docs/sphinx/source/whatsnew/v0.7.0.rst
@@ -8,7 +8,7 @@ recommend all users of v0.6.3 upgrade to this release after checking API
 compatibility notes.
 
 **Python 2.7 support ended on June 1, 2019.** (:issue:`501`)
-**Minimum numpy version is now 1.10.4. Minimum pandas version is now 0.18.1.**
+**Minimum numpy version is now 1.12.0. Minimum pandas version is now 0.18.1.** (:issue:`830`, :issue:`748`)
 
 API Breaking Changes
 ~~~~~~~~~~~~~~~~~~~~
@@ -30,7 +30,7 @@ API Changes with Deprecations
       - `temperature.sapm_cell` returns only the cell temperature, whereas the
          old `pvsystem.sapm_celltemp` returned a `DataFrame` with both cell and module temperatures.
       - Created `temperature.sapm_module` to return module temperature using the SAPM temperature model.
-      - Changed the order of arguments for`pvsystem.sapm_celltemp`,
+      - Changed the order of arguments for `pvsystem.sapm_celltemp`,
         `pvsystem.pvsyst_celltemp` and `PVSystem.sapm_celltemp` to be consistent
         among cell temperature model functions.
       - Removed `model` as a kwarg from `temperature.sapm_cell` and
@@ -119,8 +119,12 @@ Other API Changes
 
 Enhancements
 ~~~~~~~~~~~~
+* Created one new temperature model function:
+  :py:func:`pvlib.temperature.faiman`. (:issue:`750`)
 * Created two new incidence angle modifier (IAM) functions:
   :py:func:`pvlib.iam.martin_ruiz` and :py:func:`pvlib.iam.interp`. (:issue:`751`)
+* Created one new incidence angle modifier (IAM) function for diffuse irradiance:
+  :py:func:`pvlib.iam.martin_ruiz_diffuse`. (:issue:`751`)
 * Add the `martin_ruiz` IAM function as an option for `ModelChain.aoi_model`.
 * Updated the file for module parameters for the CEC model, from the SAM file
   dated 2017-6-5 to the SAM file dated 2019-03-05. (:issue:`761`)
@@ -138,8 +142,8 @@ Enhancements
 * Add `timeout` to :py:func:`pvlib.iotools.get_psm3`.
 * Add :py:func:`~pvlib.scaling.wvm`, a port of the wavelet variability model for
   computing reductions in variability due to a spatially distributed plant.
-* Created one new incidence angle modifier (IAM) function for diffuse irradiance:
-  :py:func:`pvlib.iam.martin_ruiz_diffuse`. (:issue:`751`)
+* Add :py:meth:`~pvlib.location.Location.from_epw`, a method to create a Location
+  object from epw metadata, typically coming from `pvlib.iotools.epw.read_epw`.
 
 Bug fixes
 ~~~~~~~~~
@@ -187,6 +191,11 @@ Removal of prior version deprecations
 * Removed `ModelChain.singlediode` method.
 * Removed `ModelChain.prepare_inputs` clearsky assumption when no irradiance
   data was provided.
+  
+Requirements
+~~~~~~~~~~~~
+* numpy minimum increased to v1.12.0, released in 2017. (:issue:`830`)
+* pandas minimum increased to v1.18.1, released in 2016. (:issue:`748`)
 
 Contributors
 ~~~~~~~~~~~~
@@ -202,3 +211,4 @@ Contributors
 * Joseph Ranalli (:ghuser:`jranalli`)
 * Tony Lorenzo (:ghuser:`alorenzo175`)
 * Todd Karin (:ghuser:`toddkarin`)
+* Mark Mikofski (:ghuser:`mikofski`)
diff --git a/pvlib/iotools/tmy.py b/pvlib/iotools/tmy.py
index f17908f..767e18f 100644
--- a/pvlib/iotools/tmy.py
+++ b/pvlib/iotools/tmy.py
@@ -94,7 +94,7 @@ def read_tmy3(filename=None, coerce_year=None, recolumn=True):
     TMYData.ZenithlumUncertainty        Uncertainty based on random and bias error estimates                        see [1] section 2.10
     TMYData.TotCld                      Amount of sky dome covered by clouds or obscuring phenonema at time stamp, tenths of sky
     TMYData.TotCldSource                See [1], Table 1-5, 8760x1 cell array of strings
-    TMYData.TotCldUnertainty            See [1], Table 1-6
+    TMYData.TotCldUncertainty           See [1], Table 1-6
     TMYData.OpqCld                      Amount of sky dome covered by clouds or obscuring phenonema that prevent observing the sky at time stamp, tenths of sky
     TMYData.OpqCldSource                See [1], Table 1-5, 8760x1 cell array of strings
     TMYData.OpqCldUncertainty           See [1], Table 1-6
@@ -246,7 +246,7 @@ def _recolumn(tmy3_dataframe):
         'DNillum', 'DNillumSource', 'DNillumUncertainty', 'DHillum',
         'DHillumSource', 'DHillumUncertainty', 'Zenithlum',
         'ZenithlumSource', 'ZenithlumUncertainty', 'TotCld', 'TotCldSource',
-        'TotCldUnertainty', 'OpqCld', 'OpqCldSource', 'OpqCldUncertainty',
+        'TotCldUncertainty', 'OpqCld', 'OpqCldSource', 'OpqCldUncertainty',
         'DryBulb', 'DryBulbSource', 'DryBulbUncertainty', 'DewPoint',
         'DewPointSource', 'DewPointUncertainty', 'RHum', 'RHumSource',
         'RHumUncertainty', 'Pressure', 'PressureSource',
@@ -346,7 +346,7 @@ def read_tmy2(filename):
     ZenithlumUncertainty            See [1], Table 3-4
     TotCld                          Amount of sky dome covered by clouds or obscuring phenonema at time stamp, tenths of sky
     TotCldSource                    See [1], Table 3-5, 8760x1 cell array of strings
-    TotCldUnertainty                See [1], Table 3-6
+    TotCldUncertainty                See [1], Table 3-6
     OpqCld                          Amount of sky dome covered by clouds or obscuring phenonema that prevent observing the sky at time stamp, tenths of sky
     OpqCldSource                    See [1], Table 3-5, 8760x1 cell array of strings
     OpqCldUncertainty               See [1], Table 3-6
@@ -406,7 +406,7 @@ def read_tmy2(filename):
 
     # paste in the column info as one long line
     string = '%2d%2d%2d%2d%4d%4d%4d%1s%1d%4d%1s%1d%4d%1s%1d%4d%1s%1d%4d%1s%1d%4d%1s%1d%4d%1s%1d%2d%1s%1d%2d%1s%1d%4d%1s%1d%4d%1s%1d%3d%1s%1d%4d%1s%1d%3d%1s%1d%3d%1s%1d%4d%1s%1d%5d%1s%1d%10d%3d%1s%1d%3d%1s%1d%3d%1s%1d%2d%1s%1d'  # noqa: E501
-    columns = 'year,month,day,hour,ETR,ETRN,GHI,GHISource,GHIUncertainty,DNI,DNISource,DNIUncertainty,DHI,DHISource,DHIUncertainty,GHillum,GHillumSource,GHillumUncertainty,DNillum,DNillumSource,DNillumUncertainty,DHillum,DHillumSource,DHillumUncertainty,Zenithlum,ZenithlumSource,ZenithlumUncertainty,TotCld,TotCldSource,TotCldUnertainty,OpqCld,OpqCldSource,OpqCldUncertainty,DryBulb,DryBulbSource,DryBulbUncertainty,DewPoint,DewPointSource,DewPointUncertainty,RHum,RHumSource,RHumUncertainty,Pressure,PressureSource,PressureUncertainty,Wdir,WdirSource,WdirUncertainty,Wspd,WspdSource,WspdUncertainty,Hvis,HvisSource,HvisUncertainty,CeilHgt,CeilHgtSource,CeilHgtUncertainty,PresentWeather,Pwat,PwatSource,PwatUncertainty,AOD,AODSource,AODUncertainty,SnowDepth,SnowDepthSource,SnowDepthUncertainty,LastSnowfall,LastSnowfallSource,LastSnowfallUncertaint'  # noqa: E501
+    columns = 'year,month,day,hour,ETR,ETRN,GHI,GHISource,GHIUncertainty,DNI,DNISource,DNIUncertainty,DHI,DHISource,DHIUncertainty,GHillum,GHillumSource,GHillumUncertainty,DNillum,DNillumSource,DNillumUncertainty,DHillum,DHillumSource,DHillumUncertainty,Zenithlum,ZenithlumSource,ZenithlumUncertainty,TotCld,TotCldSource,TotCldUncertainty,OpqCld,OpqCldSource,OpqCldUncertainty,DryBulb,DryBulbSource,DryBulbUncertainty,DewPoint,DewPointSource,DewPointUncertainty,RHum,RHumSource,RHumUncertainty,Pressure,PressureSource,PressureUncertainty,Wdir,WdirSource,WdirUncertainty,Wspd,WspdSource,WspdUncertainty,Hvis,HvisSource,HvisUncertainty,CeilHgt,CeilHgtSource,CeilHgtUncertainty,PresentWeather,Pwat,PwatSource,PwatUncertainty,AOD,AODSource,AODUncertainty,SnowDepth,SnowDepthSource,SnowDepthUncertainty,LastSnowfall,LastSnowfallSource,LastSnowfallUncertaint'  # noqa: E501
     hdr_columns = 'WBAN,City,State,TZ,latitude,longitude,altitude'
 
     tmy2, tmy2_meta = _read_tmy2(string, columns, hdr_columns, filename)
diff --git a/pvlib/location.py b/pvlib/location.py
index 68b2062..95029ca 100644
--- a/pvlib/location.py
+++ b/pvlib/location.py
@@ -125,6 +125,42 @@ class Location(object):
         # not sure if this should be assigned regardless of input.
         if tmy_data is not None:
             new_object.tmy_data = tmy_data
+            new_object.weather = tmy_data
+
+        return new_object
+
+    @classmethod
+    def from_epw(cls, metadata, data=None, **kwargs):
+        """
+        Create a Location object based on a metadata
+        dictionary from epw data readers.
+
+        Parameters
+        ----------
+        metadata : dict
+            Returned from epw.read_epw
+        data : None or DataFrame, default None
+            Optionally attach the epw data to this object.
+
+        Returns
+        -------
+        Location object (or the child class of Location that you
+        called this method from).
+        """
+
+        latitude = metadata['latitude']
+        longitude = metadata['longitude']
+
+        name = metadata['city']
+
+        tz = metadata['TZ']
+        altitude = metadata['altitude']
+
+        new_object = cls(latitude, longitude, tz=tz, altitude=altitude,
+                         name=name, **kwargs)
+
+        if data is not None:
+            new_object.weather = data
 
         return new_object
 
diff --git a/pvlib/temperature.py b/pvlib/temperature.py
index f43d372..326c677 100644
--- a/pvlib/temperature.py
+++ b/pvlib/temperature.py
@@ -269,3 +269,69 @@ def pvsyst_cell(poa_global, temp_air, wind_speed=1.0, u_c=29.0, u_v=0.0,
     heat_input = poa_global * alpha_absorption * (1 - eta_m)
     temp_difference = heat_input / total_loss_factor
     return temp_air + temp_difference
+
+
+def faiman(poa_global, temp_air, wind_speed=1.0, u0=25.0, u1=6.84):
+    '''
+    Calculate cell or module temperature using an empirical heat loss factor
+    model as proposed by Faiman [1] and adopted in the IEC 61853
+    standards [2] and [3].
+
+    Usage of this model in the IEC 61853 standard does not distinguish
+    between cell and module temperature.
+
+    Parameters
+    ----------
+    poa_global : numeric
+        Total incident irradiance [W/m^2].
+
+    temp_air : numeric
+        Ambient dry bulb temperature [C].
+
+    wind_speed : numeric, default 1.0
+        Wind speed in m/s measured at the same height for which the wind loss
+        factor was determined.  The default value 1.0 m/s is the wind
+        speed at module height used to determine NOCT. [m/s]
+
+    u0 : numeric, default 25.0
+        Combined heat loss factor coefficient. The default value is one
+        determined by Faiman for 7 silicon modules. [W/(m^2 C)].
+
+    u1 : numeric, default 6.84
+        Combined heat loss factor influenced by wind. The default value is one
+        determined by Faiman for 7 silicon modules. [(W/m^2 C)(m/s)].
+
+    Returns
+    -------
+    numeric, values in degrees Celsius
+
+    Notes
+    -----
+    All arguments may be scalars or vectors. If multiple arguments
+    are vectors they must be the same length.
+
+    References
+    ----------
+    [1] Faiman, D. (2008). "Assessing the outdoor operating temperature of
+    photovoltaic modules." Progress in Photovoltaics 16(4): 307-315.
+
+    [2] "IEC 61853-2 Photovoltaic (PV) module performance testing and energy
+    rating - Part 2: Spectral responsivity, incidence angle and module
+    operating temperature measurements". IEC, Geneva, 2018.
+
+    [3] "IEC 61853-3 Photovoltaic (PV) module performance testing and energy
+    rating - Part 3: Energy rating of PV modules". IEC, Geneva, 2018.
+
+    '''
+    # Contributed by Anton Driesse (@adriesse), PV Performance Labs. Dec., 2019
+
+    # The following lines may seem odd since u0 & u1 are probably scalar,
+    # but it serves an indirect and easy way of allowing lists and
+    # tuples for the other function arguments.
+    u0 = np.asanyarray(u0)
+    u1 = np.asanyarray(u1)
+
+    total_loss_factor = u0 + u1 * wind_speed
+    heat_input = poa_global
+    temp_difference = heat_input / total_loss_factor
+    return temp_air + temp_difference
diff --git a/setup.py b/setup.py
index a492b69..ca18970 100755
--- a/setup.py
+++ b/setup.py
@@ -37,7 +37,7 @@ AUTHOR = 'pvlib python Developers'
 MAINTAINER_EMAIL = 'holmgren@email.arizona.edu'
 URL = 'https://github.com/pvlib/pvlib-python'
 
-INSTALL_REQUIRES = ['numpy >= 1.10.4',
+INSTALL_REQUIRES = ['numpy >= 1.12.0',
                     'pandas >= 0.18.1',
                     'pytz',
                     'requests']


Test patch:
diff --git a/pvlib/test/test_location.py b/pvlib/test/test_location.py
index db32ac5..d9503ae 100644
--- a/pvlib/test/test_location.py
+++ b/pvlib/test/test_location.py
@@ -218,7 +218,7 @@ def test_from_tmy_3():
     assert loc.name is not None
     assert loc.altitude != 0
     assert loc.tz != 'UTC'
-    assert_frame_equal(loc.tmy_data, data)
+    assert_frame_equal(loc.weather, data)
 
 
 def test_from_tmy_2():
@@ -229,7 +229,18 @@ def test_from_tmy_2():
     assert loc.name is not None
     assert loc.altitude != 0
     assert loc.tz != 'UTC'
-    assert_frame_equal(loc.tmy_data, data)
+    assert_frame_equal(loc.weather, data)
+
+
+def test_from_epw():
+    from test_epw import epw_testfile
+    from pvlib.iotools import read_epw
+    data, meta = read_epw(epw_testfile)
+    loc = Location.from_epw(meta, data)
+    assert loc.name is not None
+    assert loc.altitude != 0
+    assert loc.tz != 'UTC'
+    assert_frame_equal(loc.weather, data)
 
 
 def test_get_solarposition(expected_solpos, golden_mst):
diff --git a/pvlib/test/test_temperature.py b/pvlib/test/test_temperature.py
index c3112d8..7027c97 100644
--- a/pvlib/test/test_temperature.py
+++ b/pvlib/test/test_temperature.py
@@ -90,6 +90,45 @@ def test_pvsyst_cell_series():
     assert_series_equal(expected, result)
 
 
+def test_faiman_default():
+    result = temperature.faiman(900, 20, 5)
+    assert_allclose(result, 35.203, 0.001)
+
+
+def test_faiman_kwargs():
+    result = temperature.faiman(900, 20, wind_speed=5.0, u0=22.0, u1=6.)
+    assert_allclose(result, 37.308, 0.001)
+
+
+def test_faiman_list():
+    temps = [0, 10, 5]
+    irrads = [0, 500, 0]
+    winds = [10, 5, 0]
+    result = temperature.faiman(irrads, temps, wind_speed=winds)
+    expected = np.array([0.0, 18.446, 5.0])
+    assert_allclose(expected, result, 3)
+
+
+def test_faiman_ndarray():
+    temps = np.array([0, 10, 5])
+    irrads = np.array([0, 500, 0])
+    winds = np.array([10, 5, 0])
+    result = temperature.faiman(irrads, temps, wind_speed=winds)
+    expected = np.array([0.0, 18.446, 5.0])
+    assert_allclose(expected, result, 3)
+
+
+def test_faiman_series():
+    times = pd.date_range(start="2015-01-01", end="2015-01-02", freq="12H")
+    temps = pd.Series([0, 10, 5], index=times)
+    irrads = pd.Series([0, 500, 0], index=times)
+    winds = pd.Series([10, 5, 0], index=times)
+
+    result = temperature.faiman(irrads, temps, wind_speed=winds)
+    expected = pd.Series([0.0, 18.446, 5.0], index=times)
+    assert_series_equal(expected, result)
+
+
 def test__temperature_model_params():
     params = temperature._temperature_model_params('sapm',
                                                    'open_rack_glass_glass')