# Buggy code
def find_MAP(
    start=None, vars=None, fmin=None, return_raw=False, model=None, *args, **kwargs
):
    """
    Sets state to the local maximum a posteriori point given a model.
    Current default of fmin_Hessian does not deal well with optimizing close
    to sharp edges, especially if they are the minimum.

    Parameters
    ----------
    start : `dict` of parameter values (Defaults to `model.test_point`)
    vars : list
        List of variables to set to MAP point (Defaults to all continuous).
    fmin : function
        Optimization algorithm (Defaults to `scipy.optimize.fmin_bfgs` unless
        discrete variables are specified in `vars`, then
        `scipy.optimize.fmin_powell` which will perform better).
    return_raw : Bool
        Whether to return extra value returned by fmin (Defaults to `False`)
    model : Model (optional if in `with` context)
    *args, **kwargs
        Extra args passed to fmin
    """
    model = modelcontext(model)
    if start is None:
        start = model.test_point

    if not set(start.keys()).issubset(model.named_vars.keys()):
        extra_keys = ", ".join(set(start.keys()) - set(model.named_vars.keys()))
        valid_keys = ", ".join(model.named_vars.keys())
        raise KeyError(
            "Some start parameters do not appear in the model!\n"
            "Valid keys are: {}, but {} was supplied".format(valid_keys, extra_keys)
        )

    if vars is None:
        vars = model.cont_vars
    vars = inputvars(vars)

    disc_vars = list(typefilter(vars, discrete_types))

    if disc_vars:
        pm._log.warning(
            "Warning: vars contains discrete variables. MAP "
            + "estimates may not be accurate for the default "
            + "parameters. Defaulting to non-gradient minimization "
            + "fmin_powell."
        )
        fmin = optimize.fmin_powell

    if fmin is None:
        if disc_vars:
            fmin = optimize.fmin_powell
        else:
            fmin = optimize.fmin_bfgs

    allinmodel(vars, model)

    start = Point(start, model=model)
    bij = DictToArrayBijection(ArrayOrdering(vars), start)

    logp = bij.mapf(model.fastlogp)
    dlogp = bij.mapf(model.fastdlogp(vars))

    def logp_o(point):
        return nan_to_high(-logp(point))

    def grad_logp_o(point):
        return nan_to_num(-dlogp(point))

    # Check to see if minimization function actually uses the gradient
    if "fprime" in getargspec(fmin).args:
        r = fmin(logp_o, bij.map(start), fprime=grad_logp_o, *args, **kwargs)
    else:
        # Check to see if minimization function uses a starting value
        if "x0" in getargspec(fmin).args:
            r = fmin(logp_o, bij.map(start), *args, **kwargs)
        else:
            r = fmin(logp_o, *args, **kwargs)

    if isinstance(r, tuple):
        mx0 = r[0]
    else:
        mx0 = r

    mx = bij.rmap(mx0)

    if (
        not allfinite(mx0)
        or not allfinite(model.logp(mx))
        or not allfinite(model.dlogp()(mx))
    ):
        messages = []
        for var in vars:
            vals = {
                "value": mx[var.name],
                "logp": var.logp(mx),
                "dlogp": var.dlogp()(mx),
            }

            def message(name, values):
                if np.size(values) < 10:
                    return name + " bad: " + str(values)
                else:
                    idx = np.nonzero(logical_not(isfinite(values)))
                    return (
                        name
                        + " bad at idx: "
                        + str(idx)
                        + " with values: "
                        + str(values[idx])
                    )

            messages += [
                message(var.name + "." + k, v)
                for k, v in vals.items()
                if not allfinite(v)
            ]

        specific_errors = "\n".join(messages)
        raise ValueError(
            "Optimization error: max, logp or dlogp at "
            + "max have non-finite values. Some values may be "
            + "outside of distribution support. max: "
            + repr(mx)
            + " logp: "
            + repr(model.logp(mx))
            + " dlogp: "
            + repr(model.dlogp()(mx))
            + "Check that "
            + "1) you don't have hierarchical parameters, "
            + "these will lead to points with infinite "
            + "density. 2) your distribution logp's are "
            + "properly specified. Specific issues: \n"
            + specific_errors
        )
    mx = {v.name: mx[v.name].astype(v.dtype) for v in model.vars}

    if return_raw:
        return mx, r
    else:
        return mx


