Skip to content

BUG: WIP: inconsistent broadcasting of outputs from singlediode methods in #409 only #416

@mikofski

Description

@mikofski

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.')?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions