'Binds the current symbol to an executor and returns it.
We first declare the computation and then bind to the data to run.
This function returns an executor which provides method `forward()` method for evaluation
and a `outputs()` method to get all the results.
Example usage:
>>> a = mx.sym.Variable(\'a\')
>>> b = mx.sym.Variable(\'b\')
>>> c = a + b
<Symbol _plus1>
>>> ex = c.bind(ctx=mx.cpu(), args={\'a\' : mx.nd.ones([2,3]), \'b\' : mx.nd.ones([2,3])})
>>> ex.forward()
[<NDArray 2x3 @cpu(0)>]
>>> ex.outputs[0].asnumpy()
[[ 2.  2.  2.]
[ 2.  2.  2.]]
Parameters
ctx : Context
The device context the generated executor to run on.
args : list of NDArray or dict of str to NDArray
Input arguments to the symbol.
- If the input type is a list of `NDArray`, the order should be same as the order
of `list_arguments()`.
- If the input type is a dict of str to `NDArray`, then it maps the name of arguments
to the corresponding `NDArray`.
- In either case, all the arguments must be provided.
args_grad : list of NDArray or dict of str to `NDArray`, optional
When specified, `args_grad` provides NDArrays to hold
the result of gradient value in backward.
- If the input type is a list of `NDArray`, the order should be same as the order
of `list_arguments()`.
- If the input type is a dict of str to `NDArray`, then it maps the name of arguments
to the corresponding NDArray.
- When the type is a dict of str to `NDArray`, one only need to provide the dict
for required argument gradient.
Only the specified argument gradient will be calculated.
grad_req : {\'write\', \'add\', \'null\'}, or list of str or dict of str to str, optional
To specify how we should update the gradient to the `args_grad`.
- \'write\' means everytime gradient is write to specified `args_grad` `NDArray`.
- \'add\' means everytime gradient is add to the specified NDArray.
- \'null\' means no action is taken, the gradient may not be calculated.
aux_states : list of `NDArray`, or dict of str to `NDArray`, optional
Input auxiliary states to the symbol, only needed when the output of
`list_auxiliary_states()` is not empty.
- If the input type is a list of `NDArray`, the order should be same as the order
of `list_auxiliary_states()`.
- If the input type is a dict of str to `NDArray`, then it maps the name of
`auxiliary_states` to the corresponding `NDArray`,
- In either case, all the auxiliary states need to be provided.
group2ctx : Dict of string to mx.Context
The dict mapping the `ctx_group` attribute to the context assignment.
shared_exec : mx.executor.Executor
Executor to share memory with. This is intended for runtime reshaping, variable length
sequences, etc. The returned executor shares state with `shared_exec`, and should not be
used in parallel with it.
Returns
executor : Executor
The generated executor
Notes
Auxiliary states are the special states of symbols that do not correspond
to an argument, and do not have gradient but are still useful
for the specific operations. Common examples of auxiliary states include
the `moving_mean` and `moving_variance` states in `BatchNorm`.
Most operators do not have auxiliary states and in those cases,
this parameter can be safely ignored.
One can give up gradient by using a dict in `args_grad` and only specify
gradient they interested in.'

def bind(self, ctx, args, args_grad=None, grad_req='write', aux_states=None, group2ctx=None, shared_exec=None):

    if (not isinstance(ctx, Context)):
        raise TypeError('Context    type    error')
    listed_arguments = self.list_arguments()
    (args_handle, args) = self._get_ndarray_inputs('args', args, listed_arguments, False)
    if (args_grad is None):
        args_grad_handle = c_array(NDArrayHandle, ([None] * len(args)))
    else:
        (args_grad_handle, args_grad) = self._get_ndarray_inputs('args_grad', args_grad, listed_arguments, True)
    if (aux_states is None):
        aux_states = []
    (aux_args_handle, aux_states) = self._get_ndarray_inputs('aux_states', aux_states, self.list_auxiliary_states(), False)
    if isinstance(grad_req, string_types):
        if (grad_req not in _GRAD_REQ_MAP):
            raise ValueError(('grad_req    must    be    in    %s' % str(_GRAD_REQ_MAP)))
        reqs_array = c_array(mx_uint, ([mx_uint(_GRAD_REQ_MAP[grad_req])] * len(listed_arguments)))
    elif isinstance(grad_req, list):
        reqs_array = c_array(mx_uint, [mx_uint(_GRAD_REQ_MAP[item]) for item in grad_req])
    elif isinstance(grad_req, dict):
        req_array = []
        for name in listed_arguments:
            if (name in grad_req):
                req_array.append(mx_uint(_GRAD_REQ_MAP[grad_req[name]]))
            else:
                req_array.append(mx_uint(0))
        reqs_array = c_array(mx_uint, req_array)
    ctx_map_keys = []
    ctx_map_dev_types = []
    ctx_map_dev_ids = []
    if group2ctx:
        for (key, val) in group2ctx.items():
            ctx_map_keys.append(c_str(key))
            ctx_map_dev_types.append(ctypes.c_int(val.device_typeid))
            ctx_map_dev_ids.append(ctypes.c_int(val.device_id))
    handle = ExecutorHandle()
    shared_handle = (shared_exec.handle if (shared_exec is not None) else ExecutorHandle())
    check_call(_LIB.MXExecutorBindEX(self.handle, ctypes.c_int(ctx.device_typeid), ctypes.c_int(ctx.device_id), mx_uint(len(ctx_map_keys)), c_array(ctypes.c_char_p, ctx_map_keys), c_array(ctypes.c_int, ctx_map_dev_types), c_array(ctypes.c_int, ctx_map_dev_ids), mx_uint(len(args)), args_handle, args_grad_handle, reqs_array, mx_uint(len(aux_states)), aux_args_handle, shared_handle, ctypes.byref(handle)))
    executor = Executor(handle, self, ctx, grad_req, group2ctx)
    executor.arg_arrays = args
    executor.grad_arrays = args_grad
    executor.aux_arrays = aux_states
    return executor