# Traceback (AttributeError)
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
/mnt/sda1/JoeFiles/Joe_Home/PythonWorkarea/pyMCMCworks/MyModel_3.py in <module>()
87
88     # Inference...
---> 89     start = pm.find_MAP() # Find starting value by optimization
90 #    start = {'m': 14., 'a': 11.}
91

/mnt/sda1/JoeFiles/Joe_Home64/Python4Astronomy/lib/python2.7/site-packages/pymc/tuning/starting.pyc in find_MAP(start, vars, fmin, return_raw, disp, model, *args, **kwargs)
67
68     logp = bij.mapf(model.fastlogp)
---> 69     dlogp = bij.mapf(model.fastdlogp(vars))
70
71     def logp_o(point):

/mnt/sda1/JoeFiles/Joe_Home64/Python4Astronomy/lib/python2.7/site-packages/pymc/model.pyc in fastdlogp(self, vars)
71     def fastdlogp(self, vars=None):
72         """Compiled log probability density gradient function"""
---> 73         return self.model.fastfn(gradient(self.logpt, vars))
74
75     def fastd2logp(self, vars=None):

/mnt/sda1/JoeFiles/Joe_Home64/Python4Astronomy/lib/python2.7/site-packages/pymc/memoize.pyc in memoizer(*args, **kwargs)
12
13         if key not in cache:
---> 14             cache[key] = obj(*args, **kwargs)
15
16         return cache[key]

/mnt/sda1/JoeFiles/Joe_Home64/Python4Astronomy/lib/python2.7/site-packages/pymc/theanof.pyc in gradient(f, vars)
49         vars = cont_inputs(f)
50
---> 51     return t.concatenate([gradient1(f, v) for v in vars], axis=0)
52
53

/mnt/sda1/JoeFiles/Joe_Home64/Python4Astronomy/lib/python2.7/site-packages/pymc/theanof.pyc in gradient1(f, v)
41 def gradient1(f, v):
42     """flat gradient of f wrt v"""
---> 43     return t.flatten(t.grad(f, v, disconnected_inputs='warn'))
44
45

/mnt/sda1/JoeFiles/Joe_Home64/Python4Astronomy/lib/python2.7/site-packages/theano/gradient.pyc in grad(cost, wrt, consider_constant, disconnected_inputs, add_names, known_grads, return_disconnected)
527
528     rval = _populate_grad_dict(var_to_app_to_idx,
--> 529             grad_dict, wrt, cost_name)
530
531     for i in xrange(len(rval)):

/mnt/sda1/JoeFiles/Joe_Home64/Python4Astronomy/lib/python2.7/site-packages/theano/gradient.pyc in _populate_grad_dict(var_to_app_to_idx, grad_dict, wrt, cost_name)
1211         return grad_dict[var]
1212
-> 1213     rval = [access_grad_cache(elem) for elem in wrt]
1214
1215     return rval

/mnt/sda1/JoeFiles/Joe_Home64/Python4Astronomy/lib/python2.7/site-packages/theano/gradient.pyc in access_grad_cache(var)
1171                     for idx in node_to_idx[node]:
1172
-> 1173                         term = access_term_cache(node)[idx]
1174
1175                         if not isinstance(term, gof.Variable):

/mnt/sda1/JoeFiles/Joe_Home64/Python4Astronomy/lib/python2.7/site-packages/theano/gradient.pyc in access_term_cache(node)
1032                                 str(g_shape))
1033
-> 1034                 input_grads = node.op.grad(inputs, new_output_grads)
1035
1036                 if input_grads is None:

AttributeError: 'FromFunctionOp' object has no attribute 'grad'

