gt:         for (int i = 2; i <= maxCandidates; i++) {
context: package de.unijena.bioinf.ms.cli;
  
  import com.google.common.collect.Iterables;
  import com.lexicalscope.jewel.cli.ArgumentValidationException;
  import de.unijena.bioinf.ChemistryBase.SimpleRectangularIsolationWindow;
  import de.unijena.bioinf.ChemistryBase.algorithm.Scored;
  import de.unijena.bioinf.ChemistryBase.chem.Ionization;
  import de.unijena.bioinf.ChemistryBase.chem.MolecularFormula;
  import de.unijena.bioinf.ChemistryBase.chem.PeriodicTable;
  import de.unijena.bioinf.ChemistryBase.chem.PrecursorIonType;
  import de.unijena.bioinf.ChemistryBase.ms.*;
  import de.unijena.bioinf.ChemistryBase.ms.ft.FTree;
  import de.unijena.bioinf.ChemistryBase.ms.ft.TreeScoring;
  import de.unijena.bioinf.ChemistryBase.ms.inputValidators.ChimericAnnotator;
  import de.unijena.bioinf.ChemistryBase.ms.inputValidators.LowIntensityAnnotator;
  import de.unijena.bioinf.ChemistryBase.ms.inputValidators.NoMs1PeakAnnotator;
  import de.unijena.bioinf.ChemistryBase.ms.inputValidators.QualityAnnotator;
  import de.unijena.bioinf.ChemistryBase.properties.PropertyManager;
  import de.unijena.bioinf.ChemistryBase.sirius.projectspace.Index;
  import de.unijena.bioinf.GibbsSampling.ZodiacUtils;
  import de.unijena.bioinf.GibbsSampling.model.*;
  import de.unijena.bioinf.GibbsSampling.model.distributions.*;
  import de.unijena.bioinf.GibbsSampling.model.scorer.CommonFragmentAndLossScorer;
  import de.unijena.bioinf.GibbsSampling.model.scorer.EdgeScorings;
  import de.unijena.bioinf.babelms.MsExperimentParser;
  import de.unijena.bioinf.babelms.ms.JenaMsWriter;
  import de.unijena.bioinf.sirius.IdentificationResult;
  import de.unijena.bioinf.sirius.Ms2DatasetPreprocessor;
  import de.unijena.bioinf.sirius.Sirius;
  import de.unijena.bioinf.sirius.projectspace.*;
  import org.slf4j.LoggerFactory;
  import oshi.SystemInfo;
  
  import java.io.BufferedWriter;
  import java.io.File;
  import java.io.IOException;
  import java.nio.charset.Charset;
  import java.nio.file.Files;
  import java.nio.file.Path;
  import java.nio.file.Paths;
  import java.text.ParseException;
  import java.util.*;
  import java.util.concurrent.ExecutionException;
  
  
  /**
   * Created by ge28quv on 18/05/17.
   */
  public class Zodiac {
      private static final  org.slf4j.Logger LOG = LoggerFactory.getLogger(Zodiac.class);
      private Path outputPath;
      private Path libraryHitsFile;
      private Path workSpacePath;
      private ZodiacOptions options;
      private int maxCandidates;
  
  
      public Zodiac(ZodiacOptions options) {
          testOptions(options);
          this.workSpacePath = Paths.get(options.getSirius());
          this.libraryHitsFile = (options.getLibraryHitsFile() == null ? null : Paths.get(options.getLibraryHitsFile()));
          this.outputPath = Paths.get(options.getOutput());
          this.options = options;
      }
  
      private void testOptions(ZodiacOptions options){
          //this is for compatibility with new workflow
          if (options.getOutput()==null){
              throw new ArgumentValidationException("Option is mandatory: --output -o value : output directory");
          }
          if (options.getSirius()==null){
              throw new ArgumentValidationException("Option is mandatory: --sirius -s value : Sirius output directory or workspace. This is the input for Zodiac");
          }
      }
  
      public void run() {
  
          //todo problem with name/id simplification
          //todo force name = id
          maxCandidates = (options.getNumberOfCandidates() == null ? Integer.MAX_VALUE : options.getNumberOfCandidates());
          Path originalSpectraPath = Paths.get(options.getSpectraFile());
          try {
              //todo For the official release zodiac should become a job an create subjobs in the jobmanager for multithreading
  //            int workerCount = PropertyManager.getNumberOfCores();
              int workerCount = options.getNumOfCores()>0 ? options.getNumOfCores() : (new SystemInfo()).getHardware().getProcessor().getPhysicalProcessorCount()-1;
              PropertyManager.PROPERTIES.setProperty("de.unijena.bioinf.sirius.cpu.cores", String.valueOf(workerCount));
  
  
              FilenameFormatter filenameFormatter = null;
              if (options.getNamingConvention()!=null){
                  String formatString = options.getNamingConvention();
                  try {
                      filenameFormatter = new StandardMSFilenameFormatter(formatString);
                  } catch (ParseException e) {
                      LOG.error("Cannot parse naming convention:\n" + e.getMessage(), e);
                      System.exit(1);
                  }
              } else {
                  //default
                  filenameFormatter = new StandardMSFilenameFormatter();
              }
  
  
              //create output dir
              if (Files.exists(outputPath)){
                  if (!Files.isDirectory(outputPath)){
                      LOG.error("specified output path must be a directory.");
                      return;
                  }
              } else {
                  Files.createDirectories(outputPath);
              }
  
  
              List<ExperimentResult> experimentResults = newLoad(workSpacePath.toFile());
              //todo reads original experiments twice!
              if (options.getIsolationWindowWidth()!=null){
                  double width = options.getIsolationWindowWidth();
                  double shift = options.getIsolationWindowShift();
                  experimentResults = updateQuality(experimentResults, originalSpectraPath, width, shift, outputPath);
              } else {
                  experimentResults = updateQuality(experimentResults, originalSpectraPath, -1d, -1d, outputPath);
              }
  
  
              if (options.isOnlyComputeStats()){
                  return;
              }
  
              List<LibraryHit> anchors = (libraryHitsFile==null)?null:ZodiacUtils.parseLibraryHits(libraryHitsFile, experimentResults, LOG); //only specific GNPS format
  
  
              NodeScorer[] nodeScorers;
              boolean useLibraryHits = (libraryHitsFile != null);
              double libraryLambda = options.getLibraryScoreLambda();//todo which lambda to use!?
              double lowestCosine = options.getLowestCosine();
              if (useLibraryHits) {
                  Reaction[] reactions = ZodiacUtils.parseReactions(1);
                  Set<MolecularFormula> netSingleReactionDiffs = new HashSet<>();
                  for (Reaction reaction : reactions) {
                      netSingleReactionDiffs.add(reaction.netChange());
                  }
                  nodeScorers = new NodeScorer[]{new StandardNodeScorer(true, 1d), new LibraryHitScorer(libraryLambda, lowestCosine, netSingleReactionDiffs)};
              } else {
                  nodeScorers = new NodeScorer[]{new StandardNodeScorer(true, 1d)};
              }
  
  
              EdgeFilter edgeFilter = null;
  
              if (options.getThresholdFilter() > 0.0D && (options.getLocalFilter() > 0.0D || options.getMinLocalConnections() > 0d)){
                  int numberOfCandidates = Math.max(options.getLocalFilter(),1);
                  int numberOfConnections = options.getMinLocalConnections()>0?options.getMinLocalConnections():10;
                  edgeFilter = new EdgeThresholdMinConnectionsFilter(options.getThresholdFilter(), numberOfCandidates, numberOfConnections);
              } else if (options.getThresholdFilter() > 0.0D) {
                  edgeFilter = new EdgeThresholdFilter(options.getThresholdFilter());
              } else if (options.getLocalFilter() > 0.0D) {
                  edgeFilter = new LocalEdgeFilter(options.getLocalFilter());
              }
              if (edgeFilter == null) {
                  edgeFilter = new EdgeThresholdFilter(0);
              }
  
  
              boolean estimateByMedian = true;
              ScoreProbabilityDistribution probabilityDistribution = null;
              if (options.getProbabilityDistribution().equals(EdgeScorings.exponential)) {
                  probabilityDistribution = new ExponentialDistribution(estimateByMedian);
              } else if (options.getProbabilityDistribution().equals(EdgeScorings.lognormal)) {
                  probabilityDistribution = new LogNormalDistribution(estimateByMedian);
              } else {
                  LOG.error("probability distribution is unknown. Use 'lognormal' or 'exponential'.");
                  return;
              }
  
  
  
              double minimumOverlap = 0.0D;
              ScoreProbabilityDistributionEstimator commonFragmentAndLossScorer;
              if (options.isEstimateDistribution()){
                  commonFragmentAndLossScorer = new ScoreProbabilityDistributionEstimator(new CommonFragmentAndLossScorer(minimumOverlap), probabilityDistribution, options.getThresholdFilter());
              } else {
                  commonFragmentAndLossScorer = new ScoreProbabilityDistributionFix(new CommonFragmentAndLossScorer(minimumOverlap), probabilityDistribution, options.getThresholdFilter());
              }
  
              EdgeScorer[] edgeScorers = new EdgeScorer[]{commonFragmentAndLossScorer};
  
  
  
              de.unijena.bioinf.GibbsSampling.Zodiac zodiac = new de.unijena.bioinf.GibbsSampling.Zodiac(experimentResults, anchors, nodeScorers, edgeScorers, edgeFilter, maxCandidates, options.isClusterCompounds(), !options.isOnlyOneStepZodiac());
  
  
              ZodiacResultsWithClusters zodiacResult = zodiac.compute(options.getIterationSteps(), options.getBurnInSteps(), options.getSeparateRuns());
              if (zodiacResult==null) return; //no results. likely, empty input
  
              CompoundResult<FragmentsCandidate>[] result = zodiacResult.getResults();
              String[] ids = zodiacResult.getIds();
  
  
              Map<String, ExperimentResult> experimentResultMap = createMap(experimentResults);
              Map<String, String[]> representativeToCluster = zodiacResult.getRepresentativeToCluster();
              Scored<IdentificationResult>[] bestInitial = bestInitial(ids, experimentResultMap);
              writeZodiacOutput(ids, bestInitial, result, outputPath.resolve("zodiac_summary.csv"));
              writeClusters(representativeToCluster, outputPath.resolve("clusters.csv"));
              writeSpectra(ids, result, experimentResultMap, outputPath, filenameFormatter);
  
          } catch (IOException e) {
              LOG.error("Error while running ZODIAC: " + e.getMessage(), e);
          } catch (ExecutionException e) {
              e.printStackTrace();
          }
      }
  
      protected List<ExperimentResult> updateQuality(List<ExperimentResult> experimentResults, Path originalMsInformation, double isolationWindowWidth, double isolationWindowShift, Path outputDir) throws IOException {
          final MsExperimentParser parser = new MsExperimentParser();
          List<Ms2Experiment> rawExperiments = parser.getParser(originalMsInformation.toFile()).parseFromFile(originalMsInformation.toFile());
          Map<String, List<Ms2Experiment>> nameToExperiment = new HashMap<>();
          for (Ms2Experiment rawExperiment : rawExperiments) {
              String name = rawExperiment.getName();
              List<Ms2Experiment> experimentList = nameToExperiment.get(name);
              if (experimentList==null){
                  //should always be the case?
                  experimentList = new ArrayList<>();
                  nameToExperiment.put(name, experimentList);
              }
              experimentList.add(rawExperiment);
          }
  
          List<Ms2Experiment> allExperiments = new ArrayList<>();
          for (ExperimentResult result : experimentResults) {
              MutableMs2Experiment mutableMs2Experiment = new MutableMs2Experiment(result.getExperiment());
              String name = mutableMs2Experiment.getName();
              List<Ms2Experiment> experimentList = nameToExperiment.get(name);
              Ms2Experiment experiment2 = null;
              if (experimentList.size()==1){
                  experiment2 = experimentList.get(0);
              } else if (experimentList.size()>1){
                  for (Ms2Experiment experiment : experimentList) {
                      if (Math.abs(mutableMs2Experiment.getIonMass()-experiment.getIonMass())<1e-15){
                          experiment2 = experiment;
                          break;
                      }
                  }
              }
              if (experiment2==null){
                  LOG.error("cannot find original MS data for compound in sirius workspace: "+mutableMs2Experiment.getName());
              } else {
                  mutableMs2Experiment.setMergedMs1Spectrum(experiment2.getMergedMs1Spectrum());
                  mutableMs2Experiment.setMs1Spectra(experiment2.getMs1Spectra());
                  mutableMs2Experiment.setMs2Spectra(experiment2.getMs2Spectra());
              }
              allExperiments.add(mutableMs2Experiment);
  
          }
          Ms2Dataset dataset = new MutableMs2Dataset(allExperiments, "default", Double.NaN, (new Sirius("default")).getMs2Analyzer().getDefaultProfile());
          Ms2DatasetPreprocessor preprocessor = new Ms2DatasetPreprocessor(true);
  
          List<QualityAnnotator> qualityAnnotators = new ArrayList<>();
          qualityAnnotators.add(new NoMs1PeakAnnotator(Ms2DatasetPreprocessor.FIND_MS1_PEAK_DEVIATION));
  //        qualityAnnotators.add(new FewPeaksAnnotator(Ms2DatasetPreprocessor.MIN_NUMBER_OF_PEAKS));
          qualityAnnotators.add(new LowIntensityAnnotator(Ms2DatasetPreprocessor.FIND_MS1_PEAK_DEVIATION, 0.01, Double.NaN));
          double max2ndMostIntenseRatio = 0.33;
          double maxSummedIntensitiesRatio = 1.0;
          qualityAnnotators.add(new ChimericAnnotator(Ms2DatasetPreprocessor.FIND_MS1_PEAK_DEVIATION, max2ndMostIntenseRatio, maxSummedIntensitiesRatio));
  
          preprocessor.setQualityAnnotators(qualityAnnotators);
  
  
          if (isolationWindowWidth>0){
              double right = isolationWindowWidth/2d+isolationWindowShift;
              double left = -isolationWindowWidth/2d+isolationWindowShift;
              ((MutableMs2Dataset) dataset).setIsolationWindow(new SimpleRectangularIsolationWindow(left, right));
          }
  
          dataset = preprocessor.preprocess(dataset);
          allExperiments = dataset.getExperiments();
  
          int pos = 0;
          List<ExperimentResult> newExperimentResults = new ArrayList<>();
          for (ExperimentResult result : experimentResults) {
              List<FTree> trees = new ArrayList<>();
              Ms2Experiment experiment = allExperiments.get(pos++); //use experiments with assigned quality
              for (IdentificationResult identificationResult : result.getResults()) {
  //                trees.add(identificationResult.getRawTree());
                  trees.add(identificationResult.getResolvedTree()); //todo use rawTree or resolvedTree?!
              }
  
  
              if (!atLeastOneTreeExplainsSomeIntensity(trees, 0.5)){
                  CompoundQuality.setProperty(experiment, SpectrumProperty.PoorlyExplained);
              }
              if (!atLeastOneTreeExplainsSomePeaks(trees, 3)){
                  CompoundQuality.setProperty(experiment, SpectrumProperty.PoorlyExplained);
              }
  
  
              newExperimentResults.add(new ExperimentResult(experiment, result.getResults()));
          }
  
          if (outputDir!=null){
              try {
                  Path qualityPath = outputDir.resolve("spectra_quality.csv");
                  Ms2Dataset dataset2 = new MutableMs2Dataset(allExperiments, "default", Double.NaN, (new Sirius("default")).getMs2Analyzer().getDefaultProfile());
                  SpectrumProperty[] usedProperties = CompoundQuality.getUsedProperties(dataset2);
                  preprocessor.writeExperimentInfos(dataset2, qualityPath, usedProperties);
  
  //            if (dataset.getIsolationWindow()!=null){
  //
  //            }
                  dataset.getIsolationWindow().writeIntensityRatiosToCsv(dataset, outputDir.resolve("isolation_window_intensities.csv"));
  
                  Path summary = outputDir.resolve("data_summary.csv");
                  System.out.println("write summary");
                  preprocessor.writeDatasetSummary(dataset, summary);
                  System.out.println("writing summary ended");
              } catch (Exception e){
                  LOG.error("Error while writing quality statistics.", e);
              }
  
          }
  
          return newExperimentResults;
      }
  
      public static boolean atLeastOneTreeExplainsSomeIntensity(List<FTree> trees, double threshold){
          for (FTree tree : trees) {
              final double intensity = tree.getAnnotationOrThrow(TreeScoring.class).getExplainedIntensity();
              if (intensity>threshold) return true;
          }
          return false;
      }
  
      public static boolean atLeastOneTreeExplainsSomePeaks(List<FTree> trees, int threshold){
          for (FTree tree : trees) {
              if (tree.numberOfVertices()>=threshold) return true;
          }
          return false;
      }
  
      private final static int NUMBER_OF_HITS = Integer.MAX_VALUE;
      private final static String SEP = "\t";
      public static void writeZodiacOutput(String[] ids, Scored<IdentificationResult>[] initial, CompoundResult<FragmentsCandidate>[] result, Path outputPath) throws IOException {
          BufferedWriter writer = Files.newBufferedWriter(outputPath, Charset.defaultCharset());
          writer.write("id" + SEP + "quality" + SEP + "precursorMass"+ SEP + "ionsByMs1" + SEP + "SiriusMF" + SEP + "SiriusScore" + SEP + "connectedCompounds" + SEP + "biggestTreeSize" + SEP + "maxExplainedIntensity" + SEP + "ZodiacMF" + SEP + "ZodiacMFIon"+ SEP + "ZodiacScore" + SEP + "treeSize");
  
          int maxCandidates = maxNumberOfCandidates(result);
  