Generate and evaluate random starts for Parameter objects.

def _random_starts(fun,
                   parameters,
                   jac,
                   args,
                   nstarts,
                   random_state,
                   data_gen=None):
    """Generate and evaluate random starts for Parameter objects."""
    if nstarts < 1:
        raise ValueError("nstarts has to be greater than or equal to 1")

    # Check to see if there are any random parameter types
    anyrand = any(flatten(map_recursive(lambda p: p.is_random, parameters),
                  returns_shapes=False))

    if not anyrand:
        log.info("No random parameters, not doing any random starts")
        params = map_recursive(lambda p: p.value, parameters, output_type=list)
        return params

    log.info("Evaluating random starts...")

    # Deal with gradient returns from objective function
    if jac is True:
        call_fun = lambda *fargs: fun(*fargs)[0]
    else:  # No gradient returns or jac is callable
        call_fun = fun

    # Randomly draw parameters and evaluate function
    def fun_eval():
        batch = next(data_gen) if data_gen else ()
        params = map_recursive(lambda p: p.rvs(random_state), parameters,
                               output_type=list)
        obj = call_fun(*chain(params, batch, args))
        return obj, params

    # Test randomly drawn parameters
    sample_gen = (fun_eval() for _ in range(nstarts))
    obj, params = min(sample_gen, key=lambda t: t[0])

    log.info("Best start found with objective = {}".format(obj))

    return flatten(params, returns_shapes=False)