diff --git a/tests/AiDotNet.Tests/UnitTests/FitDetectors/BayesianFitDetectorTests.cs b/tests/AiDotNet.Tests/UnitTests/FitDetectors/BayesianFitDetectorTests.cs new file mode 100644 index 000000000..f89d2cc33 --- /dev/null +++ b/tests/AiDotNet.Tests/UnitTests/FitDetectors/BayesianFitDetectorTests.cs @@ -0,0 +1,131 @@ +using AiDotNet.FitDetectors; +using AiDotNet.LinearAlgebra; +using AiDotNet.Models; +using AiDotNet.Models.Options; +using Xunit; + +namespace AiDotNetTests.UnitTests.FitDetectors +{ + public class BayesianFitDetectorTests + { + private ModelEvaluationData, Vector> CreateMockEvaluationData( + Vector predicted, Vector actual) + { + return new ModelEvaluationData, Vector> + { + ModelStats = new ModelStats, Vector> + { + Predicted = predicted, + Actual = actual + } + }; + } + + [Fact] + public void Constructor_WithDefaultOptions_CreatesInstance() + { + // Act + var detector = new BayesianFitDetector, Vector>(); + + // Assert + Assert.NotNull(detector); + } + + [Fact] + public void Constructor_WithCustomOptions_CreatesInstance() + { + // Arrange + var options = new BayesianFitDetectorOptions + { + PriorStrength = 0.5, + CredibleIntervalLevel = 0.9 + }; + + // Act + var detector = new BayesianFitDetector, Vector>(options); + + // Assert + Assert.NotNull(detector); + } + + [Fact] + public void DetectFit_WithValidData_ReturnsResult() + { + // Arrange + var detector = new BayesianFitDetector, Vector>(); + var predicted = new Vector(new double[] { 1.0, 2.0, 3.0, 4.0, 5.0 }); + var actual = new Vector(new double[] { 1.1, 1.9, 3.1, 3.9, 5.1 }); + + var evaluationData = CreateMockEvaluationData(predicted, actual); + + // Act + var result = detector.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.NotNull(result.FitType); + Assert.NotEmpty(result.Recommendations); + } + + [Fact] + public void DetectFit_CalculatesConfidenceLevel() + { + // Arrange + var detector = new BayesianFitDetector, Vector>(); + var predicted = new Vector(new double[] { 1.0, 2.0, 3.0, 4.0, 5.0 }); + var actual = new Vector(new double[] { 1.0, 2.0, 3.0, 4.0, 5.0 }); + + var evaluationData = CreateMockEvaluationData(predicted, actual); + + // Act + var result = detector.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.NotNull(result.ConfidenceLevel); + Assert.True(result.ConfidenceLevel >= 0.0); + Assert.True(result.ConfidenceLevel <= 1.0); + } + + [Fact] + public void DetectFit_GeneratesRecommendations() + { + // Arrange + var detector = new BayesianFitDetector, Vector>(); + var predicted = new Vector(new double[] { 1.0, 2.0, 3.0, 4.0, 5.0 }); + var actual = new Vector(new double[] { 1.2, 1.8, 3.2, 3.8, 5.2 }); + + var evaluationData = CreateMockEvaluationData(predicted, actual); + + // Act + var result = detector.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.NotEmpty(result.Recommendations); + Assert.All(result.Recommendations, r => Assert.False(string.IsNullOrWhiteSpace(r))); + } + + [Fact] + public void DetectFit_ResultContainsAllRequiredFields() + { + // Arrange + var detector = new BayesianFitDetector, Vector>(); + var predicted = new Vector(new double[] { 1.0, 2.0, 3.0, 4.0, 5.0 }); + var actual = new Vector(new double[] { 1.0, 2.0, 3.0, 4.0, 5.0 }); + + var evaluationData = CreateMockEvaluationData(predicted, actual); + + // Act + var result = detector.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.NotNull(result.FitType); + Assert.NotNull(result.ConfidenceLevel); + Assert.NotNull(result.Recommendations); + Assert.NotEmpty(result.Recommendations); + Assert.NotNull(result.AdditionalInfo); + } + } +} diff --git a/tests/AiDotNet.Tests/UnitTests/FitDetectors/CalibratedProbabilityFitDetectorTests.cs b/tests/AiDotNet.Tests/UnitTests/FitDetectors/CalibratedProbabilityFitDetectorTests.cs new file mode 100644 index 000000000..33aa1c036 --- /dev/null +++ b/tests/AiDotNet.Tests/UnitTests/FitDetectors/CalibratedProbabilityFitDetectorTests.cs @@ -0,0 +1,350 @@ +using AiDotNet.FitDetectors; +using AiDotNet.LinearAlgebra; +using AiDotNet.Models; +using AiDotNet.Models.Options; +using Xunit; + +namespace AiDotNetTests.UnitTests.FitDetectors +{ + public class CalibratedProbabilityFitDetectorTests + { + private ModelEvaluationData, Vector> CreateMockEvaluationData( + Vector predicted, Vector actual) + { + return new ModelEvaluationData, Vector> + { + ModelStats = new ModelStats, Vector> + { + Predicted = predicted, + Actual = actual + } + }; + } + + [Fact] + public void Constructor_WithDefaultOptions_CreatesInstance() + { + // Act + var detector = new CalibratedProbabilityFitDetector, Vector>(); + + // Assert + Assert.NotNull(detector); + } + + [Fact] + public void Constructor_WithCustomOptions_CreatesInstance() + { + // Arrange + var options = new CalibratedProbabilityFitDetectorOptions + { + NumCalibrationBins = 20, + GoodFitThreshold = 0.08, + OverfitThreshold = 0.25 + }; + + // Act + var detector = new CalibratedProbabilityFitDetector, Vector>(options); + + // Assert + Assert.NotNull(detector); + } + + [Fact] + public void DetectFit_WithWellCalibratedProbabilities_ReturnsGoodFit() + { + // Arrange + var detector = new CalibratedProbabilityFitDetector, Vector>(); + // Create well-calibrated probabilities (predictions match actuals closely) + var predicted = new Vector(new double[] { 0.1, 0.3, 0.5, 0.7, 0.9, 0.2, 0.4, 0.6, 0.8, 1.0 }); + var actual = new Vector(new double[] { 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0 }); + + var evaluationData = CreateMockEvaluationData(predicted, actual); + + // Act + var result = detector.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.NotNull(result.FitType); + Assert.NotEmpty(result.Recommendations); + } + + [Fact] + public void DetectFit_WithPoorlyCalibratedProbabilities_ReturnsNonGoodFit() + { + // Arrange + var detector = new CalibratedProbabilityFitDetector, Vector>(); + // Create poorly calibrated probabilities + var predicted = new Vector(new double[] { 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9 }); + var actual = new Vector(new double[] { 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0 }); + + var evaluationData = CreateMockEvaluationData(predicted, actual); + + // Act + var result = detector.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.True(result.FitType == FitType.Overfit || result.FitType == FitType.Underfit || result.FitType == FitType.GoodFit); + Assert.NotEmpty(result.Recommendations); + } + + [Fact] + public void DetectFit_CalculatesConfidenceLevel() + { + // Arrange + var detector = new CalibratedProbabilityFitDetector, Vector>(); + var predicted = new Vector(new double[] { 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0 }); + var actual = new Vector(new double[] { 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 }); + + var evaluationData = CreateMockEvaluationData(predicted, actual); + + // Act + var result = detector.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.NotNull(result.ConfidenceLevel); + Assert.True(result.ConfidenceLevel >= 0.0); + Assert.True(result.ConfidenceLevel <= 1.0); + } + + [Fact] + public void DetectFit_GeneratesRecommendationsBasedOnFitType() + { + // Arrange + var detector = new CalibratedProbabilityFitDetector, Vector>(); + var predicted = new Vector(new double[] { 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 0.95, 0.99 }); + var actual = new Vector(new double[] { 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 }); + + var evaluationData = CreateMockEvaluationData(predicted, actual); + + // Act + var result = detector.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.NotEmpty(result.Recommendations); + Assert.All(result.Recommendations, r => Assert.False(string.IsNullOrWhiteSpace(r))); + } + + [Fact] + public void DetectFit_WithOverconfidentPredictions_ReturnsOverfitRecommendations() + { + // Arrange + var detector = new CalibratedProbabilityFitDetector, Vector>(); + // Model predicts high probabilities but actuals don't match + var predicted = new Vector(new double[] { 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95 }); + var actual = new Vector(new double[] { 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0 }); + + var evaluationData = CreateMockEvaluationData(predicted, actual); + + // Act + var result = detector.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.NotEmpty(result.Recommendations); + } + + [Fact] + public void DetectFit_WithUnderconfidentPredictions_ReturnsUnderfitRecommendations() + { + // Arrange + var detector = new CalibratedProbabilityFitDetector, Vector>(); + // Model predicts middle probabilities for clear cases + var predicted = new Vector(new double[] { 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5 }); + var actual = new Vector(new double[] { 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0 }); + + var evaluationData = CreateMockEvaluationData(predicted, actual); + + // Act + var result = detector.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.NotEmpty(result.Recommendations); + } + + [Fact] + public void DetectFit_WithCustomThresholds_UsesThresholdsCorrectly() + { + // Arrange + var options = new CalibratedProbabilityFitDetectorOptions + { + GoodFitThreshold = 0.05, + OverfitThreshold = 0.2, + MaxCalibrationError = 0.5 + }; + var detector = new CalibratedProbabilityFitDetector, Vector>(options); + var predicted = new Vector(new double[] { 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0 }); + var actual = new Vector(new double[] { 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 }); + + var evaluationData = CreateMockEvaluationData(predicted, actual); + + // Act + var result = detector.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.NotNull(result.FitType); + } + + [Fact] + public void DetectFit_WithCustomBinCount_UsesCorrectBinCount() + { + // Arrange + var options = new CalibratedProbabilityFitDetectorOptions + { + NumCalibrationBins = 5 + }; + var detector = new CalibratedProbabilityFitDetector, Vector>(options); + var predicted = new Vector(new double[] { 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0 }); + var actual = new Vector(new double[] { 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 }); + + var evaluationData = CreateMockEvaluationData(predicted, actual); + + // Act + var result = detector.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.NotNull(result.FitType); + } + + [Fact] + public void DetectFit_WithLargeDataset_HandlesCorrectly() + { + // Arrange + var detector = new CalibratedProbabilityFitDetector, Vector>(); + var predictedValues = new List(); + var actualValues = new List(); + + // Create a large dataset with reasonable calibration + for (int i = 0; i < 100; i++) + { + double prob = i / 100.0; + predictedValues.Add(prob); + actualValues.Add(prob > 0.5 ? 1.0 : 0.0); + } + + var predicted = new Vector(predictedValues.ToArray()); + var actual = new Vector(actualValues.ToArray()); + + var evaluationData = CreateMockEvaluationData(predicted, actual); + + // Act + var result = detector.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.NotNull(result.FitType); + Assert.NotEmpty(result.Recommendations); + } + + [Fact] + public void DetectFit_RecommendationsIncludeCalibrationMethods() + { + // Arrange + var detector = new CalibratedProbabilityFitDetector, Vector>(); + var predicted = new Vector(new double[] { 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9 }); + var actual = new Vector(new double[] { 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0 }); + + var evaluationData = CreateMockEvaluationData(predicted, actual); + + // Act + var result = detector.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.NotEmpty(result.Recommendations); + // Check if recommendations mention calibration techniques + var hasCalibrationAdvice = result.Recommendations.Any(r => + r.Contains("calibration") || r.Contains("Platt") || r.Contains("isotonic") || + r.Contains("regularization") || r.Contains("complexity") || r.Contains("confident")); + Assert.True(hasCalibrationAdvice, "Recommendations should include calibration-related advice"); + } + + [Fact] + public void DetectFit_ResultContainsAllRequiredFields() + { + // Arrange + var detector = new CalibratedProbabilityFitDetector, Vector>(); + var predicted = new Vector(new double[] { 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0 }); + var actual = new Vector(new double[] { 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 }); + + var evaluationData = CreateMockEvaluationData(predicted, actual); + + // Act + var result = detector.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.NotNull(result.FitType); + Assert.NotNull(result.ConfidenceLevel); + Assert.NotNull(result.Recommendations); + Assert.NotEmpty(result.Recommendations); + Assert.NotNull(result.AdditionalInfo); + } + + [Fact] + public void DetectFit_WithBinaryClassificationData_HandlesCorrectly() + { + // Arrange + var detector = new CalibratedProbabilityFitDetector, Vector>(); + // Simulate binary classification with probabilities + var predicted = new Vector(new double[] + { + 0.05, 0.15, 0.25, 0.35, 0.45, 0.55, 0.65, 0.75, 0.85, 0.95, + 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 0.99 + }); + var actual = new Vector(new double[] + { + 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, + 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 + }); + + var evaluationData = CreateMockEvaluationData(predicted, actual); + + // Act + var result = detector.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.NotNull(result.FitType); + Assert.NotNull(result.ConfidenceLevel); + } + + [Fact] + public void DetectFit_WithDifferentFitTypes_GeneratesAppropriateRecommendations() + { + // Arrange + var detector = new CalibratedProbabilityFitDetector, Vector>(); + + // Test multiple scenarios + var scenarios = new[] + { + new + { + Predicted = new double[] { 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0 }, + Actual = new double[] { 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 } + } + }; + + foreach (var scenario in scenarios) + { + var predicted = new Vector(scenario.Predicted); + var actual = new Vector(scenario.Actual); + var evaluationData = CreateMockEvaluationData(predicted, actual); + + // Act + var result = detector.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.NotEmpty(result.Recommendations); + Assert.All(result.Recommendations, r => Assert.False(string.IsNullOrWhiteSpace(r))); + } + } + } +} diff --git a/tests/AiDotNet.Tests/UnitTests/FitDetectors/EnsembleFitDetectorTests.cs b/tests/AiDotNet.Tests/UnitTests/FitDetectors/EnsembleFitDetectorTests.cs new file mode 100644 index 000000000..e0011a185 --- /dev/null +++ b/tests/AiDotNet.Tests/UnitTests/FitDetectors/EnsembleFitDetectorTests.cs @@ -0,0 +1,369 @@ +using AiDotNet.FitDetectors; +using AiDotNet.Interfaces; +using AiDotNet.LinearAlgebra; +using AiDotNet.Models; +using AiDotNet.Models.Options; +using AiDotNet.Models.Results; +using Xunit; + +namespace AiDotNetTests.UnitTests.FitDetectors +{ + public class EnsembleFitDetectorTests + { + private class MockFitDetector : IFitDetector, Vector> + { + private readonly FitDetectorResult _result; + + public MockFitDetector(FitType fitType, double confidenceLevel, List? recommendations = null) + { + _result = new FitDetectorResult + { + FitType = fitType, + ConfidenceLevel = confidenceLevel, + Recommendations = recommendations ?? new List { $"Recommendation for {fitType}" } + }; + } + + public FitDetectorResult DetectFit(ModelEvaluationData, Vector> evaluationData) + { + return _result; + } + } + + private ModelEvaluationData, Vector> CreateMockEvaluationData() + { + return new ModelEvaluationData, Vector> + { + TrainingSet = new DataSetStats, Vector> + { + ErrorStats = new ErrorStats { MSE = 0.1 } + }, + ValidationSet = new DataSetStats, Vector> + { + ErrorStats = new ErrorStats { MSE = 0.12 } + } + }; + } + + [Fact] + public void Constructor_WithNullDetectors_ThrowsArgumentNullException() + { + // Act & Assert + Assert.Throws(() => + new EnsembleFitDetector, Vector>(null!)); + } + + [Fact] + public void Constructor_WithEmptyDetectorList_ThrowsArgumentException() + { + // Arrange + var detectors = new List, Vector>>(); + + // Act & Assert + Assert.Throws(() => + new EnsembleFitDetector, Vector>(detectors)); + } + + [Fact] + public void Constructor_WithValidDetectors_CreatesInstance() + { + // Arrange + var detectors = new List, Vector>> + { + new MockFitDetector(FitType.GoodFit, 0.8) + }; + + // Act + var ensemble = new EnsembleFitDetector, Vector>(detectors); + + // Assert + Assert.NotNull(ensemble); + } + + [Fact] + public void Constructor_WithCustomOptions_CreatesInstance() + { + // Arrange + var detectors = new List, Vector>> + { + new MockFitDetector(FitType.GoodFit, 0.8) + }; + var options = new EnsembleFitDetectorOptions + { + DetectorWeights = new List { 1.0 }, + MaxRecommendations = 5 + }; + + // Act + var ensemble = new EnsembleFitDetector, Vector>(detectors, options); + + // Assert + Assert.NotNull(ensemble); + } + + [Fact] + public void DetectFit_WithNullEvaluationData_ThrowsArgumentNullException() + { + // Arrange + var detectors = new List, Vector>> + { + new MockFitDetector(FitType.GoodFit, 0.8) + }; + var ensemble = new EnsembleFitDetector, Vector>(detectors); + + // Act & Assert + Assert.Throws(() => ensemble.DetectFit(null!)); + } + + [Fact] + public void DetectFit_WithSingleDetector_ReturnsSameFitType() + { + // Arrange + var detectors = new List, Vector>> + { + new MockFitDetector(FitType.GoodFit, 0.8) + }; + var ensemble = new EnsembleFitDetector, Vector>(detectors); + var evaluationData = CreateMockEvaluationData(); + + // Act + var result = ensemble.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.Equal(FitType.GoodFit, result.FitType); + } + + [Fact] + public void DetectFit_WithMultipleDetectorsReturningGoodFit_ReturnsGoodFit() + { + // Arrange + var detectors = new List, Vector>> + { + new MockFitDetector(FitType.GoodFit, 0.8), + new MockFitDetector(FitType.GoodFit, 0.9), + new MockFitDetector(FitType.GoodFit, 0.85) + }; + var ensemble = new EnsembleFitDetector, Vector>(detectors); + var evaluationData = CreateMockEvaluationData(); + + // Act + var result = ensemble.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.Equal(FitType.GoodFit, result.FitType); + } + + [Fact] + public void DetectFit_WithMixedFitTypes_CombinesResults() + { + // Arrange + var detectors = new List, Vector>> + { + new MockFitDetector(FitType.GoodFit, 0.8), + new MockFitDetector(FitType.Moderate, 0.7), + new MockFitDetector(FitType.GoodFit, 0.9) + }; + var ensemble = new EnsembleFitDetector, Vector>(detectors); + var evaluationData = CreateMockEvaluationData(); + + // Act + var result = ensemble.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.NotNull(result.FitType); + } + + [Fact] + public void DetectFit_CalculatesWeightedConfidence() + { + // Arrange + var detectors = new List, Vector>> + { + new MockFitDetector(FitType.GoodFit, 0.8), + new MockFitDetector(FitType.GoodFit, 0.6) + }; + var options = new EnsembleFitDetectorOptions + { + DetectorWeights = new List { 1.0, 1.0 } + }; + var ensemble = new EnsembleFitDetector, Vector>(detectors, options); + var evaluationData = CreateMockEvaluationData(); + + // Act + var result = ensemble.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.NotNull(result.ConfidenceLevel); + Assert.True(result.ConfidenceLevel >= 0.0); + Assert.True(result.ConfidenceLevel <= 1.0); + } + + [Fact] + public void DetectFit_WithDifferentWeights_AffectsResult() + { + // Arrange + var detectors = new List, Vector>> + { + new MockFitDetector(FitType.GoodFit, 0.9), + new MockFitDetector(FitType.PoorFit, 0.8) + }; + var options = new EnsembleFitDetectorOptions + { + DetectorWeights = new List { 2.0, 1.0 } + }; + var ensemble = new EnsembleFitDetector, Vector>(detectors, options); + var evaluationData = CreateMockEvaluationData(); + + // Act + var result = ensemble.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.NotNull(result.FitType); + } + + [Fact] + public void DetectFit_CombinesRecommendationsFromAllDetectors() + { + // Arrange + var detectors = new List, Vector>> + { + new MockFitDetector(FitType.GoodFit, 0.8, new List { "Rec 1" }), + new MockFitDetector(FitType.GoodFit, 0.9, new List { "Rec 2" }), + new MockFitDetector(FitType.GoodFit, 0.85, new List { "Rec 3" }) + }; + var ensemble = new EnsembleFitDetector, Vector>(detectors); + var evaluationData = CreateMockEvaluationData(); + + // Act + var result = ensemble.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.NotEmpty(result.Recommendations); + Assert.True(result.Recommendations.Count >= 3); // At least 3 unique recommendations + } + + [Fact] + public void DetectFit_RemovesDuplicateRecommendations() + { + // Arrange + var detectors = new List, Vector>> + { + new MockFitDetector(FitType.GoodFit, 0.8, new List { "Same Rec" }), + new MockFitDetector(FitType.GoodFit, 0.9, new List { "Same Rec" }), + new MockFitDetector(FitType.GoodFit, 0.85, new List { "Different Rec" }) + }; + var ensemble = new EnsembleFitDetector, Vector>(detectors); + var evaluationData = CreateMockEvaluationData(); + + // Act + var result = ensemble.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.NotEmpty(result.Recommendations); + var sameRecCount = result.Recommendations.Count(r => r == "Same Rec"); + Assert.True(sameRecCount <= 1, "Duplicate recommendations should be removed"); + } + + [Fact] + public void DetectFit_RespectsMaxRecommendationsLimit() + { + // Arrange + var recommendations = new List(); + for (int i = 0; i < 20; i++) + { + recommendations.Add($"Recommendation {i}"); + } + + var detectors = new List, Vector>> + { + new MockFitDetector(FitType.GoodFit, 0.8, recommendations) + }; + + var options = new EnsembleFitDetectorOptions + { + MaxRecommendations = 5 + }; + var ensemble = new EnsembleFitDetector, Vector>(detectors, options); + var evaluationData = CreateMockEvaluationData(); + + // Act + var result = ensemble.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.True(result.Recommendations.Count <= 5); + } + + [Fact] + public void DetectFit_IncludesIndividualResultsInAdditionalInfo() + { + // Arrange + var detectors = new List, Vector>> + { + new MockFitDetector(FitType.GoodFit, 0.8), + new MockFitDetector(FitType.Moderate, 0.7) + }; + var ensemble = new EnsembleFitDetector, Vector>(detectors); + var evaluationData = CreateMockEvaluationData(); + + // Act + var result = ensemble.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.NotNull(result.AdditionalInfo); + Assert.True(result.AdditionalInfo.ContainsKey("IndividualResults")); + Assert.True(result.AdditionalInfo.ContainsKey("DetectorWeights")); + } + + [Fact] + public void DetectFit_IncludesGeneralRecommendationBasedOnFitType() + { + // Arrange + var detectors = new List, Vector>> + { + new MockFitDetector(FitType.GoodFit, 0.8, new List { "Specific rec" }) + }; + var ensemble = new EnsembleFitDetector, Vector>(detectors); + var evaluationData = CreateMockEvaluationData(); + + // Act + var result = ensemble.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.NotEmpty(result.Recommendations); + Assert.Contains(result.Recommendations, r => r.Contains("ensemble") || r.Contains("good fit")); + } + + [Fact] + public void DetectFit_ResultContainsAllRequiredFields() + { + // Arrange + var detectors = new List, Vector>> + { + new MockFitDetector(FitType.GoodFit, 0.8) + }; + var ensemble = new EnsembleFitDetector, Vector>(detectors); + var evaluationData = CreateMockEvaluationData(); + + // Act + var result = ensemble.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.NotNull(result.FitType); + Assert.NotNull(result.ConfidenceLevel); + Assert.NotNull(result.Recommendations); + Assert.NotEmpty(result.Recommendations); + Assert.NotNull(result.AdditionalInfo); + } + } +} diff --git a/tests/AiDotNet.Tests/UnitTests/FitDetectors/FeatureImportanceFitDetectorTests.cs b/tests/AiDotNet.Tests/UnitTests/FitDetectors/FeatureImportanceFitDetectorTests.cs new file mode 100644 index 000000000..28ed47968 --- /dev/null +++ b/tests/AiDotNet.Tests/UnitTests/FitDetectors/FeatureImportanceFitDetectorTests.cs @@ -0,0 +1,380 @@ +using AiDotNet.FitDetectors; +using AiDotNet.Interfaces; +using AiDotNet.LinearAlgebra; +using AiDotNet.Models; +using AiDotNet.Models.Options; +using Xunit; + +namespace AiDotNetTests.UnitTests.FitDetectors +{ + public class FeatureImportanceFitDetectorTests + { + private class MockModel : IModel, Vector> + { + private readonly Func, Vector> _predictFunc; + + public MockModel(Func, Vector> predictFunc) + { + _predictFunc = predictFunc; + } + + public Vector Predict(Matrix input) + { + return _predictFunc(input); + } + } + + private ModelEvaluationData, Vector> CreateMockEvaluationData( + Vector predicted, Vector actual, Matrix features, IModel, Vector>? model = null) + { + return new ModelEvaluationData, Vector> + { + ModelStats = new ModelStats, Vector> + { + Predicted = predicted, + Actual = actual, + Features = features, + Model = model ?? new MockModel(f => predicted) + } + }; + } + + [Fact] + public void Constructor_WithDefaultOptions_CreatesInstance() + { + // Act + var detector = new FeatureImportanceFitDetector, Vector>(); + + // Assert + Assert.NotNull(detector); + } + + [Fact] + public void Constructor_WithCustomOptions_CreatesInstance() + { + // Arrange + var options = new FeatureImportanceFitDetectorOptions + { + HighImportanceThreshold = 0.3, + LowImportanceThreshold = 0.05, + CorrelationThreshold = 0.8 + }; + + // Act + var detector = new FeatureImportanceFitDetector, Vector>(options); + + // Assert + Assert.NotNull(detector); + } + + [Fact] + public void DetectFit_WithValidData_ReturnsResult() + { + // Arrange + var detector = new FeatureImportanceFitDetector, Vector>(); + var features = new Matrix(5, 3, new double[] + { + 1.0, 2.0, 3.0, + 2.0, 3.0, 4.0, + 3.0, 4.0, 5.0, + 4.0, 5.0, 6.0, + 5.0, 6.0, 7.0 + }); + var actual = new Vector(new double[] { 6.0, 9.0, 12.0, 15.0, 18.0 }); + var predicted = new Vector(new double[] { 6.1, 8.9, 12.1, 14.9, 18.1 }); + + var evaluationData = CreateMockEvaluationData(predicted, actual, features); + + // Act + var result = detector.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.NotNull(result.FitType); + Assert.NotNull(result.ConfidenceLevel); + Assert.NotEmpty(result.Recommendations); + } + + [Fact] + public void DetectFit_CalculatesFeatureImportances() + { + // Arrange + var detector = new FeatureImportanceFitDetector, Vector>(); + var features = new Matrix(5, 2, new double[] + { + 1.0, 2.0, + 2.0, 3.0, + 3.0, 4.0, + 4.0, 5.0, + 5.0, 6.0 + }); + var actual = new Vector(new double[] { 3.0, 5.0, 7.0, 9.0, 11.0 }); + var predicted = new Vector(new double[] { 3.0, 5.0, 7.0, 9.0, 11.0 }); + + var evaluationData = CreateMockEvaluationData(predicted, actual, features); + + // Act + var result = detector.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.NotEmpty(result.Recommendations); + Assert.Contains(result.Recommendations, r => r.Contains("Feature") || r.Contains("Importance")); + } + + [Fact] + public void DetectFit_IncludesTopFeaturesInRecommendations() + { + // Arrange + var detector = new FeatureImportanceFitDetector, Vector>(); + var features = new Matrix(5, 3, new double[] + { + 1.0, 2.0, 3.0, + 2.0, 3.0, 4.0, + 3.0, 4.0, 5.0, + 4.0, 5.0, 6.0, + 5.0, 6.0, 7.0 + }); + var actual = new Vector(new double[] { 6.0, 9.0, 12.0, 15.0, 18.0 }); + var predicted = new Vector(new double[] { 6.0, 9.0, 12.0, 15.0, 18.0 }); + + var evaluationData = CreateMockEvaluationData(predicted, actual, features); + + // Act + var result = detector.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.NotEmpty(result.Recommendations); + Assert.Contains(result.Recommendations, r => r.Contains("Top 3")); + } + + [Fact] + public void DetectFit_ReturnsConfidenceLevelInValidRange() + { + // Arrange + var detector = new FeatureImportanceFitDetector, Vector>(); + var features = new Matrix(5, 2, new double[] + { + 1.0, 2.0, + 2.0, 3.0, + 3.0, 4.0, + 4.0, 5.0, + 5.0, 6.0 + }); + var actual = new Vector(new double[] { 3.0, 5.0, 7.0, 9.0, 11.0 }); + var predicted = new Vector(new double[] { 3.1, 4.9, 7.1, 8.9, 11.1 }); + + var evaluationData = CreateMockEvaluationData(predicted, actual, features); + + // Act + var result = detector.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.NotNull(result.ConfidenceLevel); + Assert.True(result.ConfidenceLevel >= 0.0, "Confidence level should be >= 0"); + } + + [Fact] + public void DetectFit_GeneratesRecommendationsBasedOnFitType() + { + // Arrange + var detector = new FeatureImportanceFitDetector, Vector>(); + var features = new Matrix(5, 2, new double[] + { + 1.0, 2.0, + 2.0, 3.0, + 3.0, 4.0, + 4.0, 5.0, + 5.0, 6.0 + }); + var actual = new Vector(new double[] { 3.0, 5.0, 7.0, 9.0, 11.0 }); + var predicted = new Vector(new double[] { 3.0, 5.0, 7.0, 9.0, 11.0 }); + + var evaluationData = CreateMockEvaluationData(predicted, actual, features); + + // Act + var result = detector.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.NotEmpty(result.Recommendations); + Assert.All(result.Recommendations, r => Assert.False(string.IsNullOrWhiteSpace(r))); + } + + [Fact] + public void DetectFit_WithCustomThresholds_UsesThresholdsCorrectly() + { + // Arrange + var options = new FeatureImportanceFitDetectorOptions + { + HighImportanceThreshold = 0.2, + LowImportanceThreshold = 0.03, + LowVarianceThreshold = 0.05, + HighVarianceThreshold = 0.15 + }; + var detector = new FeatureImportanceFitDetector, Vector>(options); + var features = new Matrix(5, 2, new double[] + { + 1.0, 2.0, + 2.0, 3.0, + 3.0, 4.0, + 4.0, 5.0, + 5.0, 6.0 + }); + var actual = new Vector(new double[] { 3.0, 5.0, 7.0, 9.0, 11.0 }); + var predicted = new Vector(new double[] { 3.0, 5.0, 7.0, 9.0, 11.0 }); + + var evaluationData = CreateMockEvaluationData(predicted, actual, features); + + // Act + var result = detector.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.NotNull(result.FitType); + } + + [Fact] + public void DetectFit_WithHighDimensionalFeatures_HandlesCorrectly() + { + // Arrange + var detector = new FeatureImportanceFitDetector, Vector>(); + var features = new Matrix(6, 5, new double[] + { + 1.0, 2.0, 3.0, 4.0, 5.0, + 2.0, 3.0, 4.0, 5.0, 6.0, + 3.0, 4.0, 5.0, 6.0, 7.0, + 4.0, 5.0, 6.0, 7.0, 8.0, + 5.0, 6.0, 7.0, 8.0, 9.0, + 6.0, 7.0, 8.0, 9.0, 10.0 + }); + var actual = new Vector(new double[] { 15.0, 20.0, 25.0, 30.0, 35.0, 40.0 }); + var predicted = new Vector(new double[] { 15.1, 19.9, 25.1, 29.9, 35.1, 39.9 }); + + var evaluationData = CreateMockEvaluationData(predicted, actual, features); + + // Act + var result = detector.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.NotNull(result.FitType); + Assert.NotEmpty(result.Recommendations); + } + + [Fact] + public void DetectFit_WithCorrelatedFeatures_DetectsAppropriately() + { + // Arrange + var detector = new FeatureImportanceFitDetector, Vector>(); + // Create highly correlated features + var features = new Matrix(5, 2, new double[] + { + 1.0, 1.1, + 2.0, 2.1, + 3.0, 3.1, + 4.0, 4.1, + 5.0, 5.1 + }); + var actual = new Vector(new double[] { 2.1, 4.1, 6.1, 8.1, 10.1 }); + var predicted = new Vector(new double[] { 2.0, 4.0, 6.0, 8.0, 10.0 }); + + var evaluationData = CreateMockEvaluationData(predicted, actual, features); + + // Act + var result = detector.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.NotNull(result.FitType); + } + + [Fact] + public void DetectFit_ResultContainsAllRequiredFields() + { + // Arrange + var detector = new FeatureImportanceFitDetector, Vector>(); + var features = new Matrix(5, 2, new double[] + { + 1.0, 2.0, + 2.0, 3.0, + 3.0, 4.0, + 4.0, 5.0, + 5.0, 6.0 + }); + var actual = new Vector(new double[] { 3.0, 5.0, 7.0, 9.0, 11.0 }); + var predicted = new Vector(new double[] { 3.1, 4.9, 7.1, 8.9, 11.1 }); + + var evaluationData = CreateMockEvaluationData(predicted, actual, features); + + // Act + var result = detector.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.NotNull(result.FitType); + Assert.NotNull(result.ConfidenceLevel); + Assert.NotNull(result.Recommendations); + Assert.NotEmpty(result.Recommendations); + Assert.NotNull(result.AdditionalInfo); + } + + [Fact] + public void DetectFit_WithUncorrelatedFeatures_DetectsAppropriately() + { + // Arrange + var detector = new FeatureImportanceFitDetector, Vector>(); + // Create uncorrelated features + var features = new Matrix(5, 2, new double[] + { + 1.0, 5.0, + 2.0, 3.0, + 3.0, 1.0, + 4.0, 4.0, + 5.0, 2.0 + }); + var actual = new Vector(new double[] { 6.0, 5.0, 4.0, 8.0, 7.0 }); + var predicted = new Vector(new double[] { 6.1, 4.9, 4.1, 7.9, 7.1 }); + + var evaluationData = CreateMockEvaluationData(predicted, actual, features); + + // Act + var result = detector.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.NotNull(result.FitType); + } + + [Fact] + public void DetectFit_WithVariousFitTypes_GeneratesAppropriateRecommendations() + { + // Arrange + var detector = new FeatureImportanceFitDetector, Vector>(); + var features = new Matrix(5, 2, new double[] + { + 1.0, 2.0, + 2.0, 3.0, + 3.0, 4.0, + 4.0, 5.0, + 5.0, 6.0 + }); + var actual = new Vector(new double[] { 3.0, 5.0, 7.0, 9.0, 11.0 }); + var predicted = new Vector(new double[] { 10.0, 15.0, 20.0, 25.0, 30.0 }); + + var evaluationData = CreateMockEvaluationData(predicted, actual, features); + + // Act + var result = detector.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.NotEmpty(result.Recommendations); + // Recommendations should provide actionable advice + Assert.True(result.Recommendations.Any(r => + r.Contains("complex") || r.Contains("features") || r.Contains("regularization") || r.Contains("fit"))); + } + } +} diff --git a/tests/AiDotNet.Tests/UnitTests/FitDetectors/GaussianProcessFitDetectorTests.cs b/tests/AiDotNet.Tests/UnitTests/FitDetectors/GaussianProcessFitDetectorTests.cs new file mode 100644 index 000000000..50f3db75a --- /dev/null +++ b/tests/AiDotNet.Tests/UnitTests/FitDetectors/GaussianProcessFitDetectorTests.cs @@ -0,0 +1,320 @@ +using AiDotNet.FitDetectors; +using AiDotNet.Interfaces; +using AiDotNet.LinearAlgebra; +using AiDotNet.Models; +using AiDotNet.Models.Options; +using Xunit; + +namespace AiDotNetTests.UnitTests.FitDetectors +{ + public class GaussianProcessFitDetectorTests + { + private class MockModel : IModel, Vector> + { + private readonly Func, Vector> _predictFunc; + + public MockModel(Func, Vector> predictFunc) + { + _predictFunc = predictFunc; + } + + public Vector Predict(Matrix input) + { + return _predictFunc(input); + } + } + + private ModelEvaluationData, Vector> CreateMockEvaluationData( + Vector predicted, Vector actual, Matrix features) + { + return new ModelEvaluationData, Vector> + { + ModelStats = new ModelStats, Vector> + { + Predicted = predicted, + Actual = actual, + Features = features, + Model = new MockModel(f => predicted) + } + }; + } + + [Fact] + public void Constructor_WithDefaultOptions_CreatesInstance() + { + // Act + var detector = new GaussianProcessFitDetector, Vector>(); + + // Assert + Assert.NotNull(detector); + } + + [Fact] + public void Constructor_WithCustomOptions_CreatesInstance() + { + // Arrange + var options = new GaussianProcessFitDetectorOptions + { + GoodFitThreshold = 0.15, + OverfitThreshold = 0.35, + UnderfitThreshold = 0.5 + }; + + // Act + var detector = new GaussianProcessFitDetector, Vector>(options); + + // Assert + Assert.NotNull(detector); + } + + [Fact] + public void DetectFit_WithPerfectPredictions_ReturnsGoodFit() + { + // Arrange + var detector = new GaussianProcessFitDetector, Vector>(); + var features = new Matrix(5, 2, new double[] + { + 1.0, 2.0, + 2.0, 3.0, + 3.0, 4.0, + 4.0, 5.0, + 5.0, 6.0 + }); + var actual = new Vector(new double[] { 3.0, 5.0, 7.0, 9.0, 11.0 }); + var predicted = new Vector(new double[] { 3.0, 5.0, 7.0, 9.0, 11.0 }); + + var evaluationData = CreateMockEvaluationData(predicted, actual, features); + + // Act + var result = detector.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.Equal(FitType.GoodFit, result.FitType); + Assert.NotNull(result.ConfidenceLevel); + Assert.True(result.ConfidenceLevel > 0.0); + Assert.NotEmpty(result.Recommendations); + } + + [Fact] + public void DetectFit_WithPoorPredictions_ReturnsNonGoodFit() + { + // Arrange + var detector = new GaussianProcessFitDetector, Vector>(); + var features = new Matrix(5, 2, new double[] + { + 1.0, 2.0, + 2.0, 3.0, + 3.0, 4.0, + 4.0, 5.0, + 5.0, 6.0 + }); + var actual = new Vector(new double[] { 3.0, 5.0, 7.0, 9.0, 11.0 }); + var predicted = new Vector(new double[] { 10.0, 15.0, 20.0, 25.0, 30.0 }); + + var evaluationData = CreateMockEvaluationData(predicted, actual, features); + + // Act + var result = detector.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.NotEqual(FitType.GoodFit, result.FitType); + Assert.NotNull(result.ConfidenceLevel); + Assert.NotEmpty(result.Recommendations); + } + + [Fact] + public void DetectFit_WithReasonablePredictions_ReturnsConfidenceLevel() + { + // Arrange + var detector = new GaussianProcessFitDetector, Vector>(); + var features = new Matrix(6, 2, new double[] + { + 1.0, 2.0, + 2.0, 3.0, + 3.0, 4.0, + 4.0, 5.0, + 5.0, 6.0, + 6.0, 7.0 + }); + var actual = new Vector(new double[] { 3.0, 5.0, 7.0, 9.0, 11.0, 13.0 }); + var predicted = new Vector(new double[] { 3.1, 4.9, 7.1, 8.9, 11.1, 12.9 }); + + var evaluationData = CreateMockEvaluationData(predicted, actual, features); + + // Act + var result = detector.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.NotNull(result.ConfidenceLevel); + Assert.True(result.ConfidenceLevel >= 0.0); + Assert.True(result.ConfidenceLevel <= 1.0); + } + + [Fact] + public void DetectFit_GeneratesRecommendationsBasedOnFitType() + { + // Arrange + var detector = new GaussianProcessFitDetector, Vector>(); + var features = new Matrix(5, 2, new double[] + { + 1.0, 2.0, + 2.0, 3.0, + 3.0, 4.0, + 4.0, 5.0, + 5.0, 6.0 + }); + var actual = new Vector(new double[] { 3.0, 5.0, 7.0, 9.0, 11.0 }); + var predicted = new Vector(new double[] { 3.0, 5.0, 7.0, 9.0, 11.0 }); + + var evaluationData = CreateMockEvaluationData(predicted, actual, features); + + // Act + var result = detector.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.NotEmpty(result.Recommendations); + Assert.All(result.Recommendations, r => Assert.False(string.IsNullOrWhiteSpace(r))); + } + + [Fact] + public void DetectFit_WithVariousDataSizes_HandlesCorrectly() + { + // Arrange + var detector = new GaussianProcessFitDetector, Vector>(); + + // Small dataset + var featuresSmall = new Matrix(3, 2, new double[] { 1.0, 2.0, 2.0, 3.0, 3.0, 4.0 }); + var actualSmall = new Vector(new double[] { 3.0, 5.0, 7.0 }); + var predictedSmall = new Vector(new double[] { 3.1, 4.9, 7.1 }); + var evaluationDataSmall = CreateMockEvaluationData(predictedSmall, actualSmall, featuresSmall); + + // Act + var resultSmall = detector.DetectFit(evaluationDataSmall); + + // Assert + Assert.NotNull(resultSmall); + Assert.NotNull(resultSmall.FitType); + } + + [Fact] + public void DetectFit_WithHighDimensionalFeatures_HandlesCorrectly() + { + // Arrange + var detector = new GaussianProcessFitDetector, Vector>(); + var features = new Matrix(4, 4, new double[] + { + 1.0, 2.0, 3.0, 4.0, + 2.0, 3.0, 4.0, 5.0, + 3.0, 4.0, 5.0, 6.0, + 4.0, 5.0, 6.0, 7.0 + }); + var actual = new Vector(new double[] { 10.0, 14.0, 18.0, 22.0 }); + var predicted = new Vector(new double[] { 10.1, 13.9, 18.1, 21.9 }); + + var evaluationData = CreateMockEvaluationData(predicted, actual, features); + + // Act + var result = detector.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.NotNull(result.FitType); + Assert.NotEmpty(result.Recommendations); + } + + [Fact] + public void DetectFit_WithCustomThresholds_UsesThresholdsCorrectly() + { + // Arrange + var options = new GaussianProcessFitDetectorOptions + { + GoodFitThreshold = 0.05, + OverfitThreshold = 0.2, + UnderfitThreshold = 0.4, + LowUncertaintyThreshold = 0.15, + HighUncertaintyThreshold = 0.3 + }; + var detector = new GaussianProcessFitDetector, Vector>(options); + var features = new Matrix(5, 2, new double[] + { + 1.0, 2.0, + 2.0, 3.0, + 3.0, 4.0, + 4.0, 5.0, + 5.0, 6.0 + }); + var actual = new Vector(new double[] { 3.0, 5.0, 7.0, 9.0, 11.0 }); + var predicted = new Vector(new double[] { 3.0, 5.0, 7.0, 9.0, 11.0 }); + + var evaluationData = CreateMockEvaluationData(predicted, actual, features); + + // Act + var result = detector.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.NotNull(result.FitType); + } + + [Fact] + public void DetectFit_ResultContainsAllRequiredFields() + { + // Arrange + var detector = new GaussianProcessFitDetector, Vector>(); + var features = new Matrix(5, 2, new double[] + { + 1.0, 2.0, + 2.0, 3.0, + 3.0, 4.0, + 4.0, 5.0, + 5.0, 6.0 + }); + var actual = new Vector(new double[] { 3.0, 5.0, 7.0, 9.0, 11.0 }); + var predicted = new Vector(new double[] { 3.1, 4.9, 7.1, 8.9, 11.1 }); + + var evaluationData = CreateMockEvaluationData(predicted, actual, features); + + // Act + var result = detector.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.NotNull(result.FitType); + Assert.NotNull(result.ConfidenceLevel); + Assert.NotNull(result.Recommendations); + Assert.NotNull(result.AdditionalInfo); + } + + [Fact] + public void DetectFit_WithSlightlyOffPredictions_ReturnsModerateConfidence() + { + // Arrange + var detector = new GaussianProcessFitDetector, Vector>(); + var features = new Matrix(5, 2, new double[] + { + 1.0, 2.0, + 2.0, 3.0, + 3.0, 4.0, + 4.0, 5.0, + 5.0, 6.0 + }); + var actual = new Vector(new double[] { 3.0, 5.0, 7.0, 9.0, 11.0 }); + var predicted = new Vector(new double[] { 3.2, 5.1, 6.9, 9.1, 10.8 }); + + var evaluationData = CreateMockEvaluationData(predicted, actual, features); + + // Act + var result = detector.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.NotNull(result.ConfidenceLevel); + Assert.True(result.ConfidenceLevel >= 0.0); + Assert.True(result.ConfidenceLevel <= 1.0); + } + } +} diff --git a/tests/AiDotNet.Tests/UnitTests/FitDetectors/GradientBoostingFitDetectorTests.cs b/tests/AiDotNet.Tests/UnitTests/FitDetectors/GradientBoostingFitDetectorTests.cs new file mode 100644 index 000000000..5a2a24a13 --- /dev/null +++ b/tests/AiDotNet.Tests/UnitTests/FitDetectors/GradientBoostingFitDetectorTests.cs @@ -0,0 +1,273 @@ +using AiDotNet.FitDetectors; +using AiDotNet.LinearAlgebra; +using AiDotNet.Models; +using AiDotNet.Models.Options; +using Xunit; + +namespace AiDotNetTests.UnitTests.FitDetectors +{ + public class GradientBoostingFitDetectorTests + { + private ModelEvaluationData, Vector> CreateMockEvaluationData( + double trainMSE, double validationMSE, double trainR2 = 0.9, double validationR2 = 0.85) + { + return new ModelEvaluationData, Vector> + { + TrainingSet = new DataSetStats, Vector> + { + ErrorStats = new ErrorStats { MSE = trainMSE }, + PredictionStats = new PredictionStats { R2 = trainR2 } + }, + ValidationSet = new DataSetStats, Vector> + { + ErrorStats = new ErrorStats { MSE = validationMSE }, + PredictionStats = new PredictionStats { R2 = validationR2 } + } + }; + } + + [Fact] + public void Constructor_WithDefaultOptions_CreatesInstance() + { + // Act + var detector = new GradientBoostingFitDetector, Vector>(); + + // Assert + Assert.NotNull(detector); + } + + [Fact] + public void Constructor_WithCustomOptions_CreatesInstance() + { + // Arrange + var options = new GradientBoostingFitDetectorOptions + { + GoodFitThreshold = 0.05, + OverfitThreshold = 0.15, + SevereOverfitThreshold = 0.3 + }; + + // Act + var detector = new GradientBoostingFitDetector, Vector>(options); + + // Assert + Assert.NotNull(detector); + } + + [Fact] + public void DetectFit_WithNullEvaluationData_ThrowsArgumentNullException() + { + // Arrange + var detector = new GradientBoostingFitDetector, Vector>(); + + // Act & Assert + Assert.Throws(() => detector.DetectFit(null!)); + } + + [Fact] + public void DetectFit_WithSimilarTrainAndValidationErrors_ReturnsGoodFit() + { + // Arrange + var detector = new GradientBoostingFitDetector, Vector>(); + var evaluationData = CreateMockEvaluationData(trainMSE: 0.05, validationMSE: 0.06); + + // Act + var result = detector.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.Equal(FitType.GoodFit, result.FitType); + Assert.NotEmpty(result.Recommendations); + } + + [Fact] + public void DetectFit_WithLargeValidationError_ReturnsOverfit() + { + // Arrange + var detector = new GradientBoostingFitDetector, Vector>(); + var evaluationData = CreateMockEvaluationData(trainMSE: 0.05, validationMSE: 0.5); + + // Act + var result = detector.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.True(result.FitType == FitType.PoorFit || result.FitType == FitType.VeryPoorFit); + Assert.NotEmpty(result.Recommendations); + } + + [Fact] + public void DetectFit_WithModerateErrorDifference_ReturnsModerateFit() + { + // Arrange + var detector = new GradientBoostingFitDetector, Vector>(); + var evaluationData = CreateMockEvaluationData(trainMSE: 0.1, validationMSE: 0.15); + + // Act + var result = detector.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.True(result.FitType == FitType.Moderate || result.FitType == FitType.GoodFit); + Assert.NotEmpty(result.Recommendations); + } + + [Fact] + public void DetectFit_CalculatesConfidenceLevel() + { + // Arrange + var detector = new GradientBoostingFitDetector, Vector>(); + var evaluationData = CreateMockEvaluationData(trainMSE: 0.1, validationMSE: 0.12); + + // Act + var result = detector.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.NotNull(result.ConfidenceLevel); + Assert.True(result.ConfidenceLevel >= 0.0); + Assert.True(result.ConfidenceLevel <= 1.0); + } + + [Fact] + public void DetectFit_WithVeryLowTrainingError_IncludesDataLeakageWarning() + { + // Arrange + var detector = new GradientBoostingFitDetector, Vector>(); + var evaluationData = CreateMockEvaluationData(trainMSE: 0.005, validationMSE: 0.15); + + // Act + var result = detector.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.NotEmpty(result.Recommendations); + Assert.Contains(result.Recommendations, r => r.Contains("data leakage") || r.Contains("suspiciously low")); + } + + [Fact] + public void DetectFit_IncludesPerformanceMetricsInAdditionalInfo() + { + // Arrange + var detector = new GradientBoostingFitDetector, Vector>(); + var evaluationData = CreateMockEvaluationData(trainMSE: 0.1, validationMSE: 0.12, trainR2: 0.9, validationR2: 0.85); + + // Act + var result = detector.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.NotNull(result.AdditionalInfo); + Assert.True(result.AdditionalInfo.ContainsKey("PerformanceMetrics")); + } + + [Fact] + public void DetectFit_WithDifferentThresholds_ChangesClassification() + { + // Arrange + var options = new GradientBoostingFitDetectorOptions + { + GoodFitThreshold = 0.01, + OverfitThreshold = 0.05, + SevereOverfitThreshold = 0.1 + }; + var detector = new GradientBoostingFitDetector, Vector>(options); + var evaluationData = CreateMockEvaluationData(trainMSE: 0.05, validationMSE: 0.08); + + // Act + var result = detector.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.NotNull(result.FitType); + } + + [Fact] + public void DetectFit_GeneratesAppropriateRecommendationsForGoodFit() + { + // Arrange + var detector = new GradientBoostingFitDetector, Vector>(); + var evaluationData = CreateMockEvaluationData(trainMSE: 0.05, validationMSE: 0.06); + + // Act + var result = detector.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + if (result.FitType == FitType.GoodFit) + { + Assert.Contains(result.Recommendations, r => r.Contains("good fit") || r.Contains("fine-tuning")); + } + } + + [Fact] + public void DetectFit_GeneratesAppropriateRecommendationsForPoorFit() + { + // Arrange + var detector = new GradientBoostingFitDetector, Vector>(); + var evaluationData = CreateMockEvaluationData(trainMSE: 0.05, validationMSE: 0.4); + + // Act + var result = detector.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + if (result.FitType == FitType.PoorFit || result.FitType == FitType.VeryPoorFit) + { + Assert.Contains(result.Recommendations, r => + r.Contains("overfit") || r.Contains("regularization") || r.Contains("complexity")); + } + } + + [Fact] + public void DetectFit_WithHighConfidence_ReturnsHighConfidenceValue() + { + // Arrange + var detector = new GradientBoostingFitDetector, Vector>(); + var evaluationData = CreateMockEvaluationData(trainMSE: 0.1, validationMSE: 0.1); + + // Act + var result = detector.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.NotNull(result.ConfidenceLevel); + Assert.True(result.ConfidenceLevel > 0.5, "Confidence should be high when errors are similar"); + } + + [Fact] + public void DetectFit_WithLowConfidence_ReturnsLowConfidenceValue() + { + // Arrange + var detector = new GradientBoostingFitDetector, Vector>(); + var evaluationData = CreateMockEvaluationData(trainMSE: 0.1, validationMSE: 0.5); + + // Act + var result = detector.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.NotNull(result.ConfidenceLevel); + Assert.True(result.ConfidenceLevel >= 0.0 && result.ConfidenceLevel <= 1.0); + } + + [Fact] + public void DetectFit_ResultContainsAllRequiredFields() + { + // Arrange + var detector = new GradientBoostingFitDetector, Vector>(); + var evaluationData = CreateMockEvaluationData(trainMSE: 0.1, validationMSE: 0.12); + + // Act + var result = detector.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.NotNull(result.FitType); + Assert.NotNull(result.ConfidenceLevel); + Assert.NotNull(result.Recommendations); + Assert.NotEmpty(result.Recommendations); + Assert.NotNull(result.AdditionalInfo); + } + } +} diff --git a/tests/AiDotNet.Tests/UnitTests/FitDetectors/NeuralNetworkFitDetectorTests.cs b/tests/AiDotNet.Tests/UnitTests/FitDetectors/NeuralNetworkFitDetectorTests.cs new file mode 100644 index 000000000..fea44a411 --- /dev/null +++ b/tests/AiDotNet.Tests/UnitTests/FitDetectors/NeuralNetworkFitDetectorTests.cs @@ -0,0 +1,160 @@ +using AiDotNet.FitDetectors; +using AiDotNet.LinearAlgebra; +using AiDotNet.Models; +using AiDotNet.Models.Options; +using Xunit; + +namespace AiDotNetTests.UnitTests.FitDetectors +{ + public class NeuralNetworkFitDetectorTests + { + private ModelEvaluationData, Vector> CreateMockEvaluationData( + double trainLoss, double validationLoss, double trainAccuracy = 0.9, double validationAccuracy = 0.85) + { + return new ModelEvaluationData, Vector> + { + TrainingSet = new DataSetStats, Vector> + { + ErrorStats = new ErrorStats { Loss = trainLoss }, + PredictionStats = new PredictionStats { Accuracy = trainAccuracy } + }, + ValidationSet = new DataSetStats, Vector> + { + ErrorStats = new ErrorStats { Loss = validationLoss }, + PredictionStats = new PredictionStats { Accuracy = validationAccuracy } + } + }; + } + + [Fact] + public void Constructor_WithDefaultOptions_CreatesInstance() + { + // Act + var detector = new NeuralNetworkFitDetector, Vector>(); + + // Assert + Assert.NotNull(detector); + } + + [Fact] + public void Constructor_WithCustomOptions_CreatesInstance() + { + // Arrange + var options = new NeuralNetworkFitDetectorOptions + { + LossDifferenceThreshold = 0.15, + AccuracyDifferenceThreshold = 0.1 + }; + + // Act + var detector = new NeuralNetworkFitDetector, Vector>(options); + + // Assert + Assert.NotNull(detector); + } + + [Fact] + public void DetectFit_WithSimilarTrainAndValidationLoss_ReturnsGoodFit() + { + // Arrange + var detector = new NeuralNetworkFitDetector, Vector>(); + var evaluationData = CreateMockEvaluationData(trainLoss: 0.2, validationLoss: 0.22); + + // Act + var result = detector.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.NotNull(result.FitType); + Assert.NotEmpty(result.Recommendations); + } + + [Fact] + public void DetectFit_WithLargeValidationLoss_IndicatesOverfitting() + { + // Arrange + var detector = new NeuralNetworkFitDetector, Vector>(); + var evaluationData = CreateMockEvaluationData(trainLoss: 0.1, validationLoss: 0.8); + + // Act + var result = detector.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.NotNull(result.FitType); + Assert.NotEmpty(result.Recommendations); + } + + [Fact] + public void DetectFit_CalculatesConfidenceLevel() + { + // Arrange + var detector = new NeuralNetworkFitDetector, Vector>(); + var evaluationData = CreateMockEvaluationData(trainLoss: 0.2, validationLoss: 0.25); + + // Act + var result = detector.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.NotNull(result.ConfidenceLevel); + Assert.True(result.ConfidenceLevel >= 0.0); + Assert.True(result.ConfidenceLevel <= 1.0); + } + + [Fact] + public void DetectFit_GeneratesRecommendationsBasedOnFitType() + { + // Arrange + var detector = new NeuralNetworkFitDetector, Vector>(); + var evaluationData = CreateMockEvaluationData(trainLoss: 0.15, validationLoss: 0.18); + + // Act + var result = detector.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.NotEmpty(result.Recommendations); + Assert.All(result.Recommendations, r => Assert.False(string.IsNullOrWhiteSpace(r))); + } + + [Fact] + public void DetectFit_ResultContainsAllRequiredFields() + { + // Arrange + var detector = new NeuralNetworkFitDetector, Vector>(); + var evaluationData = CreateMockEvaluationData(trainLoss: 0.2, validationLoss: 0.22); + + // Act + var result = detector.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.NotNull(result.FitType); + Assert.NotNull(result.ConfidenceLevel); + Assert.NotNull(result.Recommendations); + Assert.NotEmpty(result.Recommendations); + Assert.NotNull(result.AdditionalInfo); + } + + [Fact] + public void DetectFit_WithCustomThresholds_UsesThresholdsCorrectly() + { + // Arrange + var options = new NeuralNetworkFitDetectorOptions + { + LossDifferenceThreshold = 0.05, + AccuracyDifferenceThreshold = 0.05 + }; + var detector = new NeuralNetworkFitDetector, Vector>(options); + var evaluationData = CreateMockEvaluationData(trainLoss: 0.1, validationLoss: 0.12); + + // Act + var result = detector.DetectFit(evaluationData); + + // Assert + Assert.NotNull(result); + Assert.NotNull(result.FitType); + } + } +}