docs: private Pair<Gradient, INDArray> getGradientsAndDelta(INDArray preOut, LayerWorkspaceMgr workspaceMgr) {
          ILossFunction lossFunction = layerConf().getLossFn();
          INDArray labels2d = getLabels2d(workspaceMgr, ArrayType.BP_WORKING_MEM);
          INDArray delta = lossFunction.computeGradient(labels2d, preOut, layerConf().getActivationFn(), maskArray);
          org.deeplearning4j.nn.conf.ocnn.OCNNOutputLayer conf = ( org.deeplearning4j.nn.conf.ocnn.OCNNOutputLayer) conf().getLayer();
  
  
          if(conf.getLastEpochSinceRUpdated() == 0 && epochCount == 0) {
              INDArray currentR = doOutput(false,workspaceMgr);
              if(window == null) {
                  window = Nd4j.createUninitializedDetached(conf.getWindowSize()).assign(0.0);
              }
  
              if(batchWindowSizeIndex < window.length() - currentR.length()) {
                  window.put(new INDArrayIndex[]{NDArrayIndex.interval(batchWindowSizeIndex,batchWindowSizeIndex + currentR.length())},currentR);
              }
              else if(batchWindowSizeIndex < window.length()) {
                  int windowIdx = (int) window.length() - batchWindowSizeIndex;
                  window.put(new INDArrayIndex[]{NDArrayIndex.interval(window.length() - windowIdx,window.length())},currentR.get(NDArrayIndex.interval(0,windowIdx)));
  
              }
  
              batchWindowSizeIndex += currentR.length();
              conf.setLastEpochSinceRUpdated(epochCount);
          }
          else if(conf.getLastEpochSinceRUpdated()  != epochCount) {
              double percentile = window.percentileNumber(100.0 * conf.getNu()).doubleValue();
              getParam(R_KEY).putScalar(0,percentile);
              conf.setLastEpochSinceRUpdated(epochCount);
              batchWindowSizeIndex = 0;
          }
          else {
              //track a running average per minibatch per epoch
              //calculate the average r value quantl=ile
              //once the epoch changes
  
              INDArray currentR = doOutput(false,workspaceMgr);
              window.put(new INDArrayIndex[]{NDArrayIndex.interval(batchWindowSizeIndex,batchWindowSizeIndex + currentR.length())},currentR);
          }
  
  
          Gradient gradient = new DefaultGradient();
          INDArray vGradView = gradientViews.get(V_KEY);
          double oneDivNu = 1.0 / layerConf().getNu();
          INDArray xTimesV = input.mmul(getParam(V_KEY));
          INDArray derivW = layerConf().getActivationFn().getActivation(xTimesV.dup(),true).negi();
          derivW = derivW.muliColumnVector(delta).mean(0).muli(oneDivNu).addi(getParam(W_KEY));
          gradient.setGradientFor(W_KEY,gradientViews.get(W_KEY).assign(derivW));
  
          //dG -> sigmoid derivative
  
          INDArray firstVertDerivV =  layerConf().getActivationFn()
                  .backprop(xTimesV.dup(),Nd4j.ones(input.dataType(), xTimesV.shape()))
                  .getFirst().muliRowVector(getParam(W_KEY).neg());
          firstVertDerivV = firstVertDerivV.muliColumnVector(delta)
                          .reshape('f',input.size(0),1,layerConf().getHiddenSize());
          INDArray secondTermDerivV = input.reshape('f',
                  input.size(0),getParam(V_KEY).size(0),1);
  
          long[]  shape = new long[firstVertDerivV.shape().length];
          for(int i = 0; i < firstVertDerivV.rank(); i++) {
              shape[i] = Math.max(firstVertDerivV.size(i),secondTermDerivV.size(i));
          }
  
          INDArray firstDerivVBroadcast = Nd4j.createUninitialized(input.dataType(), shape);
  
          INDArray mulResult = firstVertDerivV.broadcast(firstDerivVBroadcast);
          int[] bcDims = {0,1};
          Broadcast.mul(mulResult, secondTermDerivV, mulResult, bcDims);
  
          INDArray derivV = mulResult
                  .mean(0).muli(oneDivNu).addi(getParam(V_KEY));
          gradient.setGradientFor(V_KEY,vGradView.assign(derivV));
  
  
  
          INDArray derivR = Nd4j.scalar(delta.meanNumber()).muli(oneDivNu).addi(-1);
          gradient.setGradientFor(R_KEY,gradientViews.get(R_KEY).assign(derivR));
          clearNoiseWeightParams();
  
          delta = backpropDropOutIfPresent(delta);
          return new Pair<>(gradient, delta);
      }