# Fixed code
def find_MAP(
    start=None, vars=None, fmin=None, return_raw=False, model=None, *args, **kwargs
):
    """
    Sets state to the local maximum a posteriori point given a model.
    Current default of fmin_Hessian does not deal well with optimizing close
    to sharp edges, especially if they are the minimum.

    Parameters
    ----------
    start : `dict` of parameter values (Defaults to `model.test_point`)
    vars : list
        List of variables to set to MAP point (Defaults to all continuous).
    fmin : function
        Optimization algorithm (Defaults to `scipy.optimize.fmin_bfgs` unless
        discrete variables are specified in `vars`, then
        `scipy.optimize.fmin_powell` which will perform better).
    return_raw : Bool
        Whether to return extra value returned by fmin (Defaults to `False`)
    model : Model (optional if in `with` context)
    *args, **kwargs
        Extra args passed to fmin
    """
    model = modelcontext(model)
    if start is None:
        start = model.test_point

    if not set(start.keys()).issubset(model.named_vars.keys()):
        extra_keys = ", ".join(set(start.keys()) - set(model.named_vars.keys()))
        valid_keys = ", ".join(model.named_vars.keys())
        raise KeyError(
            "Some start parameters do not appear in the model!\n"
            "Valid keys are: {}, but {} was supplied".format(valid_keys, extra_keys)
        )

    if vars is None:
        vars = model.cont_vars
    vars = inputvars(vars)

    disc_vars = list(typefilter(vars, discrete_types))

    try:
        model.fastdlogp(vars)
        gradient_avail = True
    except AttributeError:
        gradient_avail = False

    if disc_vars or not gradient_avail:
        pm._log.warning(
            "Warning: gradient not available."
            + "(E.g. vars contains discrete variables). MAP "
            + "estimates may not be accurate for the default "
            + "parameters. Defaulting to non-gradient minimization "
            + "fmin_powell."
        )
        fmin = optimize.fmin_powell

    if fmin is None:
        if disc_vars:
            fmin = optimize.fmin_powell
        else:
            fmin = optimize.fmin_bfgs

    allinmodel(vars, model)

    start = Point(start, model=model)
    bij = DictToArrayBijection(ArrayOrdering(vars), start)

    logp = bij.mapf(model.fastlogp)

    def logp_o(point):
        return nan_to_high(-logp(point))

    # Check to see if minimization function actually uses the gradient
    if "fprime" in getargspec(fmin).args:
        dlogp = bij.mapf(model.fastdlogp(vars))

        def grad_logp_o(point):
            return nan_to_num(-dlogp(point))

        r = fmin(logp_o, bij.map(start), fprime=grad_logp_o, *args, **kwargs)
        compute_gradient = True
    else:
        compute_gradient = False

        # Check to see if minimization function uses a starting value
        if "x0" in getargspec(fmin).args:
            r = fmin(logp_o, bij.map(start), *args, **kwargs)
        else:
            r = fmin(logp_o, *args, **kwargs)

    if isinstance(r, tuple):
        mx0 = r[0]
    else:
        mx0 = r

    mx = bij.rmap(mx0)

    allfinite_mx0 = allfinite(mx0)
    allfinite_logp = allfinite(model.logp(mx))
    if compute_gradient:
        allfinite_dlogp = allfinite(model.dlogp()(mx))
    else:
        allfinite_dlogp = True

    if not allfinite_mx0 or not allfinite_logp or not allfinite_dlogp:
        messages = []
        for var in vars:
            vals = {"value": mx[var.name], "logp": var.logp(mx)}
            if compute_gradient:
                vals["dlogp"] = var.dlogp()(mx)

            def message(name, values):
                if np.size(values) < 10:
                    return name + " bad: " + str(values)
                else:
                    idx = np.nonzero(logical_not(isfinite(values)))
                    return (
                        name
                        + " bad at idx: "
                        + str(idx)
                        + " with values: "
                        + str(values[idx])
                    )

            messages += [
                message(var.name + "." + k, v)
                for k, v in vals.items()
                if not allfinite(v)
            ]

        specific_errors = "\n".join(messages)
        raise ValueError(
            "Optimization error: max, logp or dlogp at "
            + "max have non-finite values. Some values may be "
            + "outside of distribution support. max: "
            + repr(mx)
            + " logp: "
            + repr(model.logp(mx))
            + " dlogp: "
            + repr(model.dlogp()(mx))
            + "Check that "
            + "1) you don't have hierarchical parameters, "
            + "these will lead to points with infinite "
            + "density. 2) your distribution logp's are "
            + "properly specified. Specific issues: \n"
            + specific_errors
        )
    mx = {v.name: mx[v.name].astype(v.dtype) for v in model.vars}

    if return_raw:
        return mx, r
    else:
        return mx