docs: protected QLStepReturn<O> trainStep(O obs) {
  
          Integer action;
          INDArray input = getInput(obs);
          boolean isHistoryProcessor = getHistoryProcessor() != null;
  
  
          if (isHistoryProcessor)
              getHistoryProcessor().record(input);
  
          int skipFrame = isHistoryProcessor ? getHistoryProcessor().getConf().getSkipFrame() : 1;
          int historyLength = isHistoryProcessor ? getHistoryProcessor().getConf().getHistoryLength() : 1;
          int updateStart = getConfiguration().getUpdateStart()
                          + ((getConfiguration().getBatchSize() + historyLength) * skipFrame);
  
          Double maxQ = Double.NaN; //ignore if Nan for stats
  
          //if step of training, just repeat lastAction
          if (getStepCounter() % skipFrame != 0) {
              action = lastAction;
          } else {
              if (history == null) {
                  if (isHistoryProcessor) {
                      getHistoryProcessor().add(input);
                      history = getHistoryProcessor().getHistory();
                  } else
                      history = new INDArray[] {input};
              }
              //concat the history into a single INDArray input
              INDArray hstack = Transition.concat(Transition.dup(history));
              if (isHistoryProcessor) {
                  hstack.muli(1.0 / getHistoryProcessor().getScale());
              }
  
              //if input is not 2d, you have to append that the batch is 1 length high
              if (hstack.shape().length > 2)
                  hstack = hstack.reshape(Learning.makeShape(1, ArrayUtil.toInts(hstack.shape())));
  
              INDArray qs = getCurrentDQN().output(hstack);
              int maxAction = Learning.getMaxAction(qs);
  
              maxQ = qs.getDouble(maxAction);
              action = getEgPolicy().nextAction(hstack);
          }
  
          lastAction = action;
  
          StepReply<O> stepReply = getMdp().step(action);
  
          accuReward += stepReply.getReward() * configuration.getRewardFactor();
  
          //if it's not a skipped frame, you can do a step of training
          if (getStepCounter() % skipFrame == 0 || stepReply.isDone()) {
  
              INDArray ninput = getInput(stepReply.getObservation());
              if (isHistoryProcessor)
                  getHistoryProcessor().add(ninput);
  
              INDArray[] nhistory = isHistoryProcessor ? getHistoryProcessor().getHistory() : new INDArray[] {ninput};
  
              Transition<Integer> trans = new Transition(history, action, accuReward, stepReply.isDone(), nhistory[0]);
              getExpReplay().store(trans);
  
              if (getStepCounter() > updateStart) {
                  Pair<INDArray, INDArray> targets = setTarget(getExpReplay().getBatch());
                  getCurrentDQN().fit(targets.getFirst(), targets.getSecond());
              }
  
              history = nhistory;
              accuReward = 0;
          }
  
  
          return new QLStepReturn<O>(maxQ, getCurrentDQN().getLatestScore(), stepReply);
  
      }