diff --git a/tests/AiDotNet.Tests/UnitTests/FitnessCalculators/ContrastiveLossFitnessCalculatorTests.cs b/tests/AiDotNet.Tests/UnitTests/FitnessCalculators/ContrastiveLossFitnessCalculatorTests.cs
new file mode 100644
index 000000000..a3f57fce3
--- /dev/null
+++ b/tests/AiDotNet.Tests/UnitTests/FitnessCalculators/ContrastiveLossFitnessCalculatorTests.cs
@@ -0,0 +1,422 @@
+using System;
+using AiDotNet.FitnessCalculators;
+using AiDotNet.Models;
+using AiDotNet.LinearAlgebra;
+using Xunit;
+
+namespace AiDotNetTests.UnitTests.FitnessCalculators
+{
+ ///
+ /// Unit tests for ContrastiveLossFitnessCalculator, which evaluates model performance for similarity learning tasks.
+ ///
+ public class ContrastiveLossFitnessCalculatorTests
+ {
+ [Fact]
+ public void Constructor_WithDefaultParameters_UsesDefaultMargin()
+ {
+ // Arrange & Act
+ var calculator = new ContrastiveLossFitnessCalculator, Vector>();
+
+ // Assert
+ Assert.False(calculator.IsHigherScoreBetter); // Contrastive loss: lower is better
+ }
+
+ [Fact]
+ public void Constructor_WithCustomMargin_UsesSpecifiedMargin()
+ {
+ // Arrange & Act
+ var calculator = new ContrastiveLossFitnessCalculator, Vector>(
+ margin: 2.0, dataSetType: DataSetType.Validation);
+
+ // Assert
+ Assert.False(calculator.IsHigherScoreBetter);
+ }
+
+ [Fact]
+ public void Constructor_WithTrainingDataSetType_UsesTraining()
+ {
+ // Arrange & Act
+ var calculator = new ContrastiveLossFitnessCalculator, Vector>(
+ dataSetType: DataSetType.Training);
+
+ // Assert
+ Assert.False(calculator.IsHigherScoreBetter);
+ }
+
+ [Fact]
+ public void CalculateFitnessScore_WithIdenticalSimilarPairs_ReturnsZero()
+ {
+ // Arrange
+ var calculator = new ContrastiveLossFitnessCalculator, Vector>();
+ var dataSet = new DataSetStats, Vector>
+ {
+ // First half and second half are identical, actual values are same (similarity = 1)
+ Predicted = new Vector(new double[] { 1.0, 2.0, 1.0, 2.0 }),
+ Actual = new Vector(new double[] { 1.0, 1.0, 1.0, 1.0 })
+ };
+
+ // Act
+ var result = calculator.CalculateFitnessScore(dataSet);
+
+ // Assert
+ // For similar pairs (label=1) with identical embeddings: loss = distance² = 0
+ Assert.Equal(0.0, result, 10);
+ }
+
+ [Fact]
+ public void CalculateFitnessScore_WithDissimilarPairsBeyondMargin_ReturnsZero()
+ {
+ // Arrange
+ var calculator = new ContrastiveLossFitnessCalculator, Vector>(margin: 1.0);
+ var dataSet = new DataSetStats, Vector>
+ {
+ // Pairs that are different (label=0) and far apart (beyond margin)
+ Predicted = new Vector(new double[] { 0.0, 5.0 }),
+ Actual = new Vector(new double[] { 1.0, 2.0 })
+ };
+
+ // Act
+ var result = calculator.CalculateFitnessScore(dataSet);
+
+ // Assert
+ // For dissimilar pairs beyond margin: loss = max(0, margin - distance)² = 0
+ // Distance between [0] and [5] is 5, which is > margin (1.0)
+ Assert.Equal(0.0, result, 10);
+ }
+
+ [Fact]
+ public void CalculateFitnessScore_WithSimilarPairsAtDistance_ReturnsCorrectValue()
+ {
+ // Arrange
+ var calculator = new ContrastiveLossFitnessCalculator, Vector>();
+ var dataSet = new DataSetStats, Vector>
+ {
+ // Similar pairs (label=1) with some distance
+ Predicted = new Vector(new double[] { 1.0, 4.0 }),
+ Actual = new Vector(new double[] { 1.0, 1.0 })
+ };
+
+ // Act
+ var result = calculator.CalculateFitnessScore(dataSet);
+
+ // Assert
+ // Distance between [1.0] and [4.0] = 3.0
+ // For similar pairs: loss = distance² = 9.0
+ Assert.Equal(9.0, result, 10);
+ }
+
+ [Fact]
+ public void CalculateFitnessScore_WithDissimilarPairsWithinMargin_ReturnsCorrectValue()
+ {
+ // Arrange
+ var calculator = new ContrastiveLossFitnessCalculator, Vector>(margin: 2.0);
+ var dataSet = new DataSetStats, Vector>
+ {
+ // Dissimilar pairs (label=0) within margin
+ Predicted = new Vector(new double[] { 1.0, 1.5 }),
+ Actual = new Vector(new double[] { 2.0, 3.0 })
+ };
+
+ // Act
+ var result = calculator.CalculateFitnessScore(dataSet);
+
+ // Assert
+ // Distance = 0.5, margin = 2.0
+ // For dissimilar pairs: loss = max(0, 2.0 - 0.5)² = 1.5² = 2.25
+ Assert.Equal(2.25, result, 10);
+ }
+
+ [Fact]
+ public void CalculateFitnessScore_WithMultiplePairs_ReturnsAverageLoss()
+ {
+ // Arrange
+ var calculator = new ContrastiveLossFitnessCalculator, Vector>(margin: 1.0);
+ var dataSet = new DataSetStats, Vector>
+ {
+ // Two pairs: first similar, second dissimilar
+ Predicted = new Vector(new double[] { 1.0, 2.0, 3.0, 4.0 }),
+ Actual = new Vector(new double[] { 1.0, 2.0, 5.0, 6.0 })
+ };
+
+ // Act
+ var result = calculator.CalculateFitnessScore(dataSet);
+
+ // Assert
+ // Pair 1: [1.0, 2.0] vs [3.0, 4.0], actuals same (1.0, 2.0) so similar
+ // Distance = sqrt((1-3)² + (2-4)²) = sqrt(8) ≈ 2.828
+ // Loss for pair 1 = 2.828² ≈ 8.0
+
+ // Pair 2: [3.0, 4.0] vs [5.0, 6.0], actuals different so dissimilar
+ // Distance = sqrt((3-5)² + (4-6)²) = sqrt(8) ≈ 2.828
+ // Loss for pair 2 = max(0, 1.0 - 2.828)² = 0
+
+ // Average loss ≈ 4.0
+ Assert.True(result >= 3.9 && result <= 4.1);
+ }
+
+ [Fact]
+ public void CalculateFitnessScore_WithFloatType_WorksCorrectly()
+ {
+ // Arrange
+ var calculator = new ContrastiveLossFitnessCalculator, Vector>(margin: 1.0f);
+ var dataSet = new DataSetStats, Vector>
+ {
+ Predicted = new Vector(new float[] { 0.0f, 3.0f }),
+ Actual = new Vector(new float[] { 1.0f, 1.0f })
+ };
+
+ // Act
+ var result = calculator.CalculateFitnessScore(dataSet);
+
+ // Assert
+ // Similar pair, distance = 3.0, loss = 9.0
+ Assert.Equal(9.0f, result, 5);
+ }
+
+ [Fact]
+ public void CalculateFitnessScore_WithNullDataSet_ThrowsArgumentNullException()
+ {
+ // Arrange
+ var calculator = new ContrastiveLossFitnessCalculator, Vector>();
+
+ // Act & Assert
+ Assert.Throws(() =>
+ calculator.CalculateFitnessScore((DataSetStats, Vector>)null));
+ }
+
+ [Fact]
+ public void CalculateFitnessScore_WithModelEvaluationData_UsesValidationSet()
+ {
+ // Arrange
+ var calculator = new ContrastiveLossFitnessCalculator, Vector>(
+ dataSetType: DataSetType.Validation);
+ var evaluationData = new ModelEvaluationData, Vector>
+ {
+ ValidationSet = new DataSetStats, Vector>
+ {
+ Predicted = new Vector(new double[] { 1.0, 1.0 }),
+ Actual = new Vector(new double[] { 1.0, 1.0 })
+ }
+ };
+
+ // Act
+ var result = calculator.CalculateFitnessScore(evaluationData);
+
+ // Assert
+ Assert.Equal(0.0, result, 10); // Identical pairs
+ }
+
+ [Fact]
+ public void CalculateFitnessScore_WithModelEvaluationDataAndTestSet_UsesTestSet()
+ {
+ // Arrange
+ var calculator = new ContrastiveLossFitnessCalculator, Vector>(
+ dataSetType: DataSetType.Testing);
+ var evaluationData = new ModelEvaluationData, Vector>
+ {
+ TestSet = new DataSetStats, Vector>
+ {
+ Predicted = new Vector(new double[] { 0.0, 5.0 }),
+ Actual = new Vector(new double[] { 1.0, 1.0 })
+ }
+ };
+
+ // Act
+ var result = calculator.CalculateFitnessScore(evaluationData);
+
+ // Assert
+ Assert.Equal(25.0, result, 10); // Distance = 5.0, loss = 25.0
+ }
+
+ [Fact]
+ public void IsHigherScoreBetter_ReturnsFalse()
+ {
+ // Arrange
+ var calculator = new ContrastiveLossFitnessCalculator, Vector>();
+
+ // Assert
+ Assert.False(calculator.IsHigherScoreBetter);
+ }
+
+ [Fact]
+ public void IsBetterFitness_WithLowerScore_ReturnsTrue()
+ {
+ // Arrange
+ var calculator = new ContrastiveLossFitnessCalculator, Vector>();
+ double newScore = 0.5;
+ double currentBestScore = 1.0;
+
+ // Act
+ var result = calculator.IsBetterFitness(newScore, currentBestScore);
+
+ // Assert
+ Assert.True(result); // Lower score is better for loss functions
+ }
+
+ [Fact]
+ public void IsBetterFitness_WithHigherScore_ReturnsFalse()
+ {
+ // Arrange
+ var calculator = new ContrastiveLossFitnessCalculator, Vector>();
+ double newScore = 1.5;
+ double currentBestScore = 0.8;
+
+ // Act
+ var result = calculator.IsBetterFitness(newScore, currentBestScore);
+
+ // Assert
+ Assert.False(result); // Higher score is worse for loss functions
+ }
+
+ [Fact]
+ public void CalculateFitnessScore_FaceRecognitionScenario_ReturnsCorrectValue()
+ {
+ // Arrange - Simulating face verification scenario
+ var calculator = new ContrastiveLossFitnessCalculator, Vector>(margin: 1.0);
+ var dataSet = new DataSetStats, Vector>
+ {
+ // Same person faces should be close, different person faces should be far
+ Predicted = new Vector(new double[] { 0.1, 0.2, 0.9, 0.8 }),
+ Actual = new Vector(new double[] { 1.0, 1.0, 2.0, 3.0 })
+ };
+
+ // Act
+ var result = calculator.CalculateFitnessScore(dataSet);
+
+ // Assert
+ // Pair 1: similar (actuals both 1.0), embeddings [0.1, 0.2] vs [0.9, 0.8]
+ // Distance ≈ sqrt((0.8)² + (0.6)²) = 1.0, loss = 1.0
+
+ // Pair 2: dissimilar (actuals 2.0 vs 3.0), embeddings close
+ // Distance = 0.1, loss = max(0, 1.0 - 0.1)² = 0.81
+
+ // Average loss ≈ 0.905
+ Assert.True(result >= 0.85 && result <= 0.95);
+ }
+
+ [Fact]
+ public void CalculateFitnessScore_WithLargeMargin_AllowsMoreSeparation()
+ {
+ // Arrange
+ var smallMarginCalc = new ContrastiveLossFitnessCalculator, Vector>(margin: 0.5);
+ var largeMarginCalc = new ContrastiveLossFitnessCalculator, Vector>(margin: 2.0);
+ var dataSet = new DataSetStats, Vector>
+ {
+ // Dissimilar pairs at medium distance
+ Predicted = new Vector(new double[] { 0.0, 1.0 }),
+ Actual = new Vector(new double[] { 1.0, 2.0 })
+ };
+
+ // Act
+ var smallMarginResult = smallMarginCalc.CalculateFitnessScore(dataSet);
+ var largeMarginResult = largeMarginCalc.CalculateFitnessScore(dataSet);
+
+ // Assert
+ // Distance = 1.0
+ // Small margin (0.5): loss = max(0, 0.5 - 1.0)² = 0
+ // Large margin (2.0): loss = max(0, 2.0 - 1.0)² = 1.0
+ Assert.Equal(0.0, smallMarginResult, 10);
+ Assert.Equal(1.0, largeMarginResult, 10);
+ }
+
+ [Fact]
+ public void CalculateFitnessScore_WithEvenNumberOfElements_SplitsCorrectly()
+ {
+ // Arrange
+ var calculator = new ContrastiveLossFitnessCalculator, Vector>();
+ var dataSet = new DataSetStats, Vector>
+ {
+ // 6 elements: splits into two groups of 3
+ Predicted = new Vector(new double[] { 1.0, 2.0, 3.0, 1.0, 2.0, 3.0 }),
+ Actual = new Vector(new double[] { 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 })
+ };
+
+ // Act
+ var result = calculator.CalculateFitnessScore(dataSet);
+
+ // Assert
+ // All pairs are similar (same actuals) and identical
+ Assert.Equal(0.0, result, 10);
+ }
+
+ [Fact]
+ public void CalculateFitnessScore_WithZeroDistance_SimilarPairs_ReturnsZero()
+ {
+ // Arrange
+ var calculator = new ContrastiveLossFitnessCalculator, Vector>();
+ var dataSet = new DataSetStats, Vector>
+ {
+ Predicted = new Vector(new double[] { 0.5, 0.5, 0.5, 0.5 }),
+ Actual = new Vector(new double[] { 1.0, 1.0, 1.0, 1.0 })
+ };
+
+ // Act
+ var result = calculator.CalculateFitnessScore(dataSet);
+
+ // Assert
+ // Zero distance for similar pairs: loss = 0
+ Assert.Equal(0.0, result, 10);
+ }
+
+ [Fact]
+ public void CalculateFitnessScore_WithMaximumSeparation_DissimilarPairs_ReturnsZero()
+ {
+ // Arrange
+ var calculator = new ContrastiveLossFitnessCalculator, Vector>(margin: 1.0);
+ var dataSet = new DataSetStats, Vector>
+ {
+ // Dissimilar pairs far apart
+ Predicted = new Vector(new double[] { 0.0, 10.0 }),
+ Actual = new Vector(new double[] { 1.0, 2.0 })
+ };
+
+ // Act
+ var result = calculator.CalculateFitnessScore(dataSet);
+
+ // Assert
+ // Distance = 10.0 >> margin (1.0), loss = 0
+ Assert.Equal(0.0, result, 10);
+ }
+
+ [Fact]
+ public void CalculateFitnessScore_SignatureVerificationScenario_ReturnsCorrectValue()
+ {
+ // Arrange - Simulating signature verification
+ var calculator = new ContrastiveLossFitnessCalculator, Vector>(margin: 1.5);
+ var dataSet = new DataSetStats, Vector>
+ {
+ // Genuine signatures vs forgeries
+ Predicted = new Vector(new double[] { 0.8, 0.9, 1.0, 0.1, 0.2, 5.0 }),
+ Actual = new Vector(new double[] { 1.0, 1.0, 1.0, 2.0, 3.0, 4.0 })
+ };
+
+ // Act
+ var result = calculator.CalculateFitnessScore(dataSet);
+
+ // Assert
+ // Should handle mixed scenarios of genuine and forged signatures
+ Assert.True(result >= 0.0);
+ }
+
+ [Fact]
+ public void CalculateFitnessScore_WithVerySmallMargin_PenalizesDissimilarPairsMore()
+ {
+ // Arrange
+ var calculator = new ContrastiveLossFitnessCalculator, Vector>(margin: 0.1);
+ var dataSet = new DataSetStats, Vector>
+ {
+ // Dissimilar pairs at small distance
+ Predicted = new Vector(new double[] { 0.0, 0.05 }),
+ Actual = new Vector(new double[] { 1.0, 2.0 })
+ };
+
+ // Act
+ var result = calculator.CalculateFitnessScore(dataSet);
+
+ // Assert
+ // Distance = 0.05, margin = 0.1
+ // Loss = max(0, 0.1 - 0.05)² = 0.0025
+ Assert.Equal(0.0025, result, 10);
+ }
+ }
+}
diff --git a/tests/AiDotNet.Tests/UnitTests/FitnessCalculators/CosineSimilarityLossFitnessCalculatorTests.cs b/tests/AiDotNet.Tests/UnitTests/FitnessCalculators/CosineSimilarityLossFitnessCalculatorTests.cs
new file mode 100644
index 000000000..016cf8a4b
--- /dev/null
+++ b/tests/AiDotNet.Tests/UnitTests/FitnessCalculators/CosineSimilarityLossFitnessCalculatorTests.cs
@@ -0,0 +1,475 @@
+using System;
+using AiDotNet.FitnessCalculators;
+using AiDotNet.Models;
+using AiDotNet.LinearAlgebra;
+using Xunit;
+
+namespace AiDotNetTests.UnitTests.FitnessCalculators
+{
+ ///
+ /// Unit tests for CosineSimilarityLossFitnessCalculator, which evaluates model performance based on vector direction similarity.
+ ///
+ public class CosineSimilarityLossFitnessCalculatorTests
+ {
+ [Fact]
+ public void Constructor_WithDefaultDataSetType_UsesValidation()
+ {
+ // Arrange & Act
+ var calculator = new CosineSimilarityLossFitnessCalculator, Vector>();
+
+ // Assert
+ Assert.False(calculator.IsHigherScoreBetter); // Cosine similarity loss: lower is better
+ }
+
+ [Fact]
+ public void Constructor_WithTrainingDataSetType_UsesTraining()
+ {
+ // Arrange & Act
+ var calculator = new CosineSimilarityLossFitnessCalculator, Vector>(DataSetType.Training);
+
+ // Assert
+ Assert.False(calculator.IsHigherScoreBetter);
+ }
+
+ [Fact]
+ public void CalculateFitnessScore_WithIdenticalVectors_ReturnsZero()
+ {
+ // Arrange
+ var calculator = new CosineSimilarityLossFitnessCalculator, Vector>();
+ var dataSet = new DataSetStats, Vector>
+ {
+ Predicted = new Vector(new double[] { 1.0, 2.0, 3.0 }),
+ Actual = new Vector(new double[] { 1.0, 2.0, 3.0 })
+ };
+
+ // Act
+ var result = calculator.CalculateFitnessScore(dataSet);
+
+ // Assert
+ // Perfect alignment: cosine similarity = 1.0, loss = 1 - 1.0 = 0
+ Assert.Equal(0.0, result, 10);
+ }
+
+ [Fact]
+ public void CalculateFitnessScore_WithOppositeVectors_ReturnsTwo()
+ {
+ // Arrange
+ var calculator = new CosineSimilarityLossFitnessCalculator, Vector>();
+ var dataSet = new DataSetStats, Vector>
+ {
+ Predicted = new Vector(new double[] { 1.0, 2.0, 3.0 }),
+ Actual = new Vector(new double[] { -1.0, -2.0, -3.0 })
+ };
+
+ // Act
+ var result = calculator.CalculateFitnessScore(dataSet);
+
+ // Assert
+ // Opposite directions: cosine similarity = -1.0, loss = 1 - (-1.0) = 2.0
+ Assert.Equal(2.0, result, 10);
+ }
+
+ [Fact]
+ public void CalculateFitnessScore_WithPerpendicularVectors_ReturnsOne()
+ {
+ // Arrange
+ var calculator = new CosineSimilarityLossFitnessCalculator, Vector>();
+ var dataSet = new DataSetStats, Vector>
+ {
+ Predicted = new Vector(new double[] { 1.0, 0.0 }),
+ Actual = new Vector(new double[] { 0.0, 1.0 })
+ };
+
+ // Act
+ var result = calculator.CalculateFitnessScore(dataSet);
+
+ // Assert
+ // Perpendicular vectors: cosine similarity = 0, loss = 1 - 0 = 1.0
+ Assert.Equal(1.0, result, 10);
+ }
+
+ [Fact]
+ public void CalculateFitnessScore_WithSameDirectionDifferentMagnitude_ReturnsZero()
+ {
+ // Arrange
+ var calculator = new CosineSimilarityLossFitnessCalculator, Vector>();
+ var dataSet = new DataSetStats, Vector>
+ {
+ Predicted = new Vector(new double[] { 1.0, 2.0, 3.0 }),
+ Actual = new Vector(new double[] { 2.0, 4.0, 6.0 }) // Same direction, 2x magnitude
+ };
+
+ // Act
+ var result = calculator.CalculateFitnessScore(dataSet);
+
+ // Assert
+ // Same direction regardless of magnitude: cosine similarity = 1.0, loss = 0
+ Assert.Equal(0.0, result, 10);
+ }
+
+ [Fact]
+ public void CalculateFitnessScore_WithPartialAlignment_ReturnsCorrectValue()
+ {
+ // Arrange
+ var calculator = new CosineSimilarityLossFitnessCalculator, Vector>();
+ var dataSet = new DataSetStats, Vector>
+ {
+ Predicted = new Vector(new double[] { 1.0, 0.0, 0.0 }),
+ Actual = new Vector(new double[] { 1.0, 1.0, 0.0 })
+ };
+
+ // Act
+ var result = calculator.CalculateFitnessScore(dataSet);
+
+ // Assert
+ // Dot product = 1.0
+ // Norm predicted = 1.0, Norm actual = sqrt(2) ≈ 1.414
+ // Cosine similarity = 1.0 / (1.0 * 1.414) ≈ 0.707
+ // Loss = 1 - 0.707 ≈ 0.293
+ Assert.Equal(0.29289321881345248, result, 10);
+ }
+
+ [Fact]
+ public void CalculateFitnessScore_WithAllZeros_ReturnsOne()
+ {
+ // Arrange
+ var calculator = new CosineSimilarityLossFitnessCalculator, Vector>();
+ var dataSet = new DataSetStats, Vector>
+ {
+ Predicted = new Vector(new double[] { 0.0, 0.0, 0.0 }),
+ Actual = new Vector(new double[] { 0.0, 0.0, 0.0 })
+ };
+
+ // Act
+ var result = calculator.CalculateFitnessScore(dataSet);
+
+ // Assert
+ // All zeros case: handled by epsilon to prevent division by zero
+ // Should return close to 1.0 (no meaningful similarity)
+ Assert.True(result >= 0.999);
+ }
+
+ [Fact]
+ public void CalculateFitnessScore_WithSingleElement_ReturnsCorrectValue()
+ {
+ // Arrange
+ var calculator = new CosineSimilarityLossFitnessCalculator, Vector>();
+ var dataSet = new DataSetStats, Vector>
+ {
+ Predicted = new Vector(new double[] { 0.5 }),
+ Actual = new Vector(new double[] { 1.0 })
+ };
+
+ // Act
+ var result = calculator.CalculateFitnessScore(dataSet);
+
+ // Assert
+ // Dot product = 0.5, norms = 0.5 and 1.0
+ // Cosine similarity = 0.5 / (0.5 * 1.0) = 1.0
+ // Loss = 1 - 1.0 = 0
+ Assert.Equal(0.0, result, 10);
+ }
+
+ [Fact]
+ public void CalculateFitnessScore_WithFloatType_WorksCorrectly()
+ {
+ // Arrange
+ var calculator = new CosineSimilarityLossFitnessCalculator, Vector>();
+ var dataSet = new DataSetStats, Vector>
+ {
+ Predicted = new Vector(new float[] { 1.0f, 2.0f, 3.0f }),
+ Actual = new Vector(new float[] { 1.0f, 2.0f, 3.0f })
+ };
+
+ // Act
+ var result = calculator.CalculateFitnessScore(dataSet);
+
+ // Assert
+ Assert.Equal(0.0f, result, 5);
+ }
+
+ [Fact]
+ public void CalculateFitnessScore_WithNullDataSet_ThrowsArgumentNullException()
+ {
+ // Arrange
+ var calculator = new CosineSimilarityLossFitnessCalculator, Vector>();
+
+ // Act & Assert
+ Assert.Throws(() =>
+ calculator.CalculateFitnessScore((DataSetStats, Vector>)null));
+ }
+
+ [Fact]
+ public void CalculateFitnessScore_WithModelEvaluationData_UsesValidationSet()
+ {
+ // Arrange
+ var calculator = new CosineSimilarityLossFitnessCalculator, Vector>(DataSetType.Validation);
+ var evaluationData = new ModelEvaluationData, Vector>
+ {
+ ValidationSet = new DataSetStats, Vector>
+ {
+ Predicted = new Vector(new double[] { 1.0, 2.0 }),
+ Actual = new Vector(new double[] { 1.0, 2.0 })
+ }
+ };
+
+ // Act
+ var result = calculator.CalculateFitnessScore(evaluationData);
+
+ // Assert
+ Assert.Equal(0.0, result, 10); // Perfect alignment
+ }
+
+ [Fact]
+ public void CalculateFitnessScore_WithModelEvaluationDataAndTestSet_UsesTestSet()
+ {
+ // Arrange
+ var calculator = new CosineSimilarityLossFitnessCalculator, Vector>(DataSetType.Testing);
+ var evaluationData = new ModelEvaluationData, Vector>
+ {
+ TestSet = new DataSetStats, Vector>
+ {
+ Predicted = new Vector(new double[] { 1.0, 0.0 }),
+ Actual = new Vector(new double[] { 0.0, 1.0 })
+ }
+ };
+
+ // Act
+ var result = calculator.CalculateFitnessScore(evaluationData);
+
+ // Assert
+ Assert.Equal(1.0, result, 10); // Perpendicular vectors
+ }
+
+ [Fact]
+ public void IsHigherScoreBetter_ReturnsFalse()
+ {
+ // Arrange
+ var calculator = new CosineSimilarityLossFitnessCalculator, Vector>();
+
+ // Assert
+ Assert.False(calculator.IsHigherScoreBetter);
+ }
+
+ [Fact]
+ public void IsBetterFitness_WithLowerScore_ReturnsTrue()
+ {
+ // Arrange
+ var calculator = new CosineSimilarityLossFitnessCalculator, Vector>();
+ double newScore = 0.2;
+ double currentBestScore = 0.5;
+
+ // Act
+ var result = calculator.IsBetterFitness(newScore, currentBestScore);
+
+ // Assert
+ Assert.True(result); // Lower score is better for loss functions
+ }
+
+ [Fact]
+ public void IsBetterFitness_WithHigherScore_ReturnsFalse()
+ {
+ // Arrange
+ var calculator = new CosineSimilarityLossFitnessCalculator, Vector>();
+ double newScore = 0.8;
+ double currentBestScore = 0.3;
+
+ // Act
+ var result = calculator.IsBetterFitness(newScore, currentBestScore);
+
+ // Assert
+ Assert.False(result); // Higher score is worse for loss functions
+ }
+
+ [Fact]
+ public void CalculateFitnessScore_DocumentSimilarityScenario_ReturnsCorrectValue()
+ {
+ // Arrange - Simulating document vector comparison
+ var calculator = new CosineSimilarityLossFitnessCalculator, Vector>();
+ var dataSet = new DataSetStats, Vector>
+ {
+ // Term frequency vectors for similar documents
+ Predicted = new Vector(new double[] { 0.5, 0.8, 0.1, 0.0 }),
+ Actual = new Vector(new double[] { 0.6, 0.7, 0.2, 0.0 })
+ };
+
+ // Act
+ var result = calculator.CalculateFitnessScore(dataSet);
+
+ // Assert
+ // Dot product = 0.5*0.6 + 0.8*0.7 + 0.1*0.2 = 0.3 + 0.56 + 0.02 = 0.88
+ // Norm predicted = sqrt(0.25 + 0.64 + 0.01) ≈ 0.9487
+ // Norm actual = sqrt(0.36 + 0.49 + 0.04) ≈ 0.9434
+ // Cosine similarity = 0.88 / (0.9487 * 0.9434) ≈ 0.984
+ // Loss = 1 - 0.984 ≈ 0.016
+ Assert.Equal(0.016242195912488764, result, 10);
+ }
+
+ [Fact]
+ public void CalculateFitnessScore_WithNegativeValues_HandlesCorrectly()
+ {
+ // Arrange
+ var calculator = new CosineSimilarityLossFitnessCalculator, Vector>();
+ var dataSet = new DataSetStats, Vector>
+ {
+ Predicted = new Vector(new double[] { -1.0, 2.0, -3.0 }),
+ Actual = new Vector(new double[] { 1.0, -2.0, 3.0 })
+ };
+
+ // Act
+ var result = calculator.CalculateFitnessScore(dataSet);
+
+ // Assert
+ // Dot product = -1 + (-4) + (-9) = -14
+ // Norms are both sqrt(14)
+ // Cosine similarity = -14 / 14 = -1.0 (opposite directions)
+ // Loss = 1 - (-1.0) = 2.0
+ Assert.Equal(2.0, result, 10);
+ }
+
+ [Fact]
+ public void CalculateFitnessScore_WithSmallAngles_ReturnsSmallLoss()
+ {
+ // Arrange
+ var calculator = new CosineSimilarityLossFitnessCalculator, Vector>();
+ var dataSet = new DataSetStats, Vector>
+ {
+ // Nearly aligned vectors
+ Predicted = new Vector(new double[] { 1.0, 0.1 }),
+ Actual = new Vector(new double[] { 1.0, 0.0 })
+ };
+
+ // Act
+ var result = calculator.CalculateFitnessScore(dataSet);
+
+ // Assert
+ // Small angle should result in high similarity and low loss
+ Assert.True(result < 0.01);
+ }
+
+ [Fact]
+ public void CalculateFitnessScore_WithLargeAngles_ReturnsLargeLoss()
+ {
+ // Arrange
+ var calculator = new CosineSimilarityLossFitnessCalculator, Vector>();
+ var dataSet = new DataSetStats, Vector>
+ {
+ // Nearly opposite vectors
+ Predicted = new Vector(new double[] { 1.0, 0.1 }),
+ Actual = new Vector(new double[] { -1.0, -0.1 })
+ };
+
+ // Act
+ var result = calculator.CalculateFitnessScore(dataSet);
+
+ // Assert
+ // Large angle (nearly 180°) should result in low similarity and high loss (close to 2.0)
+ Assert.True(result > 1.9);
+ }
+
+ [Fact]
+ public void CalculateFitnessScore_RecommendationSystemScenario_ReturnsCorrectValue()
+ {
+ // Arrange - Simulating user preference vectors
+ var calculator = new CosineSimilarityLossFitnessCalculator, Vector>();
+ var dataSet = new DataSetStats, Vector>
+ {
+ // User preferences for different categories
+ Predicted = new Vector(new double[] { 0.9, 0.1, 0.5, 0.3 }),
+ Actual = new Vector(new double[] { 0.8, 0.2, 0.6, 0.2 })
+ };
+
+ // Act
+ var result = calculator.CalculateFitnessScore(dataSet);
+
+ // Assert
+ // Should show high similarity (low loss) for similar preferences
+ Assert.True(result < 0.1); // Less than 10% loss
+ }
+
+ [Fact]
+ public void CalculateFitnessScore_ImageRetrievalScenario_ReturnsCorrectValue()
+ {
+ // Arrange - Simulating image feature vectors
+ var calculator = new CosineSimilarityLossFitnessCalculator, Vector>();
+ var dataSet = new DataSetStats, Vector>
+ {
+ // Feature vectors for similar images
+ Predicted = new Vector(new double[] { 0.7, 0.5, 0.3, 0.8, 0.2 }),
+ Actual = new Vector(new double[] { 0.6, 0.6, 0.4, 0.7, 0.1 })
+ };
+
+ // Act
+ var result = calculator.CalculateFitnessScore(dataSet);
+
+ // Assert
+ // Dot product = 0.42 + 0.30 + 0.12 + 0.56 + 0.02 = 1.42
+ // Should handle feature vector comparison correctly
+ Assert.True(result >= 0.0 && result <= 2.0);
+ }
+
+ [Fact]
+ public void CalculateFitnessScore_WithVerySmallValues_HandlesCorrectly()
+ {
+ // Arrange
+ var calculator = new CosineSimilarityLossFitnessCalculator, Vector>();
+ var dataSet = new DataSetStats, Vector>
+ {
+ Predicted = new Vector(new double[] { 0.001, 0.002, 0.001 }),
+ Actual = new Vector(new double[] { 0.001, 0.001, 0.002 })
+ };
+
+ // Act
+ var result = calculator.CalculateFitnessScore(dataSet);
+
+ // Assert
+ // Should handle small values without numerical instability
+ Assert.True(result >= 0.0 && result <= 2.0);
+ }
+
+ [Fact]
+ public void CalculateFitnessScore_MagnitudeInvariance_VerifiesProperty()
+ {
+ // Arrange
+ var calculator = new CosineSimilarityLossFitnessCalculator, Vector>();
+ var dataSet1 = new DataSetStats, Vector>
+ {
+ Predicted = new Vector(new double[] { 1.0, 2.0, 3.0 }),
+ Actual = new Vector(new double[] { 2.0, 4.0, 6.0 })
+ };
+ var dataSet2 = new DataSetStats, Vector>
+ {
+ Predicted = new Vector(new double[] { 10.0, 20.0, 30.0 }),
+ Actual = new Vector(new double[] { 20.0, 40.0, 60.0 })
+ };
+
+ // Act
+ var result1 = calculator.CalculateFitnessScore(dataSet1);
+ var result2 = calculator.CalculateFitnessScore(dataSet2);
+
+ // Assert
+ // Cosine similarity is magnitude-invariant - both should give same result
+ Assert.Equal(result1, result2, 10);
+ }
+
+ [Fact]
+ public void CalculateFitnessScore_TextEmbeddingScenario_ReturnsCorrectValue()
+ {
+ // Arrange - Simulating word/sentence embeddings
+ var calculator = new CosineSimilarityLossFitnessCalculator, Vector>();
+ var dataSet = new DataSetStats, Vector>
+ {
+ // Embeddings for semantically similar text
+ Predicted = new Vector(new double[] { 0.8, 0.6, -0.2, 0.9, -0.1 }),
+ Actual = new Vector(new double[] { 0.7, 0.7, -0.1, 0.8, -0.2 })
+ };
+
+ // Act
+ var result = calculator.CalculateFitnessScore(dataSet);
+
+ // Assert
+ // Should indicate high similarity for semantically similar embeddings
+ Assert.True(result < 0.15); // Less than 15% loss
+ }
+ }
+}
diff --git a/tests/AiDotNet.Tests/UnitTests/FitnessCalculators/DiceLossFitnessCalculatorTests.cs b/tests/AiDotNet.Tests/UnitTests/FitnessCalculators/DiceLossFitnessCalculatorTests.cs
new file mode 100644
index 000000000..28d988495
--- /dev/null
+++ b/tests/AiDotNet.Tests/UnitTests/FitnessCalculators/DiceLossFitnessCalculatorTests.cs
@@ -0,0 +1,337 @@
+using System;
+using AiDotNet.FitnessCalculators;
+using AiDotNet.Models;
+using AiDotNet.LinearAlgebra;
+using Xunit;
+
+namespace AiDotNetTests.UnitTests.FitnessCalculators
+{
+ ///
+ /// Unit tests for DiceLossFitnessCalculator, which evaluates model performance for segmentation tasks.
+ ///
+ public class DiceLossFitnessCalculatorTests
+ {
+ [Fact]
+ public void Constructor_WithDefaultDataSetType_UsesValidation()
+ {
+ // Arrange & Act
+ var calculator = new DiceLossFitnessCalculator, Vector>();
+
+ // Assert
+ Assert.False(calculator.IsHigherScoreBetter); // Dice loss: lower is better
+ }
+
+ [Fact]
+ public void Constructor_WithTrainingDataSetType_UsesTraining()
+ {
+ // Arrange & Act
+ var calculator = new DiceLossFitnessCalculator, Vector>(DataSetType.Training);
+
+ // Assert
+ Assert.False(calculator.IsHigherScoreBetter);
+ }
+
+ [Fact]
+ public void CalculateFitnessScore_WithPerfectPredictions_ReturnsZero()
+ {
+ // Arrange
+ var calculator = new DiceLossFitnessCalculator