'perform a single batch update. Takes as input:
- batch of data (X)
- model (W)
- cost function which takes batch, model'

def step(self, batch, model, cost_function, **kwargs):

    learning_rate = kwargs.get('learning_rate', 0.0)
    update = kwargs.get('update', model.keys())
    grad_clip = kwargs.get('grad_clip', (-1))
    solver = kwargs.get('solver', 'vanilla')
    momentum = kwargs.get('momentum', 0)
    smooth_eps = kwargs.get('smooth_eps', 1e-08)
    decay_rate = kwargs.get('decay_rate', 0.999)
    if (not ((solver == 'vanilla') and (momentum == 0))):
        for u in update:
            if (not (u in self.step_cache_)):
                self.step_cache_[u] = np.zeros(model[u].shape)
                if (solver == 'adadelta'):
                    self.step_cache2_[u] = np.zeros(model[u].shape)
    cg = cost_function(batch, model)
    cost = cg['cost']
    grads = cg['grad']
    stats = cg['stats']
    if (grad_clip > 0):
        for p in update:
            if (p in grads):
                grads[p] = np.minimum(grads[p], grad_clip)
                grads[p] = np.maximum(grads[p], (- grad_clip))
    for p in update:
        if (p in grads):
            if (solver == 'vanilla'):
                if (momentum > 0):
                    dx = ((momentum * self.step_cache_[p]) - (learning_rate * grads[p]))
                    self.step_cache_[p] = dx
                else:
                    dx = ((- learning_rate) * grads[p])
            elif (solver == 'rmsprop'):
                self.step_cache_[p] = ((self.step_cache_[p] * decay_rate) + ((1.0 - decay_rate) * (grads[p] ** 2)))
                dx = ((- (learning_rate * grads[p])) / np.sqrt((self.step_cache_[p] + smooth_eps)))
            elif (solver == 'adagrad'):
                self.step_cache_[p] += (grads[p] ** 2)
                dx = ((- (learning_rate * grads[p])) / np.sqrt((self.step_cache_[p] + smooth_eps)))
            elif (solver == 'adadelta'):
                self.step_cache_[p] = ((self.step_cache_[p] * decay_rate) + ((1.0 - decay_rate) * (grads[p] ** 2)))
                dx = ((- np.sqrt(((self.step_cache2_[p] + smooth_eps) / (self.step_cache_[p] + smooth_eps)))) * grads[p])
                self.step_cache2_[p] = ((self.step_cache2_[p] * decay_rate) + ((1.0 - decay_rate) * (dx ** 2)))
            else:
                raise Exception(('solver    %s    not    supported' % (solver,)))
            model[p] += dx
    out = {}
    out['cost'] = cost
    out['stats'] = stats
    return out
