public static int[][] FindChargePairs(int nsilac, SilacType silacType, double matchPpm,
                                              double silacTimeCorrelationThreshold, SilacCluster[] silacClusters,
                                              IsotopeCluster[] isotopeClusters, IPeakList peakList, float[] intensities,
                                              double[] centerMz)
        {
            double[] m = new double[nsilac];
            for (int i = 0; i < m.Length; i++)
            {
                SilacCluster si = silacClusters[i];
                m[i] = GetUncalibratedSilacMass(si, silacType, isotopeClusters, centerMz, intensities);
            }
            int[] o = ArrayUtil.Order(m);
            m = ArrayUtil.SubArray(m, o);
            List <int[]> pairs = new List <int[]>();

            for (int i = 0; i < m.Length; i++)
            {
                SilacCluster si      = silacClusters[o[i]];
                int          chargei = isotopeClusters[si.IsotopeClusterindex1].Charge;
                double       m1      = m[i];
                int          ind1    = ArrayUtil.CeilIndex(m, m1 - matchPpm * 1e-6 * m1);
                for (int j = ind1; j < i; j++)
                {
                    SilacCluster sj      = silacClusters[o[j]];
                    int          chargej = isotopeClusters[sj.IsotopeClusterindex1].Charge;
                    if (chargei == chargej)
                    {
                        continue;
                    }
                    int      mini;
                    double[] pi = CalcSilacClusterProfile(si, out mini, silacType, isotopeClusters, peakList,
                                                          intensities);
                    int      minj;
                    double[] pj = CalcSilacClusterProfile(sj, out minj, silacType, isotopeClusters, peakList,
                                                          intensities);
                    if (T03SilacAssembly.Correlate(pi, mini, pj, minj) > silacTimeCorrelationThreshold)
                    {
                        pairs.Add(new int[] { o[i], o[j] });
                    }
                }
            }
            return(pairs.ToArray());
        }
        //** cluster the peaks **
        public static List <int[]> CalcClusterIndices(int minCharge, int maxCharge, double correlationThreshold, int peakCount,
                                                      double[] centerMz, float[] centerMzErrors, float[] minTimes,
                                                      float[] maxTimes,
                                                      Peak[] peaks, IPeakList peakList)
        {
            NeighbourList neighbourList = new NeighbourList();

            //** iterate through all peaks **
            for (int j = 0; j < peakCount; j++)
            {
                //** current peak's mz and RT range **
                double massJ      = centerMz[j];
                float  massErrorJ = centerMzErrors[j];
                float  timeMinJ   = minTimes[j];
                float  timeMaxJ   = maxTimes[j];

                //** get index of nearest peak at or above current mass -1.1 **
                int start = ArrayUtil.CeilIndex(centerMz, massJ - 1.1);

                //** get index of nearest peak at or below current mass -1.2
                int w = ArrayUtil.FloorIndex(centerMz, massJ - 1.2);

                //** remove any peaks outside of massj - 1.2 **
                //** so there's a removal to the left of this peak outside 1.2 away... **
                //** what is this all about?? **

                for (int i = 0; i < w; i++)
                {
                    if (peaks != null && peaks[i] != null)
                    {
                        peaks[i].Dispose();
                        peaks[i] = null;
                    }
                }

                //** iterate from current peak at mass - 1.1 to current peak **
                //** iterates through left "adjacent" traces, neihbors with current trace (j) if valid **
                for (int i = start; i < j; i++)
                {
                    //** comparing peak mz and RT range **
                    double massI      = centerMz[i];
                    double massErrorI = centerMzErrors[i];
                    double timeMinI   = minTimes[i];
                    double timeMaxI   = maxTimes[i];

                    //** difference in mass and synthesized mass error
                    double massDiff  = Math.Abs(massI - massJ);
                    double massError = 5 * Math.Sqrt(massErrorI * massErrorI + massErrorJ * massErrorJ);

                    //** invalidating conditions: **

                    //** 1) mass difference is greater than minimum **
                    if (massDiff > MolUtil.C13C12Diff + massError)
                    {
                        continue;
                    }

                    //** 2) no RT overlap
                    if (timeMinI >= timeMaxJ)
                    {
                        continue;
                    }

                    //** 2) no RT overlap
                    if (timeMinJ >= timeMaxI)
                    {
                        continue;
                    }

                    //** 3) mass difference doesn't match any charge states **
                    if (!FitsMassDifference(massDiff, massError, minCharge, maxCharge))
                    {
                        continue;
                    }

                    //** 4) The intensity profile correlation (cosine similarity) fails the threshold **
                    if (CalcCorrelation(peakList.GetPeakKeep(i), peakList.GetPeakKeep(j)) < correlationThreshold)
                    {
                        continue;
                    }

                    //** create an edge between peak I and peak J if valid: **
                    //** 1) mass difference exceeds minimum
                    //** 2) RT has overlap
                    //** 3) mass difference fits a charge state
                    //** 4) intensity profiles have strong correlation
                    neighbourList.Add(i, j);
                }
            }

            //** convert edge list to clusters! **
            List <int[]> clusterList = new List <int[]>();

            //** iterate through all peaks **
            for (int i = 0; i < peakCount; i++)
            {
                //** if the peak has neighbors... **
                if (!neighbourList.IsEmptyAt(i))
                {
                    HashSet <int> currentCluster = new HashSet <int>();

                    AddNeighbors(i, currentCluster, neighbourList);
                    int[] c = SortByMass(currentCluster.ToArray(), centerMz);
                    clusterList.Add(c);
                }
            }
            return(clusterList);
        }