docs: @Override
      public boolean optimize(LayerWorkspaceMgr workspaceMgr) {
          //validate the input before training
          INDArray gradient;
          INDArray searchDirection;
          INDArray parameters;
          Pair<Gradient, Double> pair = gradientAndScore(workspaceMgr);
          if (searchState.isEmpty()) {
              searchState.put(GRADIENT_KEY, pair.getFirst().gradient());
              try(MemoryWorkspace ws = Nd4j.getWorkspaceManager().scopeOutOfWorkspaces()) {
                  setupSearchState(pair); //Only do this once
              }
          } else {
              searchState.put(GRADIENT_KEY, pair.getFirst().gradient());
          }
  
          //calculate initial search direction
          try(MemoryWorkspace ws = Nd4j.getWorkspaceManager().scopeOutOfWorkspaces()) {
              preProcessLine();
          }
  
          gradient = (INDArray) searchState.get(GRADIENT_KEY);
          searchDirection = (INDArray) searchState.get(SEARCH_DIR);
          parameters = (INDArray) searchState.get(PARAMS_KEY);
  
          //perform one line search optimization
          try {
              step = lineMaximizer.optimize(parameters, gradient, searchDirection, workspaceMgr);
          } catch (InvalidStepException e) {
              log.warn("Invalid step...continuing another iteration: {}", e.getMessage());
              step = 0.0;
          }
  
          //Update parameters based on final/best step size returned by line search:
          if (step != 0.0) {
              // TODO: inject accumulation use here
              stepFunction.step(parameters, searchDirection, step); //Calculate params. given step size
              model.setParams(parameters);
          } else {
              log.debug("Step size returned by line search is 0.0.");
          }
  
          pair = gradientAndScore(workspaceMgr);
  
          //updates searchDirection
          try(MemoryWorkspace ws = Nd4j.getWorkspaceManager().scopeOutOfWorkspaces()) {
              postStep(pair.getFirst().gradient());
          }
  
          //invoke listeners
          int iterationCount = BaseOptimizer.getIterationCount(model);
          int epochCount = BaseOptimizer.getEpochCount(model);
          try (MemoryWorkspace workspace = Nd4j.getMemoryManager().scopeOutOfWorkspaces()) {
              for (TrainingListener listener : trainingListeners)
                  listener.iterationDone(model, iterationCount, epochCount);
          }
  
  
          //check for termination conditions based on absolute change in score
          incrementIterationCount(model, 1);
          applyConstraints(model);
          return true;
      }