From b60973fd62813393f05ad4547b4591a99d142ccf Mon Sep 17 00:00:00 2001 From: Taos Transue Date: Wed, 19 Jul 2023 18:43:54 -0700 Subject: [PATCH 1/9] bishop88 works with pandas.Series of length one --- pvlib/singlediode.py | 58 ++++++++++++++++++++------------- pvlib/tests/test_singlediode.py | 12 +++++++ 2 files changed, 47 insertions(+), 23 deletions(-) diff --git a/pvlib/singlediode.py b/pvlib/singlediode.py index cfc63f3591..5226d5464e 100644 --- a/pvlib/singlediode.py +++ b/pvlib/singlediode.py @@ -319,11 +319,9 @@ def vd_from_brent(voc, v, iph, isat, rs, rsh, gamma, d2mutau, NsVbi, vd_from_brent_vectorized = np.vectorize(vd_from_brent) vd = vd_from_brent_vectorized(voc_est, voltage, *args) elif method == 'newton': - # make sure all args are numpy arrays if max size > 1 - # if voltage is an array, then make a copy to use for initial guess, v0 - args, v0, method_kwargs = \ - _prepare_newton_inputs((voltage,), args, voltage, method_kwargs) - vd = newton(func=lambda x, *a: fv(x, voltage, *a), x0=v0, + x0, (voltage, *args), method_kwargs = \ + _prepare_newton_inputs(voltage, [voltage, *args], method_kwargs) + vd = newton(func=lambda x, *a: fv(x, voltage, *a), x0=x0, fprime=lambda x, *a: bishop88(x, *a, gradients=True)[4], args=args, **method_kwargs) @@ -454,11 +452,9 @@ def vd_from_brent(voc, i, iph, isat, rs, rsh, gamma, d2mutau, NsVbi, vd_from_brent_vectorized = np.vectorize(vd_from_brent) vd = vd_from_brent_vectorized(voc_est, current, *args) elif method == 'newton': - # make sure all args are numpy arrays if max size > 1 - # if voc_est is an array, then make a copy to use for initial guess, v0 - args, v0, method_kwargs = \ - _prepare_newton_inputs((current,), args, voc_est, method_kwargs) - vd = newton(func=lambda x, *a: fi(x, current, *a), x0=v0, + x0, (current, *args), method_kwargs = \ + _prepare_newton_inputs(voc_est, [current, *args], method_kwargs) + vd = newton(func=lambda x, *a: fi(x, current, *a), x0=x0, fprime=lambda x, *a: bishop88(x, *a, gradients=True)[3], args=args, **method_kwargs) @@ -584,10 +580,10 @@ def fmpp(x, *a): elif method == 'newton': # make sure all args are numpy arrays if max size > 1 # if voc_est is an array, then make a copy to use for initial guess, v0 - args, v0, method_kwargs = \ - _prepare_newton_inputs((), args, voc_est, method_kwargs) + x0, args, method_kwargs = \ + _prepare_newton_inputs(voc_est, args, method_kwargs) vd = newton( - func=fmpp, x0=v0, + func=fmpp, x0=x0, fprime=lambda x, *a: bishop88(x, *a, gradients=True)[7], args=args, **method_kwargs) else: @@ -627,22 +623,38 @@ def _get_size_and_shape(args): return size, shape -def _prepare_newton_inputs(i_or_v_tup, args, v0, method_kwargs): - # broadcast arguments for newton method - # the first argument should be a tuple, eg: (i,), (v,) or () - size, shape = _get_size_and_shape(i_or_v_tup + args) - if size > 1: - args = [np.asarray(arg) for arg in args] - # newton uses initial guess for the output shape - # copy v0 to a new array and broadcast it to the shape of max size +def _prepare_newton_inputs(x0, args, method_kwargs): + """ + Make inputs compatible with Scipy's newton by: + - converting all arugments (`x0` and `args`) into numpy.ndarrays if any + argument has size greater than 1 or is a pandas.Series. + - broadcasting the initial guess `x0` to the shape of the argument with + the greatest size. + + Parameters + ---------- + x0: numeric + Initial guess for newton. + args: Iterable(numeric) + Iterable of additional arguments to use in SciPy's newton. + method_kwargs: dict + Options to pass to newton. + + Returns + ------- + tuple containing the updated initial guess, arguments, and options + for newton. + """ + shape = _get_size_and_shape(args)[1] if shape is not None: - v0 = np.broadcast_to(v0, shape).copy() + args = list(map(np.asarray, args)) + x0 = np.broadcast_to(x0, shape) # set abs tolerance and maxiter from method_kwargs if not provided # apply defaults, but giving priority to user-specified values method_kwargs = {**NEWTON_DEFAULT_PARAMS, **method_kwargs} - return args, v0, method_kwargs + return x0, args, method_kwargs def _lambertw_v_from_i(current, photocurrent, saturation_current, diff --git a/pvlib/tests/test_singlediode.py b/pvlib/tests/test_singlediode.py index 16b93cbf77..7d4dbe7025 100644 --- a/pvlib/tests/test_singlediode.py +++ b/pvlib/tests/test_singlediode.py @@ -1,6 +1,7 @@ """ testing single-diode methods using JW Bishop 1988 """ +import itertools import numpy as np import pandas as pd @@ -557,3 +558,14 @@ def test_bishop88_full_output_kwarg(method, bishop88_arguments): assert isinstance(ret_val[1], tuple) # second is output from optimizer # any root finder returns at least 2 elements with full_output=True assert len(ret_val[1]) >= 2 + + +@pytest.mark.parametrize('method', ['newton', 'brentq']) +def test_bishop88_pdSeries_len_one(method, bishop88_arguments): + for k, v in bishop88_arguments.items(): + bishop88_arguments[k] = pd.Series([v]) + + # should not raise error + bishop88_i_from_v(pd.Series([0]), **bishop88_arguments, method=method) + bishop88_v_from_i(pd.Series([0]), **bishop88_arguments, method=method) + bishop88_mpp(**bishop88_arguments, method=method) From 5e2e109818c17592591b2dfea7365e10c1a5f928 Mon Sep 17 00:00:00 2001 From: Taos Transue Date: Wed, 19 Jul 2023 19:41:06 -0700 Subject: [PATCH 2/9] fix to many cases when arguments would be converted --- pvlib/singlediode.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pvlib/singlediode.py b/pvlib/singlediode.py index 5226d5464e..39bfbb189d 100644 --- a/pvlib/singlediode.py +++ b/pvlib/singlediode.py @@ -3,6 +3,7 @@ """ import numpy as np +import pandas as pd from pvlib.tools import _golden_sect_DataFrame from scipy.optimize import brentq, newton @@ -645,8 +646,8 @@ def _prepare_newton_inputs(x0, args, method_kwargs): tuple containing the updated initial guess, arguments, and options for newton. """ - shape = _get_size_and_shape(args)[1] - if shape is not None: + size, shape = max(((np.size(a), np.shape(a)) for a in args), key=lambda a: a[0]) + if size > 1 or any(isinstance(a, pd.Series) for a in args): args = list(map(np.asarray, args)) x0 = np.broadcast_to(x0, shape) From 4e24a8b2aa96fe20a60054a2986401da32231c4f Mon Sep 17 00:00:00 2001 From: Taos Transue Date: Tue, 1 Aug 2023 15:19:59 -0700 Subject: [PATCH 3/9] converting all newton numeric args if not np.isscalar --- pvlib/singlediode.py | 63 +++++++++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 30 deletions(-) diff --git a/pvlib/singlediode.py b/pvlib/singlediode.py index 39bfbb189d..f9641e8f41 100644 --- a/pvlib/singlediode.py +++ b/pvlib/singlediode.py @@ -288,9 +288,10 @@ def bishop88_i_from_v(voltage, photocurrent, saturation_current, ... method_kwargs={'full_output': True}) """ # collect args - args = (photocurrent, saturation_current, resistance_series, - resistance_shunt, nNsVth, d2mutau, NsVbi, - breakdown_factor, breakdown_voltage, breakdown_exp) + args_numeric = (photocurrent, saturation_current, resistance_series, + resistance_shunt, nNsVth, d2mutau, NsVbi) + args_scalar = (breakdown_factor, breakdown_voltage, breakdown_exp) + args = args_numeric + args_scalar method = method.lower() # method_kwargs create dict if not provided @@ -320,12 +321,13 @@ def vd_from_brent(voc, v, iph, isat, rs, rsh, gamma, d2mutau, NsVbi, vd_from_brent_vectorized = np.vectorize(vd_from_brent) vd = vd_from_brent_vectorized(voc_est, voltage, *args) elif method == 'newton': - x0, (voltage, *args), method_kwargs = \ - _prepare_newton_inputs(voltage, [voltage, *args], method_kwargs) + x0, (voltage, *args_numeric), method_kwargs = \ + _prepare_newton_inputs(voltage, [voltage, *args_numeric], + method_kwargs) + args = tuple(args_numeric) + args_scalar vd = newton(func=lambda x, *a: fv(x, voltage, *a), x0=x0, fprime=lambda x, *a: bishop88(x, *a, gradients=True)[4], - args=args, - **method_kwargs) + args=args, **method_kwargs) else: raise NotImplementedError("Method '%s' isn't implemented" % method) @@ -421,9 +423,10 @@ def bishop88_v_from_i(current, photocurrent, saturation_current, ... method_kwargs={'full_output': True}) """ # collect args - args = (photocurrent, saturation_current, resistance_series, - resistance_shunt, nNsVth, d2mutau, NsVbi, breakdown_factor, - breakdown_voltage, breakdown_exp) + args_numeric = (photocurrent, saturation_current, resistance_series, + resistance_shunt, nNsVth, d2mutau, NsVbi) + args_scalar = (breakdown_factor, breakdown_voltage, breakdown_exp) + args = args_numeric + args_scalar method = method.lower() # method_kwargs create dict if not provided @@ -453,12 +456,13 @@ def vd_from_brent(voc, i, iph, isat, rs, rsh, gamma, d2mutau, NsVbi, vd_from_brent_vectorized = np.vectorize(vd_from_brent) vd = vd_from_brent_vectorized(voc_est, current, *args) elif method == 'newton': - x0, (current, *args), method_kwargs = \ - _prepare_newton_inputs(voc_est, [current, *args], method_kwargs) + x0, (current, *args_numeric), method_kwargs = \ + _prepare_newton_inputs(voc_est, [current, *args_numeric], + method_kwargs) + args = tuple(args_numeric) + args_scalar vd = newton(func=lambda x, *a: fi(x, current, *a), x0=x0, fprime=lambda x, *a: bishop88(x, *a, gradients=True)[3], - args=args, - **method_kwargs) + args=args, **method_kwargs) else: raise NotImplementedError("Method '%s' isn't implemented" % method) @@ -552,9 +556,10 @@ def bishop88_mpp(photocurrent, saturation_current, resistance_series, ... method='newton', method_kwargs={'full_output': True}) """ # collect args - args = (photocurrent, saturation_current, resistance_series, - resistance_shunt, nNsVth, d2mutau, NsVbi, breakdown_factor, - breakdown_voltage, breakdown_exp) + args_numeric = (photocurrent, saturation_current, resistance_series, + resistance_shunt, nNsVth, d2mutau, NsVbi) + args_scalar = (breakdown_factor, breakdown_voltage, breakdown_exp) + args = args_numeric + args_scalar method = method.lower() # method_kwargs create dict if not provided @@ -581,12 +586,12 @@ def fmpp(x, *a): elif method == 'newton': # make sure all args are numpy arrays if max size > 1 # if voc_est is an array, then make a copy to use for initial guess, v0 - x0, args, method_kwargs = \ - _prepare_newton_inputs(voc_est, args, method_kwargs) - vd = newton( - func=fmpp, x0=x0, - fprime=lambda x, *a: bishop88(x, *a, gradients=True)[7], args=args, - **method_kwargs) + x0, args_numeric, method_kwargs = \ + _prepare_newton_inputs(voc_est, args_numeric, method_kwargs) + args = tuple(args_numeric) + args_scalar + vd = newton(func=fmpp, x0=x0, + fprime=lambda x, *a: bishop88(x, *a, gradients=True)[7], + args=args, **method_kwargs) else: raise NotImplementedError("Method '%s' isn't implemented" % method) @@ -628,7 +633,7 @@ def _prepare_newton_inputs(x0, args, method_kwargs): """ Make inputs compatible with Scipy's newton by: - converting all arugments (`x0` and `args`) into numpy.ndarrays if any - argument has size greater than 1 or is a pandas.Series. + argument is not a scalar. - broadcasting the initial guess `x0` to the shape of the argument with the greatest size. @@ -643,13 +648,11 @@ def _prepare_newton_inputs(x0, args, method_kwargs): Returns ------- - tuple containing the updated initial guess, arguments, and options - for newton. + tuple + The updated initial guess, arguments, and options for newton. """ - size, shape = max(((np.size(a), np.shape(a)) for a in args), key=lambda a: a[0]) - if size > 1 or any(isinstance(a, pd.Series) for a in args): - args = list(map(np.asarray, args)) - x0 = np.broadcast_to(x0, shape) + if not (np.isscalar(x0) and all(map(np.isscalar, args))): + x0, *args = np.broadcast_arrays(x0, *args) # set abs tolerance and maxiter from method_kwargs if not provided # apply defaults, but giving priority to user-specified values From 7acf63e9242b0963f8715f0b97d63ef61284adc7 Mon Sep 17 00:00:00 2001 From: Taos Transue Date: Tue, 1 Aug 2023 15:25:03 -0700 Subject: [PATCH 4/9] remove used imports --- pvlib/singlediode.py | 1 - pvlib/tests/test_singlediode.py | 2 -- 2 files changed, 3 deletions(-) diff --git a/pvlib/singlediode.py b/pvlib/singlediode.py index f9641e8f41..2ae0e47eb5 100644 --- a/pvlib/singlediode.py +++ b/pvlib/singlediode.py @@ -3,7 +3,6 @@ """ import numpy as np -import pandas as pd from pvlib.tools import _golden_sect_DataFrame from scipy.optimize import brentq, newton diff --git a/pvlib/tests/test_singlediode.py b/pvlib/tests/test_singlediode.py index 7d4dbe7025..bd3b33776a 100644 --- a/pvlib/tests/test_singlediode.py +++ b/pvlib/tests/test_singlediode.py @@ -1,8 +1,6 @@ """ testing single-diode methods using JW Bishop 1988 """ -import itertools - import numpy as np import pandas as pd import scipy From 4c0f2c59b8b59a7ae6d286ce896426a3dec1fd82 Mon Sep 17 00:00:00 2001 From: Taos Transue Date: Tue, 1 Aug 2023 15:26:42 -0700 Subject: [PATCH 5/9] add back newline --- pvlib/tests/test_singlediode.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pvlib/tests/test_singlediode.py b/pvlib/tests/test_singlediode.py index bd3b33776a..8e0d05668e 100644 --- a/pvlib/tests/test_singlediode.py +++ b/pvlib/tests/test_singlediode.py @@ -1,6 +1,7 @@ """ testing single-diode methods using JW Bishop 1988 """ + import numpy as np import pandas as pd import scipy From cc3700d39dda7a437602d43b8b5e196c90382dce Mon Sep 17 00:00:00 2001 From: Taos Transue Date: Wed, 2 Aug 2023 11:07:56 -0700 Subject: [PATCH 6/9] update whatsnew --- docs/sphinx/source/whatsnew/v0.10.2.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/sphinx/source/whatsnew/v0.10.2.rst b/docs/sphinx/source/whatsnew/v0.10.2.rst index 7259f6a566..b0a2462693 100644 --- a/docs/sphinx/source/whatsnew/v0.10.2.rst +++ b/docs/sphinx/source/whatsnew/v0.10.2.rst @@ -21,6 +21,9 @@ Bug fixes ~~~~~~~~~ * :py:func:`~pvlib.iotools.get_psm3` no longer incorrectly returns clear-sky DHI instead of clear-sky GHI when requesting ``ghi_clear``. (:pull:`1819`) +* :py:func:`pvlib.singlediode.bishop88` with `method='newton'` no longer + crashes when passed `pandas.Series` of length one. + (:issue:`1787`, :pull:`1822`) Testing ~~~~~~~ From bc6646b1eeaa3de0ab19af830cc612027ab96aa7 Mon Sep 17 00:00:00 2001 From: Taos Transue Date: Thu, 3 Aug 2023 10:47:22 -0700 Subject: [PATCH 7/9] _prepare_newton_inputs only changes shape of x0; refactoring to remove singlediode._get_size_and_shape --- pvlib/pvsystem.py | 46 ++++++++++-------------------- pvlib/singlediode.py | 66 ++++++++++++++------------------------------ 2 files changed, 34 insertions(+), 78 deletions(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 4fb0dc5772..9144106e79 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -2651,28 +2651,19 @@ def v_from_i(current, photocurrent, saturation_current, resistance_series, parameters of real solar cells using Lambert W-function", Solar Energy Materials and Solar Cells, 81 (2004) 269-277. ''' + args = (current, photocurrent, saturation_current, + resistance_series, resistance_shunt, nNsVth) if method.lower() == 'lambertw': - return _singlediode._lambertw_v_from_i( - current, photocurrent, saturation_current, resistance_series, - resistance_shunt, nNsVth - ) + return _singlediode._lambertw_v_from_i(*args) else: # Calculate points on the IV curve using either 'newton' or 'brentq' # methods. Voltages are determined by first solving the single diode # equation for the diode voltage V_d then backing out voltage - args = (current, photocurrent, saturation_current, - resistance_series, resistance_shunt, nNsVth) V = _singlediode.bishop88_v_from_i(*args, method=method.lower()) - # find the right size and shape for returns - size, shape = _singlediode._get_size_and_shape(args) - if size <= 1: - if shape is not None: - V = np.tile(V, shape) - if np.isnan(V).any() and size <= 1: - V = np.repeat(V, size) - if shape is not None: - V = V.reshape(shape) - return V + if all(map(np.isscalar, args)): + return V + shape = _singlediode._shape_of_max_size(*args) + return np.broadcast_to(V, shape) def i_from_v(voltage, photocurrent, saturation_current, resistance_series, @@ -2742,28 +2733,19 @@ def i_from_v(voltage, photocurrent, saturation_current, resistance_series, parameters of real solar cells using Lambert W-function", Solar Energy Materials and Solar Cells, 81 (2004) 269-277. ''' + args = (voltage, photocurrent, saturation_current, + resistance_series, resistance_shunt, nNsVth) if method.lower() == 'lambertw': - return _singlediode._lambertw_i_from_v( - voltage, photocurrent, saturation_current, resistance_series, - resistance_shunt, nNsVth - ) + return _singlediode._lambertw_i_from_v(*args) else: # Calculate points on the IV curve using either 'newton' or 'brentq' # methods. Voltages are determined by first solving the single diode # equation for the diode voltage V_d then backing out voltage - args = (voltage, photocurrent, saturation_current, resistance_series, - resistance_shunt, nNsVth) current = _singlediode.bishop88_i_from_v(*args, method=method.lower()) - # find the right size and shape for returns - size, shape = _singlediode._get_size_and_shape(args) - if size <= 1: - if shape is not None: - current = np.tile(current, shape) - if np.isnan(current).any() and size <= 1: - current = np.repeat(current, size) - if shape is not None: - current = current.reshape(shape) - return current + if all(map(np.isscalar, args)): + return current + shape = _singlediode._shape_of_max_size(*args) + return np.broadcast_to(current, shape) def scale_voltage_current_power(data, voltage=1, current=1): diff --git a/pvlib/singlediode.py b/pvlib/singlediode.py index 2ae0e47eb5..a00554f49e 100644 --- a/pvlib/singlediode.py +++ b/pvlib/singlediode.py @@ -287,10 +287,9 @@ def bishop88_i_from_v(voltage, photocurrent, saturation_current, ... method_kwargs={'full_output': True}) """ # collect args - args_numeric = (photocurrent, saturation_current, resistance_series, - resistance_shunt, nNsVth, d2mutau, NsVbi) - args_scalar = (breakdown_factor, breakdown_voltage, breakdown_exp) - args = args_numeric + args_scalar + args = (photocurrent, saturation_current, + resistance_series, resistance_shunt, nNsVth, d2mutau, NsVbi, + breakdown_factor, breakdown_voltage, breakdown_exp) method = method.lower() # method_kwargs create dict if not provided @@ -320,10 +319,8 @@ def vd_from_brent(voc, v, iph, isat, rs, rsh, gamma, d2mutau, NsVbi, vd_from_brent_vectorized = np.vectorize(vd_from_brent) vd = vd_from_brent_vectorized(voc_est, voltage, *args) elif method == 'newton': - x0, (voltage, *args_numeric), method_kwargs = \ - _prepare_newton_inputs(voltage, [voltage, *args_numeric], - method_kwargs) - args = tuple(args_numeric) + args_scalar + x0, (voltage, *args), method_kwargs = \ + _prepare_newton_inputs(voltage, (voltage, *args), method_kwargs) vd = newton(func=lambda x, *a: fv(x, voltage, *a), x0=x0, fprime=lambda x, *a: bishop88(x, *a, gradients=True)[4], args=args, **method_kwargs) @@ -422,10 +419,9 @@ def bishop88_v_from_i(current, photocurrent, saturation_current, ... method_kwargs={'full_output': True}) """ # collect args - args_numeric = (photocurrent, saturation_current, resistance_series, - resistance_shunt, nNsVth, d2mutau, NsVbi) - args_scalar = (breakdown_factor, breakdown_voltage, breakdown_exp) - args = args_numeric + args_scalar + args = (photocurrent, saturation_current, + resistance_series, resistance_shunt, nNsVth, d2mutau, NsVbi, + breakdown_factor, breakdown_voltage, breakdown_exp) method = method.lower() # method_kwargs create dict if not provided @@ -455,10 +451,8 @@ def vd_from_brent(voc, i, iph, isat, rs, rsh, gamma, d2mutau, NsVbi, vd_from_brent_vectorized = np.vectorize(vd_from_brent) vd = vd_from_brent_vectorized(voc_est, current, *args) elif method == 'newton': - x0, (current, *args_numeric), method_kwargs = \ - _prepare_newton_inputs(voc_est, [current, *args_numeric], - method_kwargs) - args = tuple(args_numeric) + args_scalar + x0, (current, *args), method_kwargs = \ + _prepare_newton_inputs(voc_est, (current, *args), method_kwargs) vd = newton(func=lambda x, *a: fi(x, current, *a), x0=x0, fprime=lambda x, *a: bishop88(x, *a, gradients=True)[3], args=args, **method_kwargs) @@ -555,10 +549,9 @@ def bishop88_mpp(photocurrent, saturation_current, resistance_series, ... method='newton', method_kwargs={'full_output': True}) """ # collect args - args_numeric = (photocurrent, saturation_current, resistance_series, - resistance_shunt, nNsVth, d2mutau, NsVbi) - args_scalar = (breakdown_factor, breakdown_voltage, breakdown_exp) - args = args_numeric + args_scalar + args = (photocurrent, saturation_current, + resistance_series, resistance_shunt, nNsVth, d2mutau, NsVbi, + breakdown_factor, breakdown_voltage, breakdown_exp) method = method.lower() # method_kwargs create dict if not provided @@ -585,9 +578,8 @@ def fmpp(x, *a): elif method == 'newton': # make sure all args are numpy arrays if max size > 1 # if voc_est is an array, then make a copy to use for initial guess, v0 - x0, args_numeric, method_kwargs = \ - _prepare_newton_inputs(voc_est, args_numeric, method_kwargs) - args = tuple(args_numeric) + args_scalar + x0, args, method_kwargs = \ + _prepare_newton_inputs(voc_est, args, method_kwargs) vd = newton(func=fmpp, x0=x0, fprime=lambda x, *a: bishop88(x, *a, gradients=True)[7], args=args, **method_kwargs) @@ -604,28 +596,9 @@ def fmpp(x, *a): return bishop88(vd, *args) -def _get_size_and_shape(args): - # find the right size and shape for returns - size, shape = 0, None # 0 or None both mean scalar - for arg in args: - try: - this_shape = arg.shape # try to get shape - except AttributeError: - this_shape = None - try: - this_size = len(arg) # try to get the size - except TypeError: - this_size = 0 - else: - this_size = arg.size # if it has shape then it also has size - if shape is None: - shape = this_shape # set the shape if None - # update size and shape - if this_size > size: - size = this_size - if this_shape is not None: - shape = this_shape - return size, shape +def _shape_of_max_size(*args): + return max(((np.size(a), np.shape(a)) for a in args), + key=lambda t: t[0])[1] def _prepare_newton_inputs(x0, args, method_kwargs): @@ -651,7 +624,8 @@ def _prepare_newton_inputs(x0, args, method_kwargs): The updated initial guess, arguments, and options for newton. """ if not (np.isscalar(x0) and all(map(np.isscalar, args))): - x0, *args = np.broadcast_arrays(x0, *args) + args = tuple(map(np.asarray, args)) + x0 = np.broadcast_to(x0, _shape_of_max_size(x0, *args)) # set abs tolerance and maxiter from method_kwargs if not provided # apply defaults, but giving priority to user-specified values From 7b396738bb23e2be3ac6c42e369a9f51fc857ace Mon Sep 17 00:00:00 2001 From: Taos Transue Date: Thu, 3 Aug 2023 11:23:16 -0700 Subject: [PATCH 8/9] remove _shape_of_max_size, np.broadcast_shapes handles this --- pvlib/pvsystem.py | 4 ++-- pvlib/singlediode.py | 7 +------ 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 9144106e79..a8fc0c7258 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -2662,7 +2662,7 @@ def v_from_i(current, photocurrent, saturation_current, resistance_series, V = _singlediode.bishop88_v_from_i(*args, method=method.lower()) if all(map(np.isscalar, args)): return V - shape = _singlediode._shape_of_max_size(*args) + shape = np.broadcast_shapes(*map(np.shape, args)) return np.broadcast_to(V, shape) @@ -2744,7 +2744,7 @@ def i_from_v(voltage, photocurrent, saturation_current, resistance_series, current = _singlediode.bishop88_i_from_v(*args, method=method.lower()) if all(map(np.isscalar, args)): return current - shape = _singlediode._shape_of_max_size(*args) + shape = np.broadcast_shapes(*map(np.shape, args)) return np.broadcast_to(current, shape) diff --git a/pvlib/singlediode.py b/pvlib/singlediode.py index a00554f49e..c82a69bb53 100644 --- a/pvlib/singlediode.py +++ b/pvlib/singlediode.py @@ -596,11 +596,6 @@ def fmpp(x, *a): return bishop88(vd, *args) -def _shape_of_max_size(*args): - return max(((np.size(a), np.shape(a)) for a in args), - key=lambda t: t[0])[1] - - def _prepare_newton_inputs(x0, args, method_kwargs): """ Make inputs compatible with Scipy's newton by: @@ -625,7 +620,7 @@ def _prepare_newton_inputs(x0, args, method_kwargs): """ if not (np.isscalar(x0) and all(map(np.isscalar, args))): args = tuple(map(np.asarray, args)) - x0 = np.broadcast_to(x0, _shape_of_max_size(x0, *args)) + x0 = np.broadcast_to(x0, np.broadcast_shapes(*map(np.shape, args))) # set abs tolerance and maxiter from method_kwargs if not provided # apply defaults, but giving priority to user-specified values From 54462679cb6407464337f7169d126d65afde3d46 Mon Sep 17 00:00:00 2001 From: Taos Transue Date: Thu, 3 Aug 2023 11:40:59 -0700 Subject: [PATCH 9/9] np.broadcast_shapes not available for Python 3.7 conda -min This reverts commit 7b396738bb23e2be3ac6c42e369a9f51fc857ace. --- pvlib/pvsystem.py | 4 ++-- pvlib/singlediode.py | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index a8fc0c7258..9144106e79 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -2662,7 +2662,7 @@ def v_from_i(current, photocurrent, saturation_current, resistance_series, V = _singlediode.bishop88_v_from_i(*args, method=method.lower()) if all(map(np.isscalar, args)): return V - shape = np.broadcast_shapes(*map(np.shape, args)) + shape = _singlediode._shape_of_max_size(*args) return np.broadcast_to(V, shape) @@ -2744,7 +2744,7 @@ def i_from_v(voltage, photocurrent, saturation_current, resistance_series, current = _singlediode.bishop88_i_from_v(*args, method=method.lower()) if all(map(np.isscalar, args)): return current - shape = np.broadcast_shapes(*map(np.shape, args)) + shape = _singlediode._shape_of_max_size(*args) return np.broadcast_to(current, shape) diff --git a/pvlib/singlediode.py b/pvlib/singlediode.py index c82a69bb53..a00554f49e 100644 --- a/pvlib/singlediode.py +++ b/pvlib/singlediode.py @@ -596,6 +596,11 @@ def fmpp(x, *a): return bishop88(vd, *args) +def _shape_of_max_size(*args): + return max(((np.size(a), np.shape(a)) for a in args), + key=lambda t: t[0])[1] + + def _prepare_newton_inputs(x0, args, method_kwargs): """ Make inputs compatible with Scipy's newton by: @@ -620,7 +625,7 @@ def _prepare_newton_inputs(x0, args, method_kwargs): """ if not (np.isscalar(x0) and all(map(np.isscalar, args))): args = tuple(map(np.asarray, args)) - x0 = np.broadcast_to(x0, np.broadcast_shapes(*map(np.shape, args))) + x0 = np.broadcast_to(x0, _shape_of_max_size(x0, *args)) # set abs tolerance and maxiter from method_kwargs if not provided # apply defaults, but giving priority to user-specified values