| | |
| | | <Compile Remove="Class1 - å¤å¶.cs" /> |
| | | <Compile Remove="DataFusionTest - æåçæ¬.cs" /> |
| | | <Compile Remove="Helper.cs" /> |
| | | <Compile Remove="helper\PumpCurveDataFusionCorrectorHelper2 - deepseek - å¯éèåå¤ä»½.cs" /> |
| | | <Compile Remove="Program - å¤å¶%282%29.cs" /> |
| | | <Compile Remove="Program - å¤å¶%283%29.cs" /> |
| | | <Compile Remove="Program - å¤å¶%284%29.cs" /> |
ÎļþÃû´Ó 02-desktop/Desktop/IStation.Test/Program -DataFusionCorrector - ¸´ÖÆ.cs ÐÞ¸Ä |
| | |
| | | var splineList = new List<CurvePoint>(); |
| | | var measuredList = new List<CurvePoint>(); |
| | | |
| | | var fileName = "24_37"; |
| | | var fileName = "23_34"; |
| | | var path = AppDomain.CurrentDomain.BaseDirectory; |
| | | var lienPaht = path + $@"\pumpcsv\{fileName}_old_curve.csv"; |
| | | var measuredPath = path + $@"\pumpcsv\{fileName}.csv"; |
| | |
| | | |
| | | |
| | | |
| | | var helper = new PumpCurveDataFusionCorrectorHelper2(); |
| | | var helper = new PumpCurveDataFusionCorrectorHelper2DeepSeek(); //PumpCurveDataFusionCorrectorHelper2DeepSeek |
| | | (double[] mergedX, double[] mergedY, double[] optimizedX, double[] optimizedY) = helper.Corrent(splineX, splineY, measuredXAll, measuredYAll); |
| | | |
| | | Console.WriteLine($"{splineX.Min()},{splineX.Max()}"); |
| | |
| | | var measuredXAll = flagHzRecordList.Select(x => x.FlowRate).ToArray(); |
| | | var measuredYAll = flagHzRecordList.Select(x => x.Head).ToArray(); |
| | | |
| | | var helper = new PumpCurveDataFusionCorrectorHelper(); |
| | | (double[] mergedX, double[] mergedY, double[] optimizedX, double[] optimizedY) = helper.Corrent(splineX, splineY, measuredXAll, measuredYAll); |
| | | //var helper = new PumpCurveDataFusionCorrectorHelper2(); |
| | | //(double[] mergedX, double[] mergedY, double[] optimizedX, double[] optimizedY) = helper.Corrent(splineX, splineY, measuredXAll, measuredYAll); |
| | | |
| | | if (optimizedX == null) |
| | | { |
| | | continue; |
| | | } |
| | | var optList = new List<CurvePoint>(); |
| | | for (int i = 0; i < optimizedX.Length; i++) |
| | | { |
| | | var x = optimizedX[i]; |
| | | var y = optimizedY[i]; |
| | | optList.Add(new CurvePoint(x, y)); |
| | | } |
| | | //if (optimizedX == null) |
| | | //{ |
| | | // continue; |
| | | //} |
| | | //var optList = new List<CurvePoint>(); |
| | | //for (int i = 0; i < optimizedX.Length; i++) |
| | | //{ |
| | | // var x = optimizedX[i]; |
| | | // var y = optimizedY[i]; |
| | | // optList.Add(new CurvePoint(x, y)); |
| | | //} |
| | | |
| | | CsvHelper.ExportToCsv(optList, Path.Combine(fullPath, $"{flag}_{50}_update_curve.csv")); |
| | | //CsvHelper.ExportToCsv(optList, Path.Combine(fullPath, $"{flag}_{50}_update_curve.csv")); |
| | | } |
| | | } |
| | | else |
| | |
| | | var measuredYAll = flagHzRecordList.Select(x => x.Head).ToArray(); |
| | | |
| | | |
| | | var helper = new PumpCurveDataFusionCorrectorHelper(); |
| | | (double[] mergedX, double[] mergedY, double[] optimizedX, double[] optimizedY) = helper.Corrent(splineX, splineY, measuredXAll, measuredYAll); |
| | | //var helper = new PumpCurveDataFusionCorrectorHelper2(); |
| | | //(double[] mergedX, double[] mergedY, double[] optimizedX, double[] optimizedY) = helper.Corrent(splineX, splineY, measuredXAll, measuredYAll); |
| | | |
| | | |
| | | if (optimizedX == null) |
| | | { |
| | | continue; |
| | | } |
| | | var optList = new List<CurvePoint>(); |
| | | for (int i = 0; i < optimizedX.Length; i++) |
| | | { |
| | | var x = optimizedX[i]; |
| | | var y = optimizedY[i]; |
| | | optList.Add(new CurvePoint(x, y)); |
| | | } |
| | | //if (optimizedX == null) |
| | | //{ |
| | | // continue; |
| | | //} |
| | | //var optList = new List<CurvePoint>(); |
| | | //for (int i = 0; i < optimizedX.Length; i++) |
| | | //{ |
| | | // var x = optimizedX[i]; |
| | | // var y = optimizedY[i]; |
| | | // optList.Add(new CurvePoint(x, y)); |
| | | //} |
| | | |
| | | CsvHelper.ExportToCsv(optList, Path.Combine(fullPath, $"{flag}_{hz}_update_curve.csv")); |
| | | //CsvHelper.ExportToCsv(optList, Path.Combine(fullPath, $"{flag}_{hz}_update_curve.csv")); |
| | | } |
| | | } |
| | | |
¶Ô±ÈÐÂÎļþ |
| | |
| | | using Accord.Math; |
| | | using Accord.Math.Optimization; |
| | | using DevExpress.Map.Native; |
| | | using MathNet.Numerics; |
| | | using MathNet.Numerics.LinearAlgebra; |
| | | using MathNet.Numerics.LinearRegression; |
| | | using MathNet.Numerics.Optimization; |
| | | using MathNet.Numerics.Statistics; |
| | | using System.Collections.Concurrent; |
| | | using System.Data; |
| | | using System.Runtime.CompilerServices; |
| | | |
| | | namespace IStation.Test |
| | | { |
| | | /// <summary> |
| | | /// æ³µæ²çº¿æ°æ®èåæ ¡æ£å¨ï¼æ§è½ä¼åçï¼ |
| | | /// </summary> |
| | | public class PumpCurveDataFusionCorrectorHelper2DeepSeek |
| | | { |
| | | #region é
ç½®åæ° |
| | | // å¼å¸¸å¼æ£æµéå¼ï¼åºäºæ 忣æåå¸ï¼2.5对åº98.76%置信åºé´ï¼ |
| | | public const double ZScoreThreshold = 2; |
| | | // è¿æ¸¡åºå宽度ï¼åä½ä¸è¾å
¥æ°æ®ç¸åï¼æ§å¶æ²çº¿è¡æ¥å¹³æ»åº¦ï¼ |
| | | public const double TransitionWidth = 500; |
| | | // å¤é¡¹å¼æåé¶æ°ï¼ä¸æ¬¡å¹³è¡¡æåè½åä¸è¿æåé£é©ï¼ |
| | | public const int PolynomialDegree = 3; |
| | | // ä¼å卿大è¿ä»£æ¬¡æ°ï¼Cobylaç®æ³æ¶æéå¼ï¼ |
| | | private const int OptimizationMaxIterations = 1000; |
| | | |
| | | // 卿å¤é¡¹å¼æåé¶æ°ï¼ç¨äºå¨æè°æ´ï¼ |
| | | public int DynamicPolyDegree = 2; |
| | | |
| | | // æµéæ°æ®è¿æ»¤æ¯ä¾%ï¼æ§å¶å¼å¸¸å¼è¿æ»¤å¼ºåº¦ï¼ |
| | | public double MeasuredXFilterRatio = 0.15; |
| | | |
| | | # endregion |
| | | |
| | | #region æ ¸å¿ç®æ³ |
| | | public (double[] mergedX, double[] mergedY, double[] optimizedX, double[] optimizedY) Corrent( |
| | | double[] splineX, double[] splineY, |
| | | double[] measuredXAll, double[] measuredYAll) |
| | | { |
| | | if (measuredXAll.Length != measuredYAll.Length) |
| | | return default; |
| | | |
| | | // ä¼å1ï¼ä½¿ç¨Array.BinarySearchæ¿ä»£FindIndex |
| | | double threshold = measuredXAll[0] + MeasuredXFilterRatio * (measuredXAll[^1] - measuredXAll[0]); |
| | | int startIndex = Array.BinarySearch(measuredXAll, threshold); |
| | | startIndex = (startIndex < 0) ? ~startIndex : startIndex; |
| | | |
| | | // ä¼å2ï¼æ°ç»åç代æ¿Skip |
| | | var measuredXAllFiltered = new ArraySegment<double>(measuredXAll, startIndex, measuredXAll.Length - startIndex).ToArray(); |
| | | var measuredYAllFiltered = new ArraySegment<double>(measuredYAll, startIndex, measuredYAll.Length - startIndex).ToArray(); |
| | | |
| | | if (measuredXAllFiltered.Length < 5) |
| | | { |
| | | Console.WriteLine("å¼å¸¸å¼è¿æ»¤åæ°æ®è¿å°"); |
| | | return default; |
| | | } |
| | | |
| | | #region 稳å¥åå½ä¼å |
| | | // ä¼å3ï¼é¢åé
æ°ç» |
| | | var residuals = new double[measuredXAllFiltered.Length]; |
| | | (double intercept, double slope) = SimpleRegression.Fit(measuredXAllFiltered, measuredYAllFiltered); |
| | | |
| | | // ä¼å4ï¼å并计ç®å¾ªç¯ |
| | | double meanResidual = 0, stdResidual = 0; |
| | | for (int i = 0; i < measuredXAllFiltered.Length; i++) |
| | | { |
| | | double predicted = slope * measuredXAllFiltered[i] + intercept; |
| | | residuals[i] = measuredYAllFiltered[i] - predicted; |
| | | meanResidual += residuals[i]; |
| | | } |
| | | meanResidual /= residuals.Length; |
| | | for (int i = 0; i < residuals.Length; i++) |
| | | { |
| | | stdResidual += Math.Pow(residuals[i] - meanResidual, 2); |
| | | } |
| | | stdResidual = Math.Sqrt(stdResidual / residuals.Length); |
| | | |
| | | // ä¼å5ï¼å¹¶è¡è¿æ»¤ |
| | | var validData = new List<(double X, double Y)>(measuredXAllFiltered.Length); |
| | | for (int i = 0; i < residuals.Length; i++) |
| | | { |
| | | if (Math.Abs((residuals[i] - meanResidual) / stdResidual) < ZScoreThreshold) |
| | | { |
| | | validData.Add((measuredXAllFiltered[i], measuredYAllFiltered[i])); |
| | | } |
| | | } |
| | | #endregion |
| | | |
| | | |
| | | |
| | | return ProcessData(splineX, splineY, |
| | | validData.Select(d => d.X).ToArray(), |
| | | validData.Select(d => d.Y).ToArray()); |
| | | } |
| | | #endregion |
| | | |
| | | #region æ°æ®å¤çæµç¨ä¼å |
| | | private (double[] mergedX, double[] mergedY, double[] optimizedX, double[] optimizedY) ProcessData( |
| | | double[] splineX, double[] splineY, |
| | | double[] measuredXValid, double[] measuredYValid) |
| | | { |
| | | // ä¼å6ï¼ç¼åæå¼è®¡ç® |
| | | double minX = measuredXValid[0], maxX = measuredXValid[^1]; |
| | | |
| | | // ä¼å7ï¼å¹¶è¡èç±»è®¡ç® |
| | | var (labels, areas) = new OptimizedPointClassifier().ClassifyPoints(measuredXValid, measuredYValid, "dbscan", new Dictionary<string, object> |
| | | { |
| | | { "eps", 0.25 }, { "min_samples", 15 } |
| | | }); |
| | | |
| | | |
| | | var json = JsonHelper.Object2FormatJson(areas); |
| | | |
| | | DynamicPolyDegree = areas.Count > 1 ? 2 : 1; |
| | | |
| | | // ä¼å8ï¼é¢åé
éå容é |
| | | var measured_x = new List<double>(measuredXValid.Length); |
| | | var measured_y = new List<double>(measuredYValid.Length); |
| | | |
| | | foreach (var item in areas) |
| | | { |
| | | // ä¼å9ï¼åéåèå´å¤æ |
| | | for (int i = 0; i < measuredXValid.Length; i++) |
| | | { |
| | | if (measuredXValid[i].Between(item.XRange.Min, item.XRange.Max) && |
| | | measuredYValid[i].Between(item.YRange.Min, item.YRange.Max)) |
| | | { |
| | | measured_x.Add(measuredXValid[i]); |
| | | measured_y.Add(measuredYValid[i]); |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | // ä¼å10ï¼åå¹¶æ²çº¿çæä¼å |
| | | var (mergedX, mergedY) = MergeCurvesOptimized(splineX, splineY, |
| | | measured_x.ToArray(), measured_y.ToArray(), DynamicPolyDegree); |
| | | |
| | | // ä¼å11ï¼ç©éµè¿ç®ä¼å |
| | | double xMean = mergedX.Mean(); |
| | | double xStd = mergedX.StandardDeviation(); |
| | | var xNorm = mergedX.Select(x => (x - xMean) / xStd).ToArray(); |
| | | |
| | | var X = Matrix<double>.Build.DenseOfRowArrays( |
| | | xNorm.Select(x => new[] { Math.Pow(x, 3), x * x, x, 1.0 }).ToArray()); |
| | | |
| | | // ä¼å12ï¼QRåè§£å é |
| | | var initialParams = MultipleRegression.QR(X, Vector<double>.Build.DenseOfArray(mergedY)); |
| | | |
| | | // ä¼å13ï¼çº¦æçæä¼å |
| | | var constraints = GenerateConstraints(xNorm, 50); |
| | | var optimizer = new Cobyla(BuildObjectiveFunction(xNorm, mergedY), constraints.ToArray()) |
| | | { MaxIterations = OptimizationMaxIterations }; |
| | | |
| | | if (!optimizer.Minimize(initialParams.ToArray())) |
| | | throw new OptimizationException("Optimization failed"); |
| | | |
| | | // ä¼å14ï¼éæ ·ç¹çæä¼å |
| | | double[] optimizedX = GenerateOptimizedX(splineX, minX, maxX); |
| | | double[] optimizedY = optimizedX.Select(x => CubicModel(optimizer.Solution, (x - xMean) / xStd)).ToArray(); |
| | | |
| | | return (mergedX, mergedY, optimizedX, optimizedY); |
| | | } |
| | | |
| | | |
| | | #endregion |
| | | |
| | | #region ä¼åå·¥å
·æ¹æ³ |
| | | private static double[] GenerateOptimizedX(double[] splineX, double minX, double maxX) |
| | | { |
| | | double lower = Math.Min(splineX[0], minX); |
| | | double upper = Math.Max(splineX[^1], maxX); |
| | | return Enumerable.Range(0, 300) |
| | | .Select(i => lower + i * (upper - lower) / 299.0) |
| | | .ToArray(); |
| | | } |
| | | |
| | | private List<NonlinearConstraint> GenerateConstraints(double[] xNorm, int sampleCount) |
| | | { |
| | | var constraints = new List<NonlinearConstraint>(sampleCount); |
| | | double min = xNorm[0], max = xNorm[^1]; |
| | | double step = (max - min) / (sampleCount - 1); |
| | | |
| | | for (int i = 0; i < sampleCount; i++) |
| | | { |
| | | double x = min + i * step; |
| | | constraints.Add(new NonlinearConstraint(4, |
| | | p => -Derivative(p, x), |
| | | ConstraintType.GreaterThanOrEqualTo, 0)); |
| | | } |
| | | return constraints; |
| | | } |
| | | |
| | | private static (double[] X, double[] Y) MergeCurvesOptimized( |
| | | double[] originalX, double[] originalY, |
| | | double[] measuredX, double[] measuredY, int degree) |
| | | { |
| | | if (measuredX.Length < 3) |
| | | return (originalX, originalY); |
| | | |
| | | // ä¼å15ï¼å¤é¡¹å¼æåå é |
| | | var polyCoeff = Fit.Polynomial(measuredX, measuredY, degree).Reverse().ToArray(); |
| | | |
| | | // ä¼å16ï¼å
åå¤ç¨ |
| | | var correctedY = new double[originalY.Length]; |
| | | Array.Copy(originalY, correctedY, originalY.Length); |
| | | |
| | | double measuredMin = measuredX[0], measuredMax = measuredX[^1]; |
| | | |
| | | // ä¼å17ï¼å¹¶è¡åºåå¤ç |
| | | Parallel.Invoke( |
| | | () => ProcessCoreRegion(originalX, correctedY, polyCoeff, measuredMin, measuredMax), |
| | | () => ProcessTransitionRegions(originalX, correctedY, originalY, polyCoeff, measuredMin, measuredMax) |
| | | ); |
| | | |
| | | return (originalX, correctedY); |
| | | } |
| | | #endregion |
| | | |
| | | #region å
¶ä»ä¿æä¸åçæ ¸å¿æ¹æ³ |
| | | |
| | | /// <summary> |
| | | /// 䏿¬¡å¤é¡¹å¼æ¨¡åï¼é广åï¼ |
| | | /// </summary> |
| | | private static double CubicModel(double[] param, double x) => |
| | | param[0] * x * x * x + param[1] * x * x + param[2] * x + param[3]; |
| | | |
| | | /// <summary> |
| | | /// å¤é¡¹å¼å¯¼æ°ï¼ç¨äºåè°æ§çº¦æï¼ |
| | | /// </summary> |
| | | private static double Derivative(double[] param, double x) => |
| | | 3 * param[0] * x * x + 2 * param[1] * x + param[2]; |
| | | |
| | | |
| | | /// <summary> |
| | | /// æå»ºç®æ 彿°ï¼åæ¹è¯¯å·®ï¼ |
| | | /// </summary> |
| | | private NonlinearObjectiveFunction BuildObjectiveFunction(double[] xNorm, double[] y) => |
| | | new NonlinearObjectiveFunction(4, p => |
| | | xNorm.Select((x, i) => Math.Pow(CubicModel(p, x) - y[i], 2)).Average() |
| | | ); |
| | | |
| | | // å¤é¡¹å¼æ±å¼ï¼æ¯æéåºç³»æ°ï¼ |
| | | private static double Polyval(double[] coefficients, double x) |
| | | { |
| | | double result = 0; |
| | | int degree = coefficients.Length - 1; |
| | | for (int i = 0; i < coefficients.Length; i++) |
| | | result += coefficients[i] * Math.Pow(x, degree - i); |
| | | return result; |
| | | } |
| | | |
| | | // çº¿æ§æå¼ |
| | | private static double LinearInterp(double x, double x0, double y0, double x1, double y1) |
| | | { |
| | | return x1 != x0 |
| | | ? y0 + (y1 - y0) * (x - x0) / (x1 - x0) |
| | | : y0; |
| | | } |
| | | #endregion |
| | | |
| | | #region æ°å¢ä¼åè¾
婿¹æ³ |
| | | private static void ProcessCoreRegion(double[] x, double[] y, double[] coeff, double min, double max) |
| | | { |
| | | for (int i = 0; i < x.Length; i++) |
| | | { |
| | | if (x[i].Between(min, max)) |
| | | y[i] = Polyval(coeff, x[i]); |
| | | } |
| | | } |
| | | |
| | | private static void ProcessTransitionRegions(double[] x, double[] y, double[] originalY, |
| | | double[] coeff, double measuredMin, double measuredMax) |
| | | { |
| | | // å·¦è¿æ¸¡å¤ç |
| | | double xLeftStart = Math.Max(measuredMin - TransitionWidth, x[0]); |
| | | double yLeftStart = Polyval(Fit.Polynomial(x, originalY, 3).Reverse().ToArray(), xLeftStart); |
| | | double yLeftEnd = Polyval(coeff, measuredMin); |
| | | |
| | | // å³è¿æ¸¡å¤ç |
| | | double xRightEnd = Math.Min(measuredMax + TransitionWidth, x[^1]); |
| | | double yRightStart = Polyval(coeff, measuredMax); |
| | | double yRightEnd = Polyval(Fit.Polynomial(x, originalY, 3).Reverse().ToArray(), xRightEnd); |
| | | |
| | | for (int i = 0; i < x.Length; i++) |
| | | { |
| | | double xi = x[i]; |
| | | if (xi.Between(xLeftStart, measuredMin)) |
| | | y[i] = LinearInterp(xi, xLeftStart, yLeftStart, measuredMin, yLeftEnd); |
| | | else if (xi.Between(measuredMax, xRightEnd)) |
| | | y[i] = LinearInterp(xi, measuredMax, yRightStart, xRightEnd, yRightEnd); |
| | | } |
| | | } |
| | | #endregion |
| | | } |
| | | |
| | | #region æ©å±æ¹æ³ |
| | | public static class ArrayExtensions |
| | | { |
| | | public static double Mean(this double[] array) => ArrayStatistics.Mean(array); |
| | | public static double StandardDeviation(this double[] array) => Math.Sqrt(ArrayStatistics.Variance(array)); |
| | | |
| | | public static bool Between(this double value, double min, double max) => |
| | | value >= min && value <= max; |
| | | } |
| | | #endregion |
| | | |
| | | #region æ°æ®ç¹å¯åº¦åç±»å¨ |
| | | |
| | | /* |
| | | 主è¦ä¼åç¹è¯´æï¼ç½é¡µå¼ç¨ï¼ï¼ |
| | | |
| | | ââKD-Tree空é´ç´¢å¼ââï¼éè¿æå»ºKD-Treeå°é»åæ¥è¯¢å¤æåº¦ä»O(n²)éè³O(n log n) |
| | | ââå¹¶è¡è®¡ç®ââï¼å¨æ°æ®æ ååååºåæåé¶æ®µä½¿ç¨Parallel.Foræå夿 ¸å©ç¨ç |
| | | ââéåæ©å±ç°ââï¼ç¨éå代æ¿éå½å®ç°å¹¿åº¦ä¼å
æç´¢ï¼é¿å
æ æº¢åºé®é¢ |
| | | ââå使°åç®±ä¼åââï¼å¨ç½æ ¼ååæ¶éç¨å使°åç®±çç¥æåå¯åº¦ä¼°è®¡åç¡®æ§ |
| | | ââå
åä¼åââï¼éç¨ç¼å²åºãé¿å
é夿°ç»å建ï¼åå°GCåå |
| | | 该å®ç°ç¸æ¯åå§çæ¬å¨å¤§è§æ¨¡æ°æ®éï¼10,000+ç¹ï¼ä¸å¯è·å¾10-50åçæ§è½æåï¼åæ¶ä¿æç®æ³åç¡®æ§ãæµè¯æ¶å»ºè®®ç»ååæ°è°ä¼å·¥å
·éæ©æä½³epsåmin_samplesåæ°ã |
| | | |
| | | */ |
| | | |
| | | //public class OptimizedDBSCAN |
| | | //{ |
| | | // private readonly double _epsSquared; |
| | | // private readonly int _minPts; |
| | | // private KDTree _kdTree; |
| | | |
| | | // public OptimizedDBSCAN(double eps, int minPts) |
| | | // { |
| | | // _epsSquared = eps * eps; |
| | | // _minPts = minPts; |
| | | // } |
| | | |
| | | // public int[] Run(double[,] points) |
| | | // { |
| | | |
| | | // var json = JsonHelper.Object2FormatJson(points); |
| | | |
| | | // int n = points.GetLength(0); |
| | | // int[] labels = new int[n]; |
| | | // Array.Fill(labels, -2); // -2=æªå¤ç, -1=åªå£° |
| | | |
| | | // _kdTree = new KDTree(points); |
| | | |
| | | // int clusterId = 0; |
| | | // for (int i = 0; i < n; i++) |
| | | // { |
| | | // if (labels[i] != -2) continue; |
| | | |
| | | // var neighbors = _kdTree.RangeQuery(i, _epsSquared); |
| | | |
| | | // // æ ¸å¿ç¹éªè¯ï¼ç½é¡µ7çæ ¸å¿å®ä¹ï¼ |
| | | // if (neighbors.Count < _minPts) |
| | | // { |
| | | // labels[i] = -1; |
| | | // continue; |
| | | // } |
| | | |
| | | // ExpandClusterOptimized(i, neighbors, clusterId++, labels); |
| | | // } |
| | | // return labels; |
| | | // } |
| | | |
| | | // // ä¿®å¤3ï¼å¢å¼ºåèç±»æ©å± |
| | | // private void ExpandClusterOptimized(int pointIdx, List<int> neighbors, |
| | | // int clusterId, int[] labels) |
| | | // { |
| | | // var corePoints = new HashSet<int>(); |
| | | // var queue = new Queue<int>(); |
| | | |
| | | // // 馿¬¡æ ¸å¿ç¹éªè¯ |
| | | // if (neighbors.Count >= _minPts) corePoints.Add(pointIdx); |
| | | |
| | | // foreach (var n in neighbors) |
| | | // { |
| | | // if (labels[n] == -1) labels[n] = clusterId; // åªå£°è½¬è¾¹ç |
| | | // if (labels[n] == -2) queue.Enqueue(n); |
| | | // } |
| | | |
| | | // while (queue.Count > 0) |
| | | // { |
| | | // int current = queue.Dequeue(); |
| | | // if (labels[current] != -2) continue; |
| | | |
| | | // var currentNeighbors = _kdTree.RangeQuery(current, _epsSquared); |
| | | // labels[current] = clusterId; |
| | | |
| | | // // äºæ¬¡æ ¸å¿ç¹éªè¯ |
| | | // if (currentNeighbors.Count >= _minPts) |
| | | // { |
| | | // corePoints.Add(current); |
| | | // foreach (var n in currentNeighbors) |
| | | // { |
| | | // if (labels[n] == -1) labels[n] = clusterId; |
| | | // if (labels[n] == -2 && !corePoints.Contains(n)) |
| | | // { |
| | | // queue.Enqueue(n); |
| | | // } |
| | | // } |
| | | // } |
| | | // } |
| | | // } |
| | | |
| | | // // ä¿®å¤åçKD-Treeå®ç° |
| | | // private class KDTree |
| | | // { |
| | | // private readonly double[,] _points; |
| | | // private readonly int[] _indices; |
| | | // private readonly int _dimensions; |
| | | |
| | | // public KDTree(double[,] points) |
| | | // { |
| | | // _points = points; |
| | | // _indices = Enumerable.Range(0, points.GetLength(0)).ToArray(); |
| | | // _dimensions = points.GetLength(1); |
| | | // BuildTree(0, _indices.Length - 1, 0); |
| | | // } |
| | | |
| | | // private void BuildTree(int left, int right, int depth) |
| | | // { |
| | | // if (left >= right) return; |
| | | |
| | | // int axis = depth % _dimensions; |
| | | // int median = (left + right) / 2; |
| | | |
| | | // // ä¼åå¿«ééæ©ç®æ³ï¼ç½é¡µ7çå®ç°ï¼ |
| | | // SelectMedian(left, right, median, axis); |
| | | // BuildTree(left, median - 1, depth + 1); |
| | | // BuildTree(median + 1, right, depth + 1); |
| | | // } |
| | | |
| | | // private void SelectMedian(int left, int right, int k, int axis) |
| | | // { |
| | | // while (left < right) |
| | | // { |
| | | // int pivot = Partition(left, right, axis); |
| | | // if (pivot == k) return; |
| | | // if (pivot < k) left = pivot + 1; |
| | | // else right = pivot - 1; |
| | | // } |
| | | // } |
| | | |
| | | // private int Partition(int left, int right, int axis) |
| | | // { |
| | | // double pivotValue = _points[_indices[right], axis]; |
| | | // int storeIndex = left; |
| | | // for (int i = left; i < right; i++) |
| | | // { |
| | | // if (_points[_indices[i], axis] < pivotValue) |
| | | // { |
| | | // Swap(_indices, storeIndex, i); |
| | | // storeIndex++; |
| | | // } |
| | | // } |
| | | // Swap(_indices, storeIndex, right); |
| | | // return storeIndex; |
| | | // } |
| | | |
| | | // // ä¿®å¤2ï¼å¢å¼ºåé»åæ¥è¯¢ |
| | | // public List<int> RangeQuery(int pointIdx, double radiusSquared) |
| | | // { |
| | | // var queryPoint = GetPoint(pointIdx); |
| | | // var result = new List<int>(); |
| | | // RangeQueryRecursive(queryPoint, 0, 0, radiusSquared, result); |
| | | // return result; |
| | | // } |
| | | |
| | | |
| | | // private void RangeQueryRecursive(double[] queryPoint, int nodeIdx, int depth, |
| | | // double radiusSquared, List<int> result) |
| | | // { |
| | | // if (nodeIdx >= _indices.Length) return; |
| | | |
| | | // int currentIdx = _indices[nodeIdx]; |
| | | // double[] currentPoint = GetPoint(currentIdx); |
| | | |
| | | // double distanceSq = CalculateDistanceSquared(queryPoint, currentPoint); |
| | | // if (distanceSq <= radiusSquared) result.Add(currentIdx); |
| | | |
| | | // int axis = depth % _dimensions; |
| | | // double axisDistance = currentPoint[axis] - queryPoint[axis]; |
| | | |
| | | // // å¨æåªæé»è¾ |
| | | // if (axisDistance <= 0 || axisDistance * axisDistance <= radiusSquared) |
| | | // { |
| | | // RangeQueryRecursive(queryPoint, 2 * nodeIdx + 1, depth + 1, radiusSquared, result); |
| | | // } |
| | | // if (axisDistance >= 0 || axisDistance * axisDistance <= radiusSquared) |
| | | // { |
| | | // RangeQueryRecursive(queryPoint, 2 * nodeIdx + 2, depth + 1, radiusSquared, result); |
| | | // } |
| | | // } |
| | | |
| | | // // ä¿®å¤1ï¼å¨æç»´åº¦è·ç¦»è®¡ç® |
| | | // private double CalculateDistanceSquared(double[] point1, double[] point2) |
| | | // { |
| | | // double distSq = 0; |
| | | // for (int i = 0; i < point1.Length; i++) |
| | | // { |
| | | // double diff = point1[i] - point2[i]; |
| | | // distSq += diff * diff; |
| | | // } |
| | | // return distSq; |
| | | // } |
| | | |
| | | // private double[] GetPoint(int index) => |
| | | // Enumerable.Range(0, _dimensions) |
| | | // .Select(i => _points[index, i]) |
| | | // .ToArray(); |
| | | |
| | | // private void Swap(int[] arr, int i, int j) => |
| | | // (arr[i], arr[j]) = (arr[j], arr[i]); |
| | | // } |
| | | //} |
| | | //public class OptimizedPointClassifier |
| | | //{ |
| | | // public class DenseArea |
| | | // { |
| | | // public (double Min, double Max) XRange { get; set; } |
| | | // public (double Min, double Max) YRange { get; set; } |
| | | // } |
| | | |
| | | // // æå°å¯åº¦éå¼ï¼ç½é¡µ7ï¼ |
| | | // public const double MinDensity = 0.05; |
| | | |
| | | // // å¯éçåç±»æ¹æ³ï¼ç½é¡µ3ãç½é¡µ7ï¼ |
| | | // public enum Method |
| | | // { |
| | | // DBSCAN, |
| | | // KDE, |
| | | // Grid |
| | | // } |
| | | |
| | | |
| | | // public (int[] Labels, List<DenseArea> DenseAreas) ClassifyPoints( |
| | | // double[] x, double[] y, Method method = Method.DBSCAN, |
| | | // Dictionary<string, object> parameters = null) |
| | | // { |
| | | // // å¹¶è¡æ ååæ°æ®ï¼ç½é¡µ3ãç½é¡µ7ï¼ |
| | | // var scaledData = OptimizedScaleData(x, y); |
| | | |
| | | // int[] labels = new int[x.Length]; |
| | | // switch (method) |
| | | // { |
| | | // case Method.DBSCAN: |
| | | // { |
| | | // // 使ç¨ä¼ååçDBSCANï¼ç½é¡µ4ãç½é¡µ6ï¼ |
| | | // var dbscan = new OptimizedDBSCAN( |
| | | // (double)(parameters?["eps"] ?? 0.25), |
| | | // (int)(parameters?["min_samples"] ?? 15)); |
| | | |
| | | // labels = dbscan.Run(scaledData); |
| | | // } |
| | | // break; |
| | | // case Method.KDE: |
| | | // { |
| | | // // æ ¸å¯åº¦ä¼°è®¡ |
| | | // int bandwidth = parameters != null && parameters.ContainsKey("bandwidth") |
| | | // ? (int)parameters["bandwidth"] |
| | | // : 1; |
| | | // double percentile = parameters != null && parameters.ContainsKey("percentile") |
| | | // ? (double)parameters["percentile"] |
| | | // : 70.0; |
| | | |
| | | // // 计ç®å¯åº¦ |
| | | // double[] density = new double[x.Length]; |
| | | // for (int i = 0; i < x.Length; i++) |
| | | // { |
| | | // double sum = 0; |
| | | // for (int j = 0; j < x.Length; j++) |
| | | // { |
| | | // double distance = Math.Sqrt( |
| | | // Math.Pow(scaledData[i, 0] - scaledData[j, 0], 2) + |
| | | // Math.Pow(scaledData[i, 1] - scaledData[j, 1], 2)); |
| | | // sum += Math.Exp(-distance * distance / (2 * bandwidth * bandwidth)); |
| | | // } |
| | | // density[i] = sum / (x.Length * 2 * Math.PI * bandwidth * bandwidth); |
| | | // } |
| | | |
| | | // // 计ç®éå¼ |
| | | // double threshold = GetPercentile(density, percentile); |
| | | |
| | | // // æ è®°å¯éåºå |
| | | // labels = density.Select(d => d >= threshold ? 1 : 0).ToArray(); |
| | | // } |
| | | // break; |
| | | // case Method.Grid: |
| | | // { |
| | | // // å¨æç½æ ¼åå |
| | | // int xBins = parameters != null && parameters.ContainsKey("x_bins") |
| | | // ? (int)parameters["x_bins"] |
| | | // : 30; |
| | | // int yBins = parameters != null && parameters.ContainsKey("y_bins") |
| | | // ? (int)parameters["y_bins"] |
| | | // : 20; |
| | | // double percentile = parameters != null && parameters.ContainsKey("percentile") |
| | | // ? (double)parameters["percentile"] |
| | | // : 75.0; |
| | | |
| | | // // åå»ºç½æ ¼ |
| | | // double[] xBinsArray = CreateBins(x, xBins); |
| | | // double[] yBinsArray = CreateBins(y, yBins); |
| | | |
| | | // // 计ç®ç½æ ¼å¯åº¦ |
| | | // int[,] gridDensity = new int[xBins, yBins]; |
| | | // for (int i = 0; i < x.Length; i++) |
| | | // { |
| | | // int xBin = Array.BinarySearch(xBinsArray, x[i]); |
| | | // int yBin = Array.BinarySearch(yBinsArray, y[i]); |
| | | // if (xBin >= 0 && xBin < xBins && yBin >= 0 && yBin < yBins) |
| | | // gridDensity[xBin, yBin]++; |
| | | // } |
| | | |
| | | // // 计ç®éå¼ |
| | | // double threshold = GetPercentile(gridDensity.Cast<double>().ToArray(), percentile); |
| | | |
| | | // // æ è®°å¯éç½æ ¼ |
| | | // for (int i = 0; i < x.Length; i++) |
| | | // { |
| | | // int xBin = Array.BinarySearch(xBinsArray, x[i]); |
| | | // int yBin = Array.BinarySearch(yBinsArray, y[i]); |
| | | // if (xBin >= 0 && xBin < xBins && yBin >= 0 && yBin < yBins) |
| | | // labels[i] = gridDensity[xBin, yBin] > threshold ? 1 : 0; |
| | | // else |
| | | // labels[i] = 0; |
| | | // } |
| | | // } |
| | | // break; |
| | | // default: |
| | | // { |
| | | // throw new ArgumentException("䏿¯æçæ£æµæ¹æ³"); |
| | | // } |
| | | // } |
| | | |
| | | // // å¹¶è¡æåå¯éåºåï¼ç½é¡µ7ï¼ |
| | | // return (labels, ExtractDenseAreasParallel(x, y, labels)); |
| | | // } |
| | | |
| | | // private double[,] To2DArray(double[] x, double[] y) |
| | | // { |
| | | // var arr = new double[x.Length, 2]; |
| | | // Parallel.For(0, x.Length, i => |
| | | // { |
| | | // arr[i, 0] = x[i]; |
| | | // arr[i, 1] = y[i]; |
| | | // }); |
| | | // return arr; |
| | | // } |
| | | |
| | | // private double GetPercentile(double[] data, double percentile) |
| | | // { |
| | | // Array.Sort(data); |
| | | // int index = (int)Math.Ceiling(percentile / 100.0 * data.Length) - 1; |
| | | // return data[index]; |
| | | // } |
| | | |
| | | // private double[] CreateBins(double[] data, int bins) |
| | | // { |
| | | // double min = data.Min(); |
| | | // double max = data.Max(); |
| | | // double step = (max - min) / bins; |
| | | // return Enumerable.Range(0, bins + 1).Select(i => min + i * step).ToArray(); |
| | | // } |
| | | |
| | | // private double[,] OptimizedScaleData(double[] x, double[] y) |
| | | // { |
| | | // int len = x.Length; |
| | | // var scaled = new double[len, 2]; |
| | | |
| | | // // å¹¶è¡è®¡ç®ç»è®¡éï¼ç½é¡µ3ï¼ |
| | | // Parallel.For(0, 2, dim => |
| | | // { |
| | | // double[] data = dim == 0 ? x : y; |
| | | // double sum = 0, sumSq = 0; |
| | | |
| | | // for (int i = 0; i < len; i++) |
| | | // { |
| | | // sum += data[i]; |
| | | // sumSq += data[i] * data[i]; |
| | | // } |
| | | |
| | | // double mean = sum / len; |
| | | // double std = Math.Sqrt(sumSq / len - mean * mean); |
| | | |
| | | // for (int i = 0; i < len; i++) |
| | | // scaled[i, dim] = (data[i] - mean) / std; |
| | | // }); |
| | | |
| | | // return scaled; |
| | | // } |
| | | |
| | | // private List<DenseArea> ExtractDenseAreasParallel(double[] x, double[] y, int[] labels) |
| | | // { |
| | | // var areas = new List<DenseArea>(); |
| | | // var uniqueLabels = labels.Distinct().AsParallel(); |
| | | |
| | | // uniqueLabels.ForAll(label => |
| | | // { |
| | | // if (label == -1) return; |
| | | |
| | | // var indices = Enumerable.Range(0, labels.Length) |
| | | // .Where(i => labels[i] == label).ToList(); |
| | | |
| | | // if (indices.Count > x.Length * MinDensity) |
| | | // { |
| | | // var xVals = indices.Select(i => x[i]); |
| | | // var yVals = indices.Select(i => y[i]); |
| | | |
| | | // lock (areas) |
| | | // { |
| | | // areas.Add(new DenseArea |
| | | // { |
| | | // XRange = (xVals.Min(), xVals.Max()), |
| | | // YRange = (yVals.Min(), yVals.Max()) |
| | | // }); |
| | | // } |
| | | // } |
| | | // }); |
| | | |
| | | // return areas.OrderBy(a => a.XRange.Min).ToList(); |
| | | // } |
| | | |
| | | // #endregion |
| | | |
| | | |
| | | |
| | | //} |
| | | |
| | | |
| | | public class OptimizedPointClassifier |
| | | { |
| | | #region æ ¸å¿æ°æ®ç»æ |
| | | public class DenseArea |
| | | { |
| | | public (double Min, double Max) XRange { get; init; } |
| | | public (double Min, double Max) YRange { get; init; } |
| | | } |
| | | |
| | | private struct PointData |
| | | { |
| | | public double X; |
| | | public double Y; |
| | | public int OriginalIndex; |
| | | } |
| | | #endregion |
| | | |
| | | public (int[] Labels, List<DenseArea> DenseAreas) ClassifyPoints( |
| | | double[] x, double[] y, |
| | | string method = "dbscan", |
| | | Dictionary<string, object> parameters = null) |
| | | { |
| | | ValidateInput(x, y); |
| | | var scaledData = ZScoreNormalization(x, y); |
| | | |
| | | int[] labels = method.ToLower() switch |
| | | { |
| | | "dbscan" => DBSCAN(scaledData, parameters), |
| | | "kde" => OptimizedKDE(scaledData, parameters), |
| | | "grid" => OptimizedGrid(x, y, parameters), |
| | | _ => throw new ArgumentException("䏿¯æçæ¹æ³") |
| | | }; |
| | | |
| | | return (labels, ExtractDenseAreas(x, y, labels)); |
| | | } |
| | | |
| | | #region é¢å¤ç |
| | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | | private void ValidateInput(double[] x, double[] y) |
| | | { |
| | | if (x.Length != y.Length) |
| | | throw new ArgumentException("xåyé¿åº¦ä¸ä¸è´"); |
| | | } |
| | | |
| | | private double[,] ZScoreNormalization(double[] x, double[] y) |
| | | { |
| | | Span<double> xSpan = x.AsSpan(); |
| | | Span<double> ySpan = y.AsSpan(); |
| | | |
| | | var (xMean, xStd) = CalculateStats(xSpan); |
| | | var (yMean, yStd) = CalculateStats(ySpan); |
| | | |
| | | double[,] scaled = new double[x.Length, 2]; |
| | | Parallel.For(0, x.Length, i => |
| | | { |
| | | scaled[i, 0] = (x[i] - xMean) / xStd; |
| | | scaled[i, 1] = (y[i] - yMean) / yStd; |
| | | }); |
| | | return scaled; |
| | | } |
| | | |
| | | private (double Mean, double Std) CalculateStats(Span<double> data) |
| | | { |
| | | double sum = 0, sumSq = 0; |
| | | foreach (var d in data) |
| | | { |
| | | sum += d; |
| | | sumSq += d * d; |
| | | } |
| | | double mean = sum / data.Length; |
| | | double std = Math.Sqrt(sumSq / data.Length - mean * mean); |
| | | return (mean, std); |
| | | } |
| | | #endregion |
| | | |
| | | #region DBSCANä¼åå®ç° |
| | | private int[] DBSCAN(double[,] scaledData, Dictionary<string, object> parameters) |
| | | { |
| | | double eps = parameters.GetValueOrDefault("eps", 0.3); |
| | | int minSamples = parameters.GetValueOrDefault("min_samples", (int)(scaledData.GetLength(0) * 0.02)); |
| | | |
| | | var spatialIndex = new SpatialIndex(scaledData, eps); |
| | | var dbscan = new OptimizedDBSCAN(eps, minSamples, spatialIndex); |
| | | return dbscan.Run(scaledData); |
| | | } |
| | | |
| | | private class SpatialIndex |
| | | { |
| | | private readonly Dictionary<(int, int), List<int>> _grid = new(); |
| | | private readonly double _cellSize; |
| | | private readonly double _minX, _minY; |
| | | |
| | | public SpatialIndex(double[,] points, double eps) |
| | | { |
| | | _cellSize = eps; |
| | | _minX = Enumerable.Range(0, points.GetLength(0)).Min(i => points[i, 0]); |
| | | _minY = Enumerable.Range(0, points.GetLength(0)).Min(i => points[i, 1]); |
| | | |
| | | for (int i = 0; i < points.GetLength(0); i++) |
| | | { |
| | | int xCell = (int)((points[i, 0] - _minX) / _cellSize); |
| | | int yCell = (int)((points[i, 1] - _minY) / _cellSize); |
| | | var key = (xCell, yCell); |
| | | |
| | | if (!_grid.ContainsKey(key)) _grid[key] = new List<int>(); |
| | | _grid[key].Add(i); |
| | | } |
| | | } |
| | | |
| | | public List<int> GetNeighborCandidates(int pointIndex, double[,] points) |
| | | { |
| | | var candidates = new List<int>(); |
| | | double x = points[pointIndex, 0]; |
| | | double y = points[pointIndex, 1]; |
| | | |
| | | int xCell = (int)((x - _minX) / _cellSize); |
| | | int yCell = (int)((y - _minY) / _cellSize); |
| | | |
| | | for (int dx = -1; dx <= 1; dx++) |
| | | { |
| | | for (int dy = -1; dy <= 1; dy++) |
| | | { |
| | | var key = (xCell + dx, yCell + dy); |
| | | if (_grid.TryGetValue(key, out var cellPoints)) |
| | | candidates.AddRange(cellPoints); |
| | | } |
| | | } |
| | | return candidates.Distinct().ToList(); |
| | | } |
| | | } |
| | | |
| | | private class OptimizedDBSCAN |
| | | { |
| | | private readonly double _eps; |
| | | private readonly int _minPts; |
| | | private readonly SpatialIndex _index; |
| | | |
| | | public OptimizedDBSCAN(double eps, int minPts, SpatialIndex index) |
| | | { |
| | | _eps = eps; |
| | | _minPts = minPts; |
| | | _index = index; |
| | | } |
| | | |
| | | public int[] Run(double[,] points) |
| | | { |
| | | int n = points.GetLength(0); |
| | | int[] labels = Enumerable.Repeat(-2, n).ToArray(); |
| | | int clusterId = -1; |
| | | |
| | | Parallel.For(0, n, i => |
| | | { |
| | | if (labels[i] != -2) return; |
| | | |
| | | var neighbors = GetNeighbors(points, i); |
| | | if (neighbors.Count < _minPts) |
| | | { |
| | | Interlocked.Exchange(ref labels[i], -1); |
| | | return; |
| | | } |
| | | |
| | | int newClusterId = Interlocked.Increment(ref clusterId); |
| | | ExpandCluster(points, i, neighbors, newClusterId, labels); |
| | | }); |
| | | |
| | | return labels; |
| | | } |
| | | |
| | | private void ExpandCluster(double[,] points, int seedIndex, List<int> neighbors, int clusterId, int[] labels) |
| | | { |
| | | var queue = new ConcurrentQueue<int>(neighbors); |
| | | while (queue.TryDequeue(out int current)) |
| | | { |
| | | if (Interlocked.CompareExchange(ref labels[current], clusterId, -2) != -2) continue; |
| | | |
| | | var newNeighbors = GetNeighbors(points, current); |
| | | if (newNeighbors.Count >= _minPts) |
| | | newNeighbors.ForEach(n => queue.Enqueue(n)); |
| | | } |
| | | } |
| | | |
| | | private List<int> GetNeighbors(double[,] points, int pointIndex) |
| | | { |
| | | var candidates = _index.GetNeighborCandidates(pointIndex, points); |
| | | var neighbors = new List<int>(); |
| | | |
| | | Parallel.ForEach(candidates, j => |
| | | { |
| | | double dx = points[pointIndex, 0] - points[j, 0]; |
| | | double dy = points[pointIndex, 1] - points[j, 1]; |
| | | if (dx * dx + dy * dy <= _eps * _eps) |
| | | lock (neighbors) { neighbors.Add(j); } |
| | | }); |
| | | |
| | | return neighbors; |
| | | } |
| | | } |
| | | #endregion |
| | | |
| | | #region å
¶ä»æ¹æ³å®ç°ï¼KDE/Gridï¼åè¾
婿¹æ³ |
| | | // 代ç ç»æç±»ä¼¼ï¼å
å«ï¼ |
| | | // 1. åç®±é¢å¤ç |
| | | // 2. å¹¶è¡è®¡ç®å¯åº¦/ç½æ ¼ç»è®¡ |
| | | // 3. éå¼è®¡ç® |
| | | |
| | | #region KDEä¼åå®ç° |
| | | private int[] OptimizedKDE(double[,] scaledData, Dictionary<string, object> parameters) |
| | | { |
| | | int bandwidth = parameters.GetValueOrDefault("bandwidth", 1); |
| | | double percentile = parameters.GetValueOrDefault("percentile", 70.0); |
| | | |
| | | // åç®±å éè®¡ç® |
| | | var (bins, binDensities) = CreateBinnedDensities(scaledData, bandwidth); |
| | | |
| | | // å¹¶è¡æå¼è®¡ç®å¯åº¦ |
| | | double[] density = new double[scaledData.GetLength(0)]; |
| | | Parallel.For(0, scaledData.GetLength(0), i => |
| | | { |
| | | density[i] = InterpolateDensity(scaledData, i, bins, binDensities); |
| | | }); |
| | | |
| | | // 计ç®å¨æéå¼ |
| | | double threshold = CalculateDynamicThreshold(density, percentile); |
| | | |
| | | return density.Select(d => d >= threshold ? 1 : 0).ToArray(); |
| | | } |
| | | |
| | | private (double[,], double[]) CreateBinnedDensities(double[,] data, int bandwidth) |
| | | { |
| | | const int gridSize = 50; // åç®±ç²åº¦ |
| | | int n = data.GetLength(0); |
| | | |
| | | // 计ç®å箱边ç |
| | | var (xMin, xMax, yMin, yMax) = GetDataRange(data); |
| | | double xStep = (xMax - xMin) / gridSize; |
| | | double yStep = (yMax - yMin) / gridSize; |
| | | |
| | | // åå§ååç®±ç©éµ |
| | | double[,] bins = new double[gridSize + 1, gridSize + 1]; |
| | | Parallel.For(0, gridSize + 1, i => |
| | | { |
| | | for (int j = 0; j <= gridSize; j++) |
| | | { |
| | | double x = xMin + i * xStep; |
| | | double y = yMin + j * yStep; |
| | | bins[i, j] = GaussianKernel(x, y, data, bandwidth); |
| | | } |
| | | }); |
| | | |
| | | // é¢è®¡ç®åç®±å¯åº¦ |
| | | double[] flatDensities = new double[(gridSize + 1) * (gridSize + 1)]; |
| | | Buffer.BlockCopy(bins, 0, flatDensities, 0, sizeof(double) * bins.Length); |
| | | |
| | | return (bins, flatDensities); |
| | | } |
| | | |
| | | private double GaussianKernel(double x, double y, double[,] data, int bandwidth) |
| | | { |
| | | double sum = 0; |
| | | double factor = 1.0 / (2 * Math.PI * bandwidth * bandwidth); |
| | | |
| | | for (int i = 0; i < data.GetLength(0); i++) |
| | | { |
| | | double dx = x - data[i, 0]; |
| | | double dy = y - data[i, 1]; |
| | | double distanceSq = dx * dx + dy * dy; |
| | | sum += Math.Exp(-distanceSq / (2 * bandwidth * bandwidth)); |
| | | } |
| | | |
| | | return sum * factor / data.GetLength(0); |
| | | } |
| | | |
| | | private double InterpolateDensity(double[,] data, int index, double[,] bins, double[] binDensities) |
| | | { |
| | | const int gridSize = 50; |
| | | var (xMin, xMax, yMin, yMax) = GetDataRange(data); |
| | | |
| | | // 计ç®å½ä¸ååæ |
| | | double x = (data[index, 0] - xMin) / (xMax - xMin) * gridSize; |
| | | double y = (data[index, 1] - yMin) / (yMax - yMin) * gridSize; |
| | | |
| | | // åçº¿æ§æå¼ |
| | | int x0 = (int)Math.Floor(x); |
| | | int y0 = (int)Math.Floor(y); |
| | | double dx = x - x0; |
| | | double dy = y - y0; |
| | | |
| | | return bins[x0, y0] * (1 - dx) * (1 - dy) + |
| | | bins[x0 + 1, y0] * dx * (1 - dy) + |
| | | bins[x0, y0 + 1] * (1 - dx) * dy + |
| | | bins[x0 + 1, y0 + 1] * dx * dy; |
| | | } |
| | | #endregion |
| | | |
| | | #region Gridä¼åå®ç° |
| | | private int[] OptimizedGrid(double[] x, double[] y, Dictionary<string, object> parameters) |
| | | { |
| | | int xBins = parameters.GetValueOrDefault("x_bins", 30); |
| | | int yBins = parameters.GetValueOrDefault("y_bins", 20); |
| | | double percentile = parameters.GetValueOrDefault("percentile", 75.0); |
| | | |
| | | // 卿åç®±çç¥ |
| | | var xEdges = AdaptiveBinning(x, xBins); |
| | | var yEdges = AdaptiveBinning(y, yBins); |
| | | |
| | | // 使ç¨åå
¸åå¨ç½æ ¼è®¡æ° |
| | | var gridCounts = new ConcurrentDictionary<(int, int), int>(); |
| | | |
| | | // å¹¶è¡ç»è®¡ç½æ ¼è®¡æ° |
| | | Parallel.For(0, x.Length, i => |
| | | { |
| | | int xBin = FindBinIndex(x[i], xEdges); |
| | | int yBin = FindBinIndex(y[i], yEdges); |
| | | if (xBin >= 0 && yBin >= 0) |
| | | gridCounts.AddOrUpdate((xBin, yBin), 1, (k, v) => v + 1); |
| | | }); |
| | | |
| | | // æåå¯åº¦ç©éµ |
| | | double[] densityMatrix = gridCounts.Values.Select(v => (double)v).ToArray(); |
| | | double threshold = GetPercentile(densityMatrix, percentile); |
| | | |
| | | // çææ ç¾ |
| | | int[] labels = new int[x.Length]; |
| | | Parallel.For(0, x.Length, i => |
| | | { |
| | | int xBin = FindBinIndex(x[i], xEdges); |
| | | int yBin = FindBinIndex(y[i], yEdges); |
| | | labels[i] = (xBin >= 0 && yBin >= 0 && gridCounts.TryGetValue((xBin, yBin), out int count)) |
| | | ? (count > threshold ? 1 : 0) |
| | | : 0; |
| | | }); |
| | | |
| | | return labels; |
| | | } |
| | | |
| | | private double[] AdaptiveBinning(double[] data, int targetBins) |
| | | { |
| | | // åºäºæ°æ®åå¸ç卿åç®± |
| | | Array.Sort(data); |
| | | double[] quantiles = new double[targetBins + 1]; |
| | | for (int i = 0; i <= targetBins; i++) |
| | | quantiles[i] = data[(int)((double)i / targetBins * (data.Length - 1))]; |
| | | return quantiles.Distinct(); |
| | | } |
| | | |
| | | private int FindBinIndex(double value, double[] edges) |
| | | { |
| | | int index = Array.BinarySearch(edges, value); |
| | | return index < 0 ? ~index - 1 : Math.Min(index, edges.Length - 2); |
| | | } |
| | | |
| | | private double GetPercentile(double[] data, double percentile) |
| | | { |
| | | Array.Sort(data); |
| | | int index = (int)Math.Ceiling(percentile / 100.0 * data.Length) - 1; |
| | | return data[index]; |
| | | } |
| | | #endregion |
| | | |
| | | #region è¾
婿¹æ³ |
| | | private (double xMin, double xMax, double yMin, double yMax) GetDataRange(double[,] data) |
| | | { |
| | | double xMin = double.MaxValue, xMax = double.MinValue; |
| | | double yMin = double.MaxValue, yMax = double.MinValue; |
| | | |
| | | for (int i = 0; i < data.GetLength(0); i++) |
| | | { |
| | | xMin = Math.Min(xMin, data[i, 0]); |
| | | xMax = Math.Max(xMax, data[i, 0]); |
| | | yMin = Math.Min(yMin, data[i, 1]); |
| | | yMax = Math.Max(yMax, data[i, 1]); |
| | | } |
| | | return (xMin, xMax, yMin, yMax); |
| | | } |
| | | |
| | | private double CalculateDynamicThreshold(double[] densities, double percentile) |
| | | { |
| | | // åºäºæ»å¨çªå£ç卿éå¼ |
| | | const int windowSize = 100; |
| | | double[] sorted = densities.OrderBy(d => d).ToArray(); |
| | | int index = (int)Math.Ceiling(sorted.Length * percentile / 100) - 1; |
| | | return index >= 0 ? sorted[index] : sorted.Last(); |
| | | } |
| | | #endregion |
| | | |
| | | |
| | | private List<DenseArea> ExtractDenseAreas(double[] x, double[] y, int[] labels) |
| | | { |
| | | // 使ç¨Spanä¼åå
åè®¿é® |
| | | var clusters = labels.Distinct().Where(l => l != -1) |
| | | .Select(clusterId => new |
| | | { |
| | | Points = labels.Select((l, i) => (l, i)) |
| | | .Where(t => t.l == clusterId) |
| | | .Select(t => (x[t.i], y[t.i])) |
| | | }) |
| | | .Where(c => c.Points.Count() > x.Length * 0.05); |
| | | |
| | | return clusters.Select(c => new DenseArea |
| | | { |
| | | XRange = (c.Points.Min(p => p.Item1), c.Points.Max(p => p.Item1)), |
| | | YRange = (c.Points.Min(p => p.Item2), c.Points.Max(p => p.Item2)) |
| | | }).ToList(); |
| | | } |
| | | #endregion |
| | | } |
| | | |
| | | public static class ParameterExtensions |
| | | { |
| | | public static T GetValueOrDefault<T>(this Dictionary<string, object> dict, string key, T defaultValue) |
| | | { |
| | | return (dict != null && dict.TryGetValue(key, out object value)) ? |
| | | (T)Convert.ChangeType(value, typeof(T)) : defaultValue; |
| | | } |
| | | } |
| | | |
| | | |
| | | #endregion |
| | | } |
¶Ô±ÈÐÂÎļþ |
| | |
| | | using Accord.Math; |
| | | using Accord.Math.Optimization; |
| | | using DevExpress.Map.Native; |
| | | using MathNet.Numerics; |
| | | using MathNet.Numerics.LinearAlgebra; |
| | | using MathNet.Numerics.LinearRegression; |
| | | using MathNet.Numerics.Optimization; |
| | | using MathNet.Numerics.Statistics; |
| | | using System.Collections.Concurrent; |
| | | using System.Data; |
| | | using System.Runtime.CompilerServices; |
| | | |
| | | namespace IStation.Test |
| | | { |
| | | /// <summary> |
| | | /// æ³µæ²çº¿æ°æ®èåæ ¡æ£å¨ï¼æ§è½ä¼åçï¼ |
| | | /// </summary> |
| | | public class PumpCurveDataFusionCorrectorHelper2DeepSeek |
| | | { |
| | | #region é
ç½®åæ° |
| | | // å¼å¸¸å¼æ£æµéå¼ï¼åºäºæ 忣æåå¸ï¼2.5对åº98.76%置信åºé´ï¼ |
| | | public const double ZScoreThreshold = 2; |
| | | // è¿æ¸¡åºå宽度ï¼åä½ä¸è¾å
¥æ°æ®ç¸åï¼æ§å¶æ²çº¿è¡æ¥å¹³æ»åº¦ï¼ |
| | | public const double TransitionWidth = 500; |
| | | // å¤é¡¹å¼æåé¶æ°ï¼ä¸æ¬¡å¹³è¡¡æåè½åä¸è¿æåé£é©ï¼ |
| | | public const int PolynomialDegree = 3; |
| | | // ä¼å卿大è¿ä»£æ¬¡æ°ï¼Cobylaç®æ³æ¶æéå¼ï¼ |
| | | private const int OptimizationMaxIterations = 1000; |
| | | |
| | | // 卿å¤é¡¹å¼æåé¶æ°ï¼ç¨äºå¨æè°æ´ï¼ |
| | | public int DynamicPolyDegree = 2; |
| | | |
| | | // æµéæ°æ®è¿æ»¤æ¯ä¾%ï¼æ§å¶å¼å¸¸å¼è¿æ»¤å¼ºåº¦ï¼ |
| | | public double MeasuredXFilterRatio = 0.15; |
| | | |
| | | # endregion |
| | | |
| | | #region æ ¸å¿ç®æ³ |
| | | public (double[] mergedX, double[] mergedY, double[] optimizedX, double[] optimizedY) Corrent( |
| | | double[] splineX, double[] splineY, |
| | | double[] measuredXAll, double[] measuredYAll) |
| | | { |
| | | if (measuredXAll.Length != measuredYAll.Length) |
| | | return default; |
| | | |
| | | // ä¼å1ï¼ä½¿ç¨Array.BinarySearchæ¿ä»£FindIndex |
| | | double threshold = measuredXAll[0] + MeasuredXFilterRatio * (measuredXAll[^1] - measuredXAll[0]); |
| | | int startIndex = Array.BinarySearch(measuredXAll, threshold); |
| | | startIndex = (startIndex < 0) ? ~startIndex : startIndex; |
| | | |
| | | // ä¼å2ï¼æ°ç»åç代æ¿Skip |
| | | var measuredXAllFiltered = new ArraySegment<double>(measuredXAll, startIndex, measuredXAll.Length - startIndex).ToArray(); |
| | | var measuredYAllFiltered = new ArraySegment<double>(measuredYAll, startIndex, measuredYAll.Length - startIndex).ToArray(); |
| | | |
| | | if (measuredXAllFiltered.Length < 5) |
| | | { |
| | | Console.WriteLine("å¼å¸¸å¼è¿æ»¤åæ°æ®è¿å°"); |
| | | return default; |
| | | } |
| | | |
| | | #region 稳å¥åå½ä¼å |
| | | // ä¼å3ï¼é¢åé
æ°ç» |
| | | var residuals = new double[measuredXAllFiltered.Length]; |
| | | (double intercept, double slope) = SimpleRegression.Fit(measuredXAllFiltered, measuredYAllFiltered); |
| | | |
| | | // ä¼å4ï¼å并计ç®å¾ªç¯ |
| | | double meanResidual = 0, stdResidual = 0; |
| | | for (int i = 0; i < measuredXAllFiltered.Length; i++) |
| | | { |
| | | double predicted = slope * measuredXAllFiltered[i] + intercept; |
| | | residuals[i] = measuredYAllFiltered[i] - predicted; |
| | | meanResidual += residuals[i]; |
| | | } |
| | | meanResidual /= residuals.Length; |
| | | for (int i = 0; i < residuals.Length; i++) |
| | | { |
| | | stdResidual += Math.Pow(residuals[i] - meanResidual, 2); |
| | | } |
| | | stdResidual = Math.Sqrt(stdResidual / residuals.Length); |
| | | |
| | | // ä¼å5ï¼å¹¶è¡è¿æ»¤ |
| | | var validData = new List<(double X, double Y)>(measuredXAllFiltered.Length); |
| | | for (int i = 0; i < residuals.Length; i++) |
| | | { |
| | | if (Math.Abs((residuals[i] - meanResidual) / stdResidual) < ZScoreThreshold) |
| | | { |
| | | validData.Add((measuredXAllFiltered[i], measuredYAllFiltered[i])); |
| | | } |
| | | } |
| | | #endregion |
| | | |
| | | |
| | | |
| | | return ProcessData(splineX, splineY, |
| | | validData.Select(d => d.X).ToArray(), |
| | | validData.Select(d => d.Y).ToArray()); |
| | | } |
| | | #endregion |
| | | |
| | | #region æ°æ®å¤çæµç¨ä¼å |
| | | private (double[] mergedX, double[] mergedY, double[] optimizedX, double[] optimizedY) ProcessData( |
| | | double[] splineX, double[] splineY, |
| | | double[] measuredXValid, double[] measuredYValid) |
| | | { |
| | | // ä¼å6ï¼ç¼åæå¼è®¡ç® |
| | | double minX = measuredXValid[0], maxX = measuredXValid[^1]; |
| | | |
| | | // ä¼å7ï¼å¹¶è¡èç±»è®¡ç® |
| | | var (labels, areas) = new OptimizedPointClassifier().ClassifyPoints(measuredXValid, measuredYValid, "dbscan", new Dictionary<string, object> |
| | | { |
| | | { "eps", 3 }, { "min_samples", (int)(measuredXValid.GetLength(0) * 0.02) } |
| | | }); |
| | | |
| | | DynamicPolyDegree = areas.Count > 1 ? 2 : 1; |
| | | |
| | | // ä¼å8ï¼é¢åé
éå容é |
| | | var measured_x = new List<double>(measuredXValid.Length); |
| | | var measured_y = new List<double>(measuredYValid.Length); |
| | | |
| | | foreach (var item in areas) |
| | | { |
| | | // ä¼å9ï¼åéåèå´å¤æ |
| | | for (int i = 0; i < measuredXValid.Length; i++) |
| | | { |
| | | if (measuredXValid[i].Between(item.XRange.Min, item.XRange.Max) && |
| | | measuredYValid[i].Between(item.YRange.Min, item.YRange.Max)) |
| | | { |
| | | measured_x.Add(measuredXValid[i]); |
| | | measured_y.Add(measuredYValid[i]); |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | // ä¼å10ï¼åå¹¶æ²çº¿çæä¼å |
| | | var (mergedX, mergedY) = MergeCurvesOptimized(splineX, splineY, |
| | | measured_x.ToArray(), measured_y.ToArray(), DynamicPolyDegree); |
| | | |
| | | // ä¼å11ï¼ç©éµè¿ç®ä¼å |
| | | double xMean = mergedX.Mean(); |
| | | double xStd = mergedX.StandardDeviation(); |
| | | var xNorm = mergedX.Select(x => (x - xMean) / xStd).ToArray(); |
| | | |
| | | var X = Matrix<double>.Build.DenseOfRowArrays( |
| | | xNorm.Select(x => new[] { Math.Pow(x, 3), x * x, x, 1.0 }).ToArray()); |
| | | |
| | | // ä¼å12ï¼QRåè§£å é |
| | | var initialParams = MultipleRegression.QR(X, Vector<double>.Build.DenseOfArray(mergedY)); |
| | | |
| | | // ä¼å13ï¼çº¦æçæä¼å |
| | | var constraints = GenerateConstraints(xNorm, 50); |
| | | var optimizer = new Cobyla(BuildObjectiveFunction(xNorm, mergedY), constraints.ToArray()) |
| | | { MaxIterations = OptimizationMaxIterations }; |
| | | |
| | | if (!optimizer.Minimize(initialParams.ToArray())) |
| | | throw new OptimizationException("Optimization failed"); |
| | | |
| | | // ä¼å14ï¼éæ ·ç¹çæä¼å |
| | | double[] optimizedX = GenerateOptimizedX(splineX, minX, maxX); |
| | | double[] optimizedY = optimizedX.Select(x => CubicModel(optimizer.Solution, (x - xMean) / xStd)).ToArray(); |
| | | |
| | | return (mergedX, mergedY, optimizedX, optimizedY); |
| | | } |
| | | |
| | | |
| | | #endregion |
| | | |
| | | #region ä¼åå·¥å
·æ¹æ³ |
| | | private static double[] GenerateOptimizedX(double[] splineX, double minX, double maxX) |
| | | { |
| | | double lower = Math.Min(splineX[0], minX); |
| | | double upper = Math.Max(splineX[^1], maxX); |
| | | return Enumerable.Range(0, 300) |
| | | .Select(i => lower + i * (upper - lower) / 299.0) |
| | | .ToArray(); |
| | | } |
| | | |
| | | private List<NonlinearConstraint> GenerateConstraints(double[] xNorm, int sampleCount) |
| | | { |
| | | var constraints = new List<NonlinearConstraint>(sampleCount); |
| | | double min = xNorm[0], max = xNorm[^1]; |
| | | double step = (max - min) / (sampleCount - 1); |
| | | |
| | | for (int i = 0; i < sampleCount; i++) |
| | | { |
| | | double x = min + i * step; |
| | | constraints.Add(new NonlinearConstraint(4, |
| | | p => -Derivative(p, x), |
| | | ConstraintType.GreaterThanOrEqualTo, 0)); |
| | | } |
| | | return constraints; |
| | | } |
| | | |
| | | private static (double[] X, double[] Y) MergeCurvesOptimized( |
| | | double[] originalX, double[] originalY, |
| | | double[] measuredX, double[] measuredY, int degree) |
| | | { |
| | | if (measuredX.Length < 3) |
| | | return (originalX, originalY); |
| | | |
| | | // ä¼å15ï¼å¤é¡¹å¼æåå é |
| | | var polyCoeff = Fit.Polynomial(measuredX, measuredY, degree).Reverse().ToArray(); |
| | | |
| | | // ä¼å16ï¼å
åå¤ç¨ |
| | | var correctedY = new double[originalY.Length]; |
| | | Array.Copy(originalY, correctedY, originalY.Length); |
| | | |
| | | double measuredMin = measuredX[0], measuredMax = measuredX[^1]; |
| | | |
| | | // ä¼å17ï¼å¹¶è¡åºåå¤ç |
| | | Parallel.Invoke( |
| | | () => ProcessCoreRegion(originalX, correctedY, polyCoeff, measuredMin, measuredMax), |
| | | () => ProcessTransitionRegions(originalX, correctedY, originalY, polyCoeff, measuredMin, measuredMax) |
| | | ); |
| | | |
| | | return (originalX, correctedY); |
| | | } |
| | | #endregion |
| | | |
| | | #region å
¶ä»ä¿æä¸åçæ ¸å¿æ¹æ³ |
| | | |
| | | /// <summary> |
| | | /// 䏿¬¡å¤é¡¹å¼æ¨¡åï¼é广åï¼ |
| | | /// </summary> |
| | | private static double CubicModel(double[] param, double x) => |
| | | param[0] * x * x * x + param[1] * x * x + param[2] * x + param[3]; |
| | | |
| | | /// <summary> |
| | | /// å¤é¡¹å¼å¯¼æ°ï¼ç¨äºåè°æ§çº¦æï¼ |
| | | /// </summary> |
| | | private static double Derivative(double[] param, double x) => |
| | | 3 * param[0] * x * x + 2 * param[1] * x + param[2]; |
| | | |
| | | |
| | | /// <summary> |
| | | /// æå»ºç®æ 彿°ï¼åæ¹è¯¯å·®ï¼ |
| | | /// </summary> |
| | | private NonlinearObjectiveFunction BuildObjectiveFunction(double[] xNorm, double[] y) => |
| | | new NonlinearObjectiveFunction(4, p => |
| | | xNorm.Select((x, i) => Math.Pow(CubicModel(p, x) - y[i], 2)).Average() |
| | | ); |
| | | |
| | | // å¤é¡¹å¼æ±å¼ï¼æ¯æéåºç³»æ°ï¼ |
| | | private static double Polyval(double[] coefficients, double x) |
| | | { |
| | | double result = 0; |
| | | int degree = coefficients.Length - 1; |
| | | for (int i = 0; i < coefficients.Length; i++) |
| | | result += coefficients[i] * Math.Pow(x, degree - i); |
| | | return result; |
| | | } |
| | | |
| | | // çº¿æ§æå¼ |
| | | private static double LinearInterp(double x, double x0, double y0, double x1, double y1) |
| | | { |
| | | return x1 != x0 |
| | | ? y0 + (y1 - y0) * (x - x0) / (x1 - x0) |
| | | : y0; |
| | | } |
| | | #endregion |
| | | |
| | | #region æ°å¢ä¼åè¾
婿¹æ³ |
| | | private static void ProcessCoreRegion(double[] x, double[] y, double[] coeff, double min, double max) |
| | | { |
| | | for (int i = 0; i < x.Length; i++) |
| | | { |
| | | if (x[i].Between(min, max)) |
| | | y[i] = Polyval(coeff, x[i]); |
| | | } |
| | | } |
| | | |
| | | private static void ProcessTransitionRegions(double[] x, double[] y, double[] originalY, |
| | | double[] coeff, double measuredMin, double measuredMax) |
| | | { |
| | | // å·¦è¿æ¸¡å¤ç |
| | | double xLeftStart = Math.Max(measuredMin - TransitionWidth, x[0]); |
| | | double yLeftStart = Polyval(Fit.Polynomial(x, originalY, 3).Reverse().ToArray(), xLeftStart); |
| | | double yLeftEnd = Polyval(coeff, measuredMin); |
| | | |
| | | // å³è¿æ¸¡å¤ç |
| | | double xRightEnd = Math.Min(measuredMax + TransitionWidth, x[^1]); |
| | | double yRightStart = Polyval(coeff, measuredMax); |
| | | double yRightEnd = Polyval(Fit.Polynomial(x, originalY, 3).Reverse().ToArray(), xRightEnd); |
| | | |
| | | for (int i = 0; i < x.Length; i++) |
| | | { |
| | | double xi = x[i]; |
| | | if (xi.Between(xLeftStart, measuredMin)) |
| | | y[i] = LinearInterp(xi, xLeftStart, yLeftStart, measuredMin, yLeftEnd); |
| | | else if (xi.Between(measuredMax, xRightEnd)) |
| | | y[i] = LinearInterp(xi, measuredMax, yRightStart, xRightEnd, yRightEnd); |
| | | } |
| | | } |
| | | #endregion |
| | | } |
| | | |
| | | #region æ©å±æ¹æ³ |
| | | public static class ArrayExtensions |
| | | { |
| | | public static double Mean(this double[] array) => ArrayStatistics.Mean(array); |
| | | public static double StandardDeviation(this double[] array) => Math.Sqrt(ArrayStatistics.Variance(array)); |
| | | |
| | | public static bool Between(this double value, double min, double max) => |
| | | value >= min && value <= max; |
| | | } |
| | | #endregion |
| | | |
| | | #region æ°æ®ç¹å¯åº¦åç±»å¨ |
| | | |
| | | /* |
| | | 主è¦ä¼åç¹è¯´æï¼ç½é¡µå¼ç¨ï¼ï¼ |
| | | |
| | | ââKD-Tree空é´ç´¢å¼ââï¼éè¿æå»ºKD-Treeå°é»åæ¥è¯¢å¤æåº¦ä»O(n²)éè³O(n log n) |
| | | ââå¹¶è¡è®¡ç®ââï¼å¨æ°æ®æ ååååºåæåé¶æ®µä½¿ç¨Parallel.Foræå夿 ¸å©ç¨ç |
| | | ââéåæ©å±ç°ââï¼ç¨éå代æ¿éå½å®ç°å¹¿åº¦ä¼å
æç´¢ï¼é¿å
æ æº¢åºé®é¢ |
| | | ââå使°åç®±ä¼åââï¼å¨ç½æ ¼ååæ¶éç¨å使°åç®±çç¥æåå¯åº¦ä¼°è®¡åç¡®æ§ |
| | | ââå
åä¼åââï¼éç¨ç¼å²åºãé¿å
é夿°ç»å建ï¼åå°GCåå |
| | | 该å®ç°ç¸æ¯åå§çæ¬å¨å¤§è§æ¨¡æ°æ®éï¼10,000+ç¹ï¼ä¸å¯è·å¾10-50åçæ§è½æåï¼åæ¶ä¿æç®æ³åç¡®æ§ãæµè¯æ¶å»ºè®®ç»ååæ°è°ä¼å·¥å
·éæ©æä½³epsåmin_samplesåæ°ã |
| | | |
| | | */ |
| | | |
| | | //public class OptimizedDBSCAN |
| | | //{ |
| | | // private readonly double _epsSquared; |
| | | // private readonly int _minPts; |
| | | // private KDTree _kdTree; |
| | | |
| | | // public OptimizedDBSCAN(double eps, int minPts) |
| | | // { |
| | | // _epsSquared = eps * eps; |
| | | // _minPts = minPts; |
| | | // } |
| | | |
| | | // public int[] Run(double[,] points) |
| | | // { |
| | | |
| | | // var json = JsonHelper.Object2FormatJson(points); |
| | | |
| | | // int n = points.GetLength(0); |
| | | // int[] labels = new int[n]; |
| | | // Array.Fill(labels, -2); // -2=æªå¤ç, -1=åªå£° |
| | | |
| | | // _kdTree = new KDTree(points); |
| | | |
| | | // int clusterId = 0; |
| | | // for (int i = 0; i < n; i++) |
| | | // { |
| | | // if (labels[i] != -2) continue; |
| | | |
| | | // var neighbors = _kdTree.RangeQuery(i, _epsSquared); |
| | | |
| | | // // æ ¸å¿ç¹éªè¯ï¼ç½é¡µ7çæ ¸å¿å®ä¹ï¼ |
| | | // if (neighbors.Count < _minPts) |
| | | // { |
| | | // labels[i] = -1; |
| | | // continue; |
| | | // } |
| | | |
| | | // ExpandClusterOptimized(i, neighbors, clusterId++, labels); |
| | | // } |
| | | // return labels; |
| | | // } |
| | | |
| | | // // ä¿®å¤3ï¼å¢å¼ºåèç±»æ©å± |
| | | // private void ExpandClusterOptimized(int pointIdx, List<int> neighbors, |
| | | // int clusterId, int[] labels) |
| | | // { |
| | | // var corePoints = new HashSet<int>(); |
| | | // var queue = new Queue<int>(); |
| | | |
| | | // // 馿¬¡æ ¸å¿ç¹éªè¯ |
| | | // if (neighbors.Count >= _minPts) corePoints.Add(pointIdx); |
| | | |
| | | // foreach (var n in neighbors) |
| | | // { |
| | | // if (labels[n] == -1) labels[n] = clusterId; // åªå£°è½¬è¾¹ç |
| | | // if (labels[n] == -2) queue.Enqueue(n); |
| | | // } |
| | | |
| | | // while (queue.Count > 0) |
| | | // { |
| | | // int current = queue.Dequeue(); |
| | | // if (labels[current] != -2) continue; |
| | | |
| | | // var currentNeighbors = _kdTree.RangeQuery(current, _epsSquared); |
| | | // labels[current] = clusterId; |
| | | |
| | | // // äºæ¬¡æ ¸å¿ç¹éªè¯ |
| | | // if (currentNeighbors.Count >= _minPts) |
| | | // { |
| | | // corePoints.Add(current); |
| | | // foreach (var n in currentNeighbors) |
| | | // { |
| | | // if (labels[n] == -1) labels[n] = clusterId; |
| | | // if (labels[n] == -2 && !corePoints.Contains(n)) |
| | | // { |
| | | // queue.Enqueue(n); |
| | | // } |
| | | // } |
| | | // } |
| | | // } |
| | | // } |
| | | |
| | | // // ä¿®å¤åçKD-Treeå®ç° |
| | | // private class KDTree |
| | | // { |
| | | // private readonly double[,] _points; |
| | | // private readonly int[] _indices; |
| | | // private readonly int _dimensions; |
| | | |
| | | // public KDTree(double[,] points) |
| | | // { |
| | | // _points = points; |
| | | // _indices = Enumerable.Range(0, points.GetLength(0)).ToArray(); |
| | | // _dimensions = points.GetLength(1); |
| | | // BuildTree(0, _indices.Length - 1, 0); |
| | | // } |
| | | |
| | | // private void BuildTree(int left, int right, int depth) |
| | | // { |
| | | // if (left >= right) return; |
| | | |
| | | // int axis = depth % _dimensions; |
| | | // int median = (left + right) / 2; |
| | | |
| | | // // ä¼åå¿«ééæ©ç®æ³ï¼ç½é¡µ7çå®ç°ï¼ |
| | | // SelectMedian(left, right, median, axis); |
| | | // BuildTree(left, median - 1, depth + 1); |
| | | // BuildTree(median + 1, right, depth + 1); |
| | | // } |
| | | |
| | | // private void SelectMedian(int left, int right, int k, int axis) |
| | | // { |
| | | // while (left < right) |
| | | // { |
| | | // int pivot = Partition(left, right, axis); |
| | | // if (pivot == k) return; |
| | | // if (pivot < k) left = pivot + 1; |
| | | // else right = pivot - 1; |
| | | // } |
| | | // } |
| | | |
| | | // private int Partition(int left, int right, int axis) |
| | | // { |
| | | // double pivotValue = _points[_indices[right], axis]; |
| | | // int storeIndex = left; |
| | | // for (int i = left; i < right; i++) |
| | | // { |
| | | // if (_points[_indices[i], axis] < pivotValue) |
| | | // { |
| | | // Swap(_indices, storeIndex, i); |
| | | // storeIndex++; |
| | | // } |
| | | // } |
| | | // Swap(_indices, storeIndex, right); |
| | | // return storeIndex; |
| | | // } |
| | | |
| | | // // ä¿®å¤2ï¼å¢å¼ºåé»åæ¥è¯¢ |
| | | // public List<int> RangeQuery(int pointIdx, double radiusSquared) |
| | | // { |
| | | // var queryPoint = GetPoint(pointIdx); |
| | | // var result = new List<int>(); |
| | | // RangeQueryRecursive(queryPoint, 0, 0, radiusSquared, result); |
| | | // return result; |
| | | // } |
| | | |
| | | |
| | | // private void RangeQueryRecursive(double[] queryPoint, int nodeIdx, int depth, |
| | | // double radiusSquared, List<int> result) |
| | | // { |
| | | // if (nodeIdx >= _indices.Length) return; |
| | | |
| | | // int currentIdx = _indices[nodeIdx]; |
| | | // double[] currentPoint = GetPoint(currentIdx); |
| | | |
| | | // double distanceSq = CalculateDistanceSquared(queryPoint, currentPoint); |
| | | // if (distanceSq <= radiusSquared) result.Add(currentIdx); |
| | | |
| | | // int axis = depth % _dimensions; |
| | | // double axisDistance = currentPoint[axis] - queryPoint[axis]; |
| | | |
| | | // // å¨æåªæé»è¾ |
| | | // if (axisDistance <= 0 || axisDistance * axisDistance <= radiusSquared) |
| | | // { |
| | | // RangeQueryRecursive(queryPoint, 2 * nodeIdx + 1, depth + 1, radiusSquared, result); |
| | | // } |
| | | // if (axisDistance >= 0 || axisDistance * axisDistance <= radiusSquared) |
| | | // { |
| | | // RangeQueryRecursive(queryPoint, 2 * nodeIdx + 2, depth + 1, radiusSquared, result); |
| | | // } |
| | | // } |
| | | |
| | | // // ä¿®å¤1ï¼å¨æç»´åº¦è·ç¦»è®¡ç® |
| | | // private double CalculateDistanceSquared(double[] point1, double[] point2) |
| | | // { |
| | | // double distSq = 0; |
| | | // for (int i = 0; i < point1.Length; i++) |
| | | // { |
| | | // double diff = point1[i] - point2[i]; |
| | | // distSq += diff * diff; |
| | | // } |
| | | // return distSq; |
| | | // } |
| | | |
| | | // private double[] GetPoint(int index) => |
| | | // Enumerable.Range(0, _dimensions) |
| | | // .Select(i => _points[index, i]) |
| | | // .ToArray(); |
| | | |
| | | // private void Swap(int[] arr, int i, int j) => |
| | | // (arr[i], arr[j]) = (arr[j], arr[i]); |
| | | // } |
| | | //} |
| | | //public class OptimizedPointClassifier |
| | | //{ |
| | | // public class DenseArea |
| | | // { |
| | | // public (double Min, double Max) XRange { get; set; } |
| | | // public (double Min, double Max) YRange { get; set; } |
| | | // } |
| | | |
| | | // // æå°å¯åº¦éå¼ï¼ç½é¡µ7ï¼ |
| | | // public const double MinDensity = 0.05; |
| | | |
| | | // // å¯éçåç±»æ¹æ³ï¼ç½é¡µ3ãç½é¡µ7ï¼ |
| | | // public enum Method |
| | | // { |
| | | // DBSCAN, |
| | | // KDE, |
| | | // Grid |
| | | // } |
| | | |
| | | |
| | | // public (int[] Labels, List<DenseArea> DenseAreas) ClassifyPoints( |
| | | // double[] x, double[] y, Method method = Method.DBSCAN, |
| | | // Dictionary<string, object> parameters = null) |
| | | // { |
| | | // // å¹¶è¡æ ååæ°æ®ï¼ç½é¡µ3ãç½é¡µ7ï¼ |
| | | // var scaledData = OptimizedScaleData(x, y); |
| | | |
| | | // int[] labels = new int[x.Length]; |
| | | // switch (method) |
| | | // { |
| | | // case Method.DBSCAN: |
| | | // { |
| | | // // 使ç¨ä¼ååçDBSCANï¼ç½é¡µ4ãç½é¡µ6ï¼ |
| | | // var dbscan = new OptimizedDBSCAN( |
| | | // (double)(parameters?["eps"] ?? 0.25), |
| | | // (int)(parameters?["min_samples"] ?? 15)); |
| | | |
| | | // labels = dbscan.Run(scaledData); |
| | | // } |
| | | // break; |
| | | // case Method.KDE: |
| | | // { |
| | | // // æ ¸å¯åº¦ä¼°è®¡ |
| | | // int bandwidth = parameters != null && parameters.ContainsKey("bandwidth") |
| | | // ? (int)parameters["bandwidth"] |
| | | // : 1; |
| | | // double percentile = parameters != null && parameters.ContainsKey("percentile") |
| | | // ? (double)parameters["percentile"] |
| | | // : 70.0; |
| | | |
| | | // // 计ç®å¯åº¦ |
| | | // double[] density = new double[x.Length]; |
| | | // for (int i = 0; i < x.Length; i++) |
| | | // { |
| | | // double sum = 0; |
| | | // for (int j = 0; j < x.Length; j++) |
| | | // { |
| | | // double distance = Math.Sqrt( |
| | | // Math.Pow(scaledData[i, 0] - scaledData[j, 0], 2) + |
| | | // Math.Pow(scaledData[i, 1] - scaledData[j, 1], 2)); |
| | | // sum += Math.Exp(-distance * distance / (2 * bandwidth * bandwidth)); |
| | | // } |
| | | // density[i] = sum / (x.Length * 2 * Math.PI * bandwidth * bandwidth); |
| | | // } |
| | | |
| | | // // 计ç®éå¼ |
| | | // double threshold = GetPercentile(density, percentile); |
| | | |
| | | // // æ è®°å¯éåºå |
| | | // labels = density.Select(d => d >= threshold ? 1 : 0).ToArray(); |
| | | // } |
| | | // break; |
| | | // case Method.Grid: |
| | | // { |
| | | // // å¨æç½æ ¼åå |
| | | // int xBins = parameters != null && parameters.ContainsKey("x_bins") |
| | | // ? (int)parameters["x_bins"] |
| | | // : 30; |
| | | // int yBins = parameters != null && parameters.ContainsKey("y_bins") |
| | | // ? (int)parameters["y_bins"] |
| | | // : 20; |
| | | // double percentile = parameters != null && parameters.ContainsKey("percentile") |
| | | // ? (double)parameters["percentile"] |
| | | // : 75.0; |
| | | |
| | | // // åå»ºç½æ ¼ |
| | | // double[] xBinsArray = CreateBins(x, xBins); |
| | | // double[] yBinsArray = CreateBins(y, yBins); |
| | | |
| | | // // 计ç®ç½æ ¼å¯åº¦ |
| | | // int[,] gridDensity = new int[xBins, yBins]; |
| | | // for (int i = 0; i < x.Length; i++) |
| | | // { |
| | | // int xBin = Array.BinarySearch(xBinsArray, x[i]); |
| | | // int yBin = Array.BinarySearch(yBinsArray, y[i]); |
| | | // if (xBin >= 0 && xBin < xBins && yBin >= 0 && yBin < yBins) |
| | | // gridDensity[xBin, yBin]++; |
| | | // } |
| | | |
| | | // // 计ç®éå¼ |
| | | // double threshold = GetPercentile(gridDensity.Cast<double>().ToArray(), percentile); |
| | | |
| | | // // æ è®°å¯éç½æ ¼ |
| | | // for (int i = 0; i < x.Length; i++) |
| | | // { |
| | | // int xBin = Array.BinarySearch(xBinsArray, x[i]); |
| | | // int yBin = Array.BinarySearch(yBinsArray, y[i]); |
| | | // if (xBin >= 0 && xBin < xBins && yBin >= 0 && yBin < yBins) |
| | | // labels[i] = gridDensity[xBin, yBin] > threshold ? 1 : 0; |
| | | // else |
| | | // labels[i] = 0; |
| | | // } |
| | | // } |
| | | // break; |
| | | // default: |
| | | // { |
| | | // throw new ArgumentException("䏿¯æçæ£æµæ¹æ³"); |
| | | // } |
| | | // } |
| | | |
| | | // // å¹¶è¡æåå¯éåºåï¼ç½é¡µ7ï¼ |
| | | // return (labels, ExtractDenseAreasParallel(x, y, labels)); |
| | | // } |
| | | |
| | | // private double[,] To2DArray(double[] x, double[] y) |
| | | // { |
| | | // var arr = new double[x.Length, 2]; |
| | | // Parallel.For(0, x.Length, i => |
| | | // { |
| | | // arr[i, 0] = x[i]; |
| | | // arr[i, 1] = y[i]; |
| | | // }); |
| | | // return arr; |
| | | // } |
| | | |
| | | // private double GetPercentile(double[] data, double percentile) |
| | | // { |
| | | // Array.Sort(data); |
| | | // int index = (int)Math.Ceiling(percentile / 100.0 * data.Length) - 1; |
| | | // return data[index]; |
| | | // } |
| | | |
| | | // private double[] CreateBins(double[] data, int bins) |
| | | // { |
| | | // double min = data.Min(); |
| | | // double max = data.Max(); |
| | | // double step = (max - min) / bins; |
| | | // return Enumerable.Range(0, bins + 1).Select(i => min + i * step).ToArray(); |
| | | // } |
| | | |
| | | // private double[,] OptimizedScaleData(double[] x, double[] y) |
| | | // { |
| | | // int len = x.Length; |
| | | // var scaled = new double[len, 2]; |
| | | |
| | | // // å¹¶è¡è®¡ç®ç»è®¡éï¼ç½é¡µ3ï¼ |
| | | // Parallel.For(0, 2, dim => |
| | | // { |
| | | // double[] data = dim == 0 ? x : y; |
| | | // double sum = 0, sumSq = 0; |
| | | |
| | | // for (int i = 0; i < len; i++) |
| | | // { |
| | | // sum += data[i]; |
| | | // sumSq += data[i] * data[i]; |
| | | // } |
| | | |
| | | // double mean = sum / len; |
| | | // double std = Math.Sqrt(sumSq / len - mean * mean); |
| | | |
| | | // for (int i = 0; i < len; i++) |
| | | // scaled[i, dim] = (data[i] - mean) / std; |
| | | // }); |
| | | |
| | | // return scaled; |
| | | // } |
| | | |
| | | // private List<DenseArea> ExtractDenseAreasParallel(double[] x, double[] y, int[] labels) |
| | | // { |
| | | // var areas = new List<DenseArea>(); |
| | | // var uniqueLabels = labels.Distinct().AsParallel(); |
| | | |
| | | // uniqueLabels.ForAll(label => |
| | | // { |
| | | // if (label == -1) return; |
| | | |
| | | // var indices = Enumerable.Range(0, labels.Length) |
| | | // .Where(i => labels[i] == label).ToList(); |
| | | |
| | | // if (indices.Count > x.Length * MinDensity) |
| | | // { |
| | | // var xVals = indices.Select(i => x[i]); |
| | | // var yVals = indices.Select(i => y[i]); |
| | | |
| | | // lock (areas) |
| | | // { |
| | | // areas.Add(new DenseArea |
| | | // { |
| | | // XRange = (xVals.Min(), xVals.Max()), |
| | | // YRange = (yVals.Min(), yVals.Max()) |
| | | // }); |
| | | // } |
| | | // } |
| | | // }); |
| | | |
| | | // return areas.OrderBy(a => a.XRange.Min).ToList(); |
| | | // } |
| | | |
| | | // #endregion |
| | | |
| | | |
| | | |
| | | //} |
| | | |
| | | |
| | | public class OptimizedPointClassifier |
| | | { |
| | | #region æ ¸å¿æ°æ®ç»æ |
| | | public class DenseArea |
| | | { |
| | | public (double Min, double Max) XRange { get; init; } |
| | | public (double Min, double Max) YRange { get; init; } |
| | | } |
| | | |
| | | private struct PointData |
| | | { |
| | | public double X; |
| | | public double Y; |
| | | public int OriginalIndex; |
| | | } |
| | | #endregion |
| | | |
| | | public (int[] Labels, List<DenseArea> DenseAreas) ClassifyPoints( |
| | | double[] x, double[] y, |
| | | string method = "dbscan", |
| | | Dictionary<string, object> parameters = null) |
| | | { |
| | | ValidateInput(x, y); |
| | | var scaledData = ZScoreNormalization(x, y); |
| | | |
| | | int[] labels = method.ToLower() switch |
| | | { |
| | | "dbscan" => DBSCAN(scaledData, parameters), |
| | | "kde" => OptimizedKDE(scaledData, parameters), |
| | | "grid" => OptimizedGrid(x, y, parameters), |
| | | _ => throw new ArgumentException("䏿¯æçæ¹æ³") |
| | | }; |
| | | |
| | | return (labels, ExtractDenseAreas(x, y, labels)); |
| | | } |
| | | |
| | | #region é¢å¤ç |
| | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | | private void ValidateInput(double[] x, double[] y) |
| | | { |
| | | if (x.Length != y.Length) |
| | | throw new ArgumentException("xåyé¿åº¦ä¸ä¸è´"); |
| | | } |
| | | |
| | | private double[,] ZScoreNormalization(double[] x, double[] y) |
| | | { |
| | | Span<double> xSpan = x.AsSpan(); |
| | | Span<double> ySpan = y.AsSpan(); |
| | | |
| | | var (xMean, xStd) = CalculateStats(xSpan); |
| | | var (yMean, yStd) = CalculateStats(ySpan); |
| | | |
| | | double[,] scaled = new double[x.Length, 2]; |
| | | Parallel.For(0, x.Length, i => |
| | | { |
| | | scaled[i, 0] = (x[i] - xMean) / xStd; |
| | | scaled[i, 1] = (y[i] - yMean) / yStd; |
| | | }); |
| | | return scaled; |
| | | } |
| | | |
| | | private (double Mean, double Std) CalculateStats(Span<double> data) |
| | | { |
| | | double sum = 0, sumSq = 0; |
| | | foreach (var d in data) |
| | | { |
| | | sum += d; |
| | | sumSq += d * d; |
| | | } |
| | | double mean = sum / data.Length; |
| | | double std = Math.Sqrt(sumSq / data.Length - mean * mean); |
| | | return (mean, std); |
| | | } |
| | | #endregion |
| | | |
| | | #region DBSCANä¼åå®ç° |
| | | private int[] DBSCAN(double[,] scaledData, Dictionary<string, object> parameters) |
| | | { |
| | | double eps = parameters.GetValueOrDefault("eps", 0.3); |
| | | int minSamples = parameters.GetValueOrDefault("min_samples", (int)(scaledData.GetLength(0) * 0.02)); |
| | | |
| | | var spatialIndex = new SpatialIndex(scaledData, eps); |
| | | var dbscan = new OptimizedDBSCAN(eps, minSamples, spatialIndex); |
| | | return dbscan.Run(scaledData); |
| | | } |
| | | |
| | | private class SpatialIndex |
| | | { |
| | | private readonly Dictionary<(int, int), List<int>> _grid = new(); |
| | | private readonly double _cellSize; |
| | | private readonly double _minX, _minY; |
| | | |
| | | public int TotalPoints; |
| | | |
| | | public SpatialIndex(double[,] points, double eps) |
| | | { |
| | | _cellSize = eps * 1.5; // æ©å¤§ç½æ ¼å°ºå¯¸ç¡®ä¿Îµé»åè¦ç |
| | | _minX = Enumerable.Range(0, points.GetLength(0)).Min(i => points[i, 0]); |
| | | _minY = Enumerable.Range(0, points.GetLength(0)).Min(i => points[i, 1]); |
| | | |
| | | for (int i = 0; i < points.GetLength(0); i++) |
| | | { |
| | | int xCell = (int)((points[i, 0] - _minX) / _cellSize); |
| | | int yCell = (int)((points[i, 1] - _minY) / _cellSize); |
| | | var key = (xCell, yCell); |
| | | |
| | | if (!_grid.ContainsKey(key)) _grid[key] = new List<int>(); |
| | | _grid[key].Add(i); |
| | | } |
| | | TotalPoints = points.GetLength(0); |
| | | } |
| | | |
| | | public List<int> GetNeighborCandidates(int pointIndex, double[,] points) |
| | | { |
| | | var candidates = new List<int>(); |
| | | double x = points[pointIndex, 0]; |
| | | double y = points[pointIndex, 1]; |
| | | |
| | | int xCell = (int)((x - _minX) / _cellSize); |
| | | int yCell = (int)((y - _minY) / _cellSize); |
| | | |
| | | for (int dx = -1; dx <= 1; dx++) |
| | | { |
| | | for (int dy = -1; dy <= 1; dy++) |
| | | { |
| | | var key = (xCell + dx, yCell + dy); |
| | | if (_grid.TryGetValue(key, out var cellPoints)) |
| | | candidates.AddRange(cellPoints); |
| | | } |
| | | } |
| | | return candidates.Distinct().ToList(); |
| | | } |
| | | } |
| | | |
| | | private class OptimizedDBSCAN |
| | | { |
| | | private readonly double _eps; |
| | | private readonly int _minPts; |
| | | private readonly SpatialIndex _index; |
| | | |
| | | public OptimizedDBSCAN(double rawEps, int minPts, SpatialIndex index) |
| | | { |
| | | _eps = rawEps / Math.Sqrt(2); // æ ååè·ç¦»ä¿®æ£ |
| | | _minPts = CalculateMinPts(minPts, index.TotalPoints); |
| | | _index = index; |
| | | } |
| | | |
| | | private int CalculateMinPts(int minPts, int totalPoints) |
| | | { |
| | | return minPts > 0 ? minPts : |
| | | Math.Max(5, (int)(Math.Log(totalPoints) + totalPoints * 0.005)); |
| | | } |
| | | |
| | | public int[] Run(double[,] points) |
| | | { |
| | | int n = points.GetLength(0); |
| | | int[] labels = Enumerable.Repeat(-2, n).ToArray(); |
| | | int clusterId = -1; |
| | | |
| | | Parallel.For(0, n, i => |
| | | { |
| | | if (labels[i] != -2) return; |
| | | |
| | | var neighbors = GetNeighbors(points, i); |
| | | if (neighbors.Count < _minPts) |
| | | { |
| | | Interlocked.Exchange(ref labels[i], -1); |
| | | return; |
| | | } |
| | | |
| | | int newClusterId = Interlocked.Increment(ref clusterId); |
| | | ExpandCluster(points, i, neighbors, newClusterId, labels); |
| | | }); |
| | | |
| | | return labels; |
| | | } |
| | | |
| | | private void ExpandCluster(double[,] points, int seedIndex, List<int> neighbors, int clusterId, int[] labels) |
| | | { |
| | | //var queue = new ConcurrentQueue<int>(neighbors); |
| | | //while (queue.TryDequeue(out int current)) |
| | | //{ |
| | | // if (Interlocked.CompareExchange(ref labels[current], clusterId, -2) != -2) continue; |
| | | |
| | | // var newNeighbors = GetNeighbors(points, current); |
| | | // if (newNeighbors.Count >= _minPts) |
| | | // newNeighbors.ForEach(n => queue.Enqueue(n)); |
| | | //} |
| | | |
| | | var visited = new ConcurrentDictionary<int, bool>(); |
| | | var queue = new ConcurrentQueue<int>(neighbors); |
| | | |
| | | while (!queue.IsEmpty) |
| | | { |
| | | if (queue.TryDequeue(out int current)) |
| | | { |
| | | if (Interlocked.CompareExchange(ref labels[current], clusterId, -2) != -2) |
| | | continue; |
| | | |
| | | var newNeighbors = GetNeighbors(points, current); |
| | | foreach (var n in newNeighbors) |
| | | { |
| | | if (!visited.ContainsKey(n)) |
| | | { |
| | | queue.Enqueue(n); |
| | | visited.TryAdd(n, true); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | private List<int> GetNeighbors(double[,] points, int pointIndex) |
| | | { |
| | | var candidates = _index.GetNeighborCandidates(pointIndex, points); |
| | | var neighbors = new List<int>(); |
| | | |
| | | Parallel.ForEach(candidates, j => |
| | | { |
| | | double dx = points[pointIndex, 0] - points[j, 0]; |
| | | double dy = points[pointIndex, 1] - points[j, 1]; |
| | | if (dx * dx + dy * dy <= _eps * _eps) |
| | | lock (neighbors) { neighbors.Add(j); } |
| | | }); |
| | | |
| | | return neighbors; |
| | | } |
| | | } |
| | | #endregion |
| | | |
| | | #region å
¶ä»æ¹æ³å®ç°ï¼KDE/Gridï¼åè¾
婿¹æ³ |
| | | // 代ç ç»æç±»ä¼¼ï¼å
å«ï¼ |
| | | // 1. åç®±é¢å¤ç |
| | | // 2. å¹¶è¡è®¡ç®å¯åº¦/ç½æ ¼ç»è®¡ |
| | | // 3. éå¼è®¡ç® |
| | | |
| | | #region KDEä¼åå®ç° |
| | | private int[] OptimizedKDE(double[,] scaledData, Dictionary<string, object> parameters) |
| | | { |
| | | int bandwidth = parameters.GetValueOrDefault("bandwidth", 1); |
| | | double percentile = parameters.GetValueOrDefault("percentile", 70.0); |
| | | |
| | | // åç®±å éè®¡ç® |
| | | var (bins, binDensities) = CreateBinnedDensities(scaledData, bandwidth); |
| | | |
| | | // å¹¶è¡æå¼è®¡ç®å¯åº¦ |
| | | double[] density = new double[scaledData.GetLength(0)]; |
| | | Parallel.For(0, scaledData.GetLength(0), i => |
| | | { |
| | | density[i] = InterpolateDensity(scaledData, i, bins, binDensities); |
| | | }); |
| | | |
| | | // 计ç®å¨æéå¼ |
| | | double threshold = CalculateDynamicThreshold(density, percentile); |
| | | |
| | | return density.Select(d => d >= threshold ? 1 : 0).ToArray(); |
| | | } |
| | | |
| | | private (double[,], double[]) CreateBinnedDensities(double[,] data, int bandwidth) |
| | | { |
| | | const int gridSize = 50; // åç®±ç²åº¦ |
| | | int n = data.GetLength(0); |
| | | |
| | | // 计ç®å箱边ç |
| | | var (xMin, xMax, yMin, yMax) = GetDataRange(data); |
| | | double xStep = (xMax - xMin) / gridSize; |
| | | double yStep = (yMax - yMin) / gridSize; |
| | | |
| | | // åå§ååç®±ç©éµ |
| | | double[,] bins = new double[gridSize + 1, gridSize + 1]; |
| | | Parallel.For(0, gridSize + 1, i => |
| | | { |
| | | for (int j = 0; j <= gridSize; j++) |
| | | { |
| | | double x = xMin + i * xStep; |
| | | double y = yMin + j * yStep; |
| | | bins[i, j] = GaussianKernel(x, y, data, bandwidth); |
| | | } |
| | | }); |
| | | |
| | | // é¢è®¡ç®åç®±å¯åº¦ |
| | | double[] flatDensities = new double[(gridSize + 1) * (gridSize + 1)]; |
| | | Buffer.BlockCopy(bins, 0, flatDensities, 0, sizeof(double) * bins.Length); |
| | | |
| | | return (bins, flatDensities); |
| | | } |
| | | |
| | | private double GaussianKernel(double x, double y, double[,] data, int bandwidth) |
| | | { |
| | | double sum = 0; |
| | | double factor = 1.0 / (2 * Math.PI * bandwidth * bandwidth); |
| | | |
| | | for (int i = 0; i < data.GetLength(0); i++) |
| | | { |
| | | double dx = x - data[i, 0]; |
| | | double dy = y - data[i, 1]; |
| | | double distanceSq = dx * dx + dy * dy; |
| | | sum += Math.Exp(-distanceSq / (2 * bandwidth * bandwidth)); |
| | | } |
| | | |
| | | return sum * factor / data.GetLength(0); |
| | | } |
| | | |
| | | private double InterpolateDensity(double[,] data, int index, double[,] bins, double[] binDensities) |
| | | { |
| | | const int gridSize = 50; |
| | | var (xMin, xMax, yMin, yMax) = GetDataRange(data); |
| | | |
| | | // 计ç®å½ä¸ååæ |
| | | double x = (data[index, 0] - xMin) / (xMax - xMin) * gridSize; |
| | | double y = (data[index, 1] - yMin) / (yMax - yMin) * gridSize; |
| | | |
| | | // åçº¿æ§æå¼ |
| | | int x0 = (int)Math.Floor(x); |
| | | int y0 = (int)Math.Floor(y); |
| | | double dx = x - x0; |
| | | double dy = y - y0; |
| | | |
| | | return bins[x0, y0] * (1 - dx) * (1 - dy) + |
| | | bins[x0 + 1, y0] * dx * (1 - dy) + |
| | | bins[x0, y0 + 1] * (1 - dx) * dy + |
| | | bins[x0 + 1, y0 + 1] * dx * dy; |
| | | } |
| | | #endregion |
| | | |
| | | #region Gridä¼åå®ç° |
| | | private int[] OptimizedGrid(double[] x, double[] y, Dictionary<string, object> parameters) |
| | | { |
| | | int xBins = parameters.GetValueOrDefault("x_bins", 30); |
| | | int yBins = parameters.GetValueOrDefault("y_bins", 20); |
| | | double percentile = parameters.GetValueOrDefault("percentile", 75.0); |
| | | |
| | | // 卿åç®±çç¥ |
| | | var xEdges = AdaptiveBinning(x, xBins); |
| | | var yEdges = AdaptiveBinning(y, yBins); |
| | | |
| | | // 使ç¨åå
¸åå¨ç½æ ¼è®¡æ° |
| | | var gridCounts = new ConcurrentDictionary<(int, int), int>(); |
| | | |
| | | // å¹¶è¡ç»è®¡ç½æ ¼è®¡æ° |
| | | Parallel.For(0, x.Length, i => |
| | | { |
| | | int xBin = FindBinIndex(x[i], xEdges); |
| | | int yBin = FindBinIndex(y[i], yEdges); |
| | | if (xBin >= 0 && yBin >= 0) |
| | | gridCounts.AddOrUpdate((xBin, yBin), 1, (k, v) => v + 1); |
| | | }); |
| | | |
| | | // æåå¯åº¦ç©éµ |
| | | double[] densityMatrix = gridCounts.Values.Select(v => (double)v).ToArray(); |
| | | double threshold = GetPercentile(densityMatrix, percentile); |
| | | |
| | | // çææ ç¾ |
| | | int[] labels = new int[x.Length]; |
| | | Parallel.For(0, x.Length, i => |
| | | { |
| | | int xBin = FindBinIndex(x[i], xEdges); |
| | | int yBin = FindBinIndex(y[i], yEdges); |
| | | labels[i] = (xBin >= 0 && yBin >= 0 && gridCounts.TryGetValue((xBin, yBin), out int count)) |
| | | ? (count > threshold ? 1 : 0) |
| | | : 0; |
| | | }); |
| | | |
| | | return labels; |
| | | } |
| | | |
| | | private double[] AdaptiveBinning(double[] data, int targetBins) |
| | | { |
| | | // åºäºæ°æ®åå¸ç卿åç®± |
| | | Array.Sort(data); |
| | | double[] quantiles = new double[targetBins + 1]; |
| | | for (int i = 0; i <= targetBins; i++) |
| | | quantiles[i] = data[(int)((double)i / targetBins * (data.Length - 1))]; |
| | | return quantiles.Distinct(); |
| | | } |
| | | |
| | | private int FindBinIndex(double value, double[] edges) |
| | | { |
| | | int index = Array.BinarySearch(edges, value); |
| | | return index < 0 ? ~index - 1 : Math.Min(index, edges.Length - 2); |
| | | } |
| | | |
| | | private double GetPercentile(double[] data, double percentile) |
| | | { |
| | | Array.Sort(data); |
| | | int index = (int)Math.Ceiling(percentile / 100.0 * data.Length) - 1; |
| | | return data[index]; |
| | | } |
| | | #endregion |
| | | |
| | | #region è¾
婿¹æ³ |
| | | private (double xMin, double xMax, double yMin, double yMax) GetDataRange(double[,] data) |
| | | { |
| | | double xMin = double.MaxValue, xMax = double.MinValue; |
| | | double yMin = double.MaxValue, yMax = double.MinValue; |
| | | |
| | | for (int i = 0; i < data.GetLength(0); i++) |
| | | { |
| | | xMin = Math.Min(xMin, data[i, 0]); |
| | | xMax = Math.Max(xMax, data[i, 0]); |
| | | yMin = Math.Min(yMin, data[i, 1]); |
| | | yMax = Math.Max(yMax, data[i, 1]); |
| | | } |
| | | return (xMin, xMax, yMin, yMax); |
| | | } |
| | | |
| | | private double CalculateDynamicThreshold(double[] densities, double percentile) |
| | | { |
| | | // åºäºæ»å¨çªå£ç卿éå¼ |
| | | const int windowSize = 100; |
| | | double[] sorted = densities.OrderBy(d => d).ToArray(); |
| | | int index = (int)Math.Ceiling(sorted.Length * percentile / 100) - 1; |
| | | return index >= 0 ? sorted[index] : sorted.Last(); |
| | | } |
| | | #endregion |
| | | |
| | | |
| | | private List<DenseArea> ExtractDenseAreas(double[] x, double[] y, int[] labels) |
| | | { |
| | | // 使ç¨Spanä¼åå
åè®¿é® |
| | | var clusters = labels.Distinct().Where(l => l != -1) |
| | | .Select(clusterId => new |
| | | { |
| | | Points = labels.Select((l, i) => (l, i)) |
| | | .Where(t => t.l == clusterId) |
| | | .Select(t => (x[t.i], y[t.i])) |
| | | }) |
| | | .Where(c => c.Points.Count() > x.Length * 0.05); |
| | | |
| | | return clusters.Select(c => new DenseArea |
| | | { |
| | | XRange = (c.Points.Min(p => p.Item1), c.Points.Max(p => p.Item1)), |
| | | YRange = (c.Points.Min(p => p.Item2), c.Points.Max(p => p.Item2)) |
| | | }).ToList(); |
| | | } |
| | | #endregion |
| | | } |
| | | |
| | | public static class ParameterExtensions |
| | | { |
| | | public static T GetValueOrDefault<T>(this Dictionary<string, object> dict, string key, T defaultValue) |
| | | { |
| | | return (dict != null && dict.TryGetValue(key, out object value)) ? |
| | | (T)Convert.ChangeType(value, typeof(T)) : defaultValue; |
| | | } |
| | | } |
| | | |
| | | |
| | | #endregion |
| | | } |
| | |
| | |  |
| | | using Accord.IO; |
| | | using Accord.Math; |
| | | using Accord.Math; |
| | | using Accord.Math.Optimization; |
| | | using DevExpress.Data.Extensions; |
| | | using DevExpress.XtraRichEdit.Model; |
| | | using MathNet.Numerics; |
| | | using MathNet.Numerics.Interpolation; |
| | | using MathNet.Numerics.LinearAlgebra; |
| | | using MathNet.Numerics.LinearRegression; |
| | | using MathNet.Numerics.Statistics; |
| | | using NPOI.HSSF.Record.CF; |
| | | using NPOI.SS.Formula.Functions; |
| | | using System.Text; |
| | | using static Org.BouncyCastle.Math.EC.ECCurve; |
| | | |
| | | namespace IStation.Test |
| | | { |
| | |
| | | var measuredXAllFiltered = measuredXAll.Skip(startIndex).ToArray(); |
| | | var measuredYAllFiltered = measuredYAll.Skip(startIndex).ToArray(); |
| | | |
| | | |
| | | |
| | | |
| | | if (measuredXAllFiltered.Length < 5) |
| | | { |
| | | Console.WriteLine("å¼å¸¸å¼è¿æ»¤åæ°æ®è¿å°"); |
| | | return default; |
| | | } |
| | | |
| | | #region æ¥éª¤1ï¼ç¨³å¥åå½ä¸å¼å¸¸å¼è¿æ»¤ï¼æå°äºä¹æ³ï¼ |
| | | // æ§è¡ç®å线æ§åå½è·ååºå线 |
| | |
| | | |
| | | } |
| | | |
| | | |
| | | |
| | | |
| | | public class DBSCAN |
| | | { |
| | | private readonly double eps; |