The issue was raised in some comments in #410 and #409 that the rules for broadcasting outputs are inconsistent among the single diode methods
singlediode will only broadcast the output if photocurrent is an sequence, array or series
- float -> OrderedDict of float
- array or sequence -> OrderedDict of array or sequence
- Series -> DataFrame
i_from_v will broadcast the smallest size of either voltage, photocurrent, or resistance_shunt
v_from_i will broadcast the smallest size of either current, photocurrent, or resistance_shunt
In those same comments @cwhanse proposed:
to expand singletons when any parameter input is a vector, and to fail (gracefully) if two parameters are vectors of unequal length
I agree with this idea, and both i_from_v and v_from_i already have some boilerplate code to address this (note this snippet is lifted from v_from_i, with variable names suited for that function, change the
arguments to other methods like singlediode or mpp):
# find the right size and shape for returns
args = (current, photocurrent, saturation_current,
resistance_series, resistance_shunt, nNsVth) <-- list the args allowed to broadcast here
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 = sum(this_shape) # calc size from shape
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
Now knowing the size and/or the shape one can let numpy.vectorize cast the output as proposed:
if size <= 1:
V = v_from_i_fun(*args)
if shape is not None:
V = np.tile(V, shape) <-- hacky way to make either array(V), array([V]), array([[V]]), ...
else:
# np.vectorize handles broadcasting, raises ValueError
vecfun = np.vectorize(v_from_i_fun)
V = vecfun(*args)
if np.isnan(V).any() and size <= 1:
V = np.repeat(V, size)
if shape is not None:
V = V.reshape(shape)
On a related note: @cwhanse I realize now I misread your comment and made i_from_v and v_from_i fail silently instead of gracefully, oops!. This might cause some confusion, in the short term, but maybe we can address it in this issue here?
@wholmgren is there a PVLibException class? What exception would you expect here, maybe ValueError('All arguments must be either scalar or the same size.')?
The issue was raised in some comments in #410 and #409 that the rules for broadcasting outputs are inconsistent among the single diode methods
singlediodewill only broadcast the output ifphotocurrentis an sequence, array or seriesi_from_vwill broadcast the smallest size of eithervoltage,photocurrent, orresistance_shuntv_from_iwill broadcast the smallest size of eithercurrent,photocurrent, orresistance_shuntIn those same comments @cwhanse proposed:
I agree with this idea, and both
i_from_vandv_from_ialready have some boilerplate code to address this (note this snippet is lifted fromv_from_i, with variable names suited for that function, change thearguments to other methods like singlediode or mpp):
Now knowing the
sizeand/or theshapeone can letnumpy.vectorizecast the output as proposed:On a related note: @cwhanse I realize now I misread your comment and made
i_from_vandv_from_ifail silently instead of gracefully, oops!. This might cause some confusion, in the short term, but maybe we can address it in this issue here?@wholmgren is there a
PVLibExceptionclass? What exception would you expect here, maybeValueError('All arguments must be either scalar or the same size.')?