From ff27ef0cdfb2709e85a603b27ccd6ad1c98997f8 Mon Sep 17 00:00:00 2001 From: Singari Sahith Kumar Date: Fri, 24 May 2024 01:23:10 +0200 Subject: [PATCH] SDR Reconstruction --- source/NeoCortexApi/Helpers.cs | 55 +++ source/NeoCortexApi/SPSdrReconstructor.cs | 4 +- source/NeoCortexUtils/NeoCortexUtils.cs | 319 +++++++++++++- .../ImageBinarizerSpatialPattern.cs | 332 +++++++++++++++ source/Samples/NeoCortexApiSample/Program.cs | 5 + .../SpatialPatternLearning.cs | 189 ++++++++- .../SdrReconstructionTests.cs | 394 +++++++++++++++++- 7 files changed, 1287 insertions(+), 11 deletions(-) create mode 100644 source/Samples/NeoCortexApiSample/ImageBinarizerSpatialPattern.cs diff --git a/source/NeoCortexApi/Helpers.cs b/source/NeoCortexApi/Helpers.cs index 108c154d4..827f7ab31 100644 --- a/source/NeoCortexApi/Helpers.cs +++ b/source/NeoCortexApi/Helpers.cs @@ -581,5 +581,60 @@ public static List GetDistalConnectedCells(Cell cell, IList populati return connectedCells; } + + + + public static string StringifyDictionarybykeys(Dictionary dictionary) + { + if (dictionary == null) + { + return "null"; + } + + StringBuilder result = new StringBuilder(); + foreach (var key in dictionary.Keys) + { + // Access the value using the key and append it to the result + result.Append($"{dictionary[key]},"); + } + + // Remove the trailing separator + if (result.Length > 0) + { + result.Length--; // Remove the last character + } + + return result.ToString(); + } + + + + /// + /// Thresholds a collection of Reconstruced Permanence values based on a given threshold. + /// + /// The collection of probability values to be thresholded. + /// The threshold value used for thresholding. + /// A list of binary values (0 or 1) representing the thresholded probabilities. + + + public static List ThresholdingProbabilities(IEnumerable values, double threshold) + { + if (values == null) + { + return null; + } + + List resultList = new List(); + + foreach (var numericValue in values) + { + int thresholdedValue = (numericValue >= threshold) ? 1 : 0; + + resultList.Add(thresholdedValue); + } + + return resultList; + } + } } diff --git a/source/NeoCortexApi/SPSdrReconstructor.cs b/source/NeoCortexApi/SPSdrReconstructor.cs index 974596ca2..e9d566e81 100644 --- a/source/NeoCortexApi/SPSdrReconstructor.cs +++ b/source/NeoCortexApi/SPSdrReconstructor.cs @@ -30,13 +30,13 @@ public SPSdrReconstructor(Connections mem) /// public Dictionary Reconstruct(int[] activeMiniColumns) { - if(activeMiniColumns == null) + if (activeMiniColumns == null) { throw new ArgumentNullException(nameof(activeMiniColumns)); } var cols = _mem.GetColumnList(activeMiniColumns); - + Dictionary result = new Dictionary(); // diff --git a/source/NeoCortexUtils/NeoCortexUtils.cs b/source/NeoCortexUtils/NeoCortexUtils.cs index d92653e49..4cfde92d6 100644 --- a/source/NeoCortexUtils/NeoCortexUtils.cs +++ b/source/NeoCortexUtils/NeoCortexUtils.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. using Daenet.ImageBinarizerLib; using Daenet.ImageBinarizerLib.Entities; +using SkiaSharp; using System; using System.Collections.Generic; using System.Drawing; @@ -204,6 +205,152 @@ public static void DrawBitmaps(List twoDimArrays, String filePath, Color myBitmap.Save(filePath, ImageFormat.Png); } + /// + /// Combines heatmap and normalized permanence representations into a single image with title. + /// This Drwaitng Function is used to Visulalization of the Permanence Values. + /// + /// List of arrays representing the heatmap data. + /// List of arrays representing normalized data below the heatmap. + /// List of arrays of original Encoded data encoded by the scaler encoder. + /// Output image path for saving the combined image. + /// Width of the heatmap bitmap (default is 1024). + /// Height of the heatmap bitmap (default is 1024). + /// Threshold for values above which pixels are red (default is 200). + /// Threshold for values between which pixels are yellow (default is 127). + /// Threshold for values below which pixels are green (default is 20). + /// Factor by which the image is enlarged for better visualization (default is 4). + public static void Draw1dHeatmap(List heatmapData, List normalizedData, List encodedData, String filePath, + int bmpWidth = 1024, + int bmpHeight = 1024, + decimal redStart = 200, decimal yellowMiddle = 127, decimal greenStart = 20, + int enlargementFactor = 4) + { + int height = heatmapData.Count; + int maxLength = heatmapData.Max(arr => arr.Length); + + if (maxLength > bmpWidth || height > bmpHeight) + throw new ArgumentException("Size of all included arrays must be less than specified 'bmpWidth' and 'bmpHeight'"); + + // Calculate target width and height based on the enlargement factor + int targetWidth = bmpWidth * enlargementFactor; + // Include space for the title and labels + int targetHeight = bmpHeight * enlargementFactor + 40; + + // Create a new bitmap for the heatmap and text row with background + System.Drawing.Bitmap myBitmap = new System.Drawing.Bitmap(targetWidth, targetHeight, System.Drawing.Imaging.PixelFormat.Format32bppArgb); + using (Graphics g = Graphics.FromImage(myBitmap)) + { + // Set the background color to LightSkyBlue + g.Clear(Color.LightSkyBlue); + + // Draw title + string title = "HeatMap Image"; + Font titleFont = new Font("Arial", 12); + SizeF titleSize = g.MeasureString(title, titleFont); + float titleX = (targetWidth - titleSize.Width) / 2; + // Move the title further up (adjust the value as needed) + float titleY = 0; + g.DrawString(title, titleFont, Brushes.Black, new PointF(titleX, titleY)); + + // Calculate scale factors for width and height based on the target dimensions + var scaleX = (double)targetWidth / bmpWidth; + // Exclude the space for the title and labels from scaleY + var scaleY = (double)(targetHeight - 40) / bmpHeight; + + // Leave a gap between sections + float labelY = 30; + + // Draw heatmap + for (int i = 0; i < height; i++) + { + var heatmapArr = heatmapData[i]; + + int w = heatmapArr.Length; + + for (int Xcount = 0; Xcount < w; Xcount++) + { + for (int padX = 0; padX < scaleX; padX++) + { + for (int padY = 0; padY < scaleY; padY++) + { + myBitmap.SetPixel((int)(i * scaleX) + (int)(Xcount * scaleX) + padX, (int)(padY) + (int)labelY, GetColor(redStart, yellowMiddle, greenStart, (Decimal)heatmapArr[Xcount])); + } + } + } + // Draw normalized representation below the heatmap + using (var font = new Font("Arial", 12)) + { + string normalizedLabel = "Normalized Permanence (Reconstructed Inputs)"; + Font normalizedLabelFont = new Font("Arial", 10); + SizeF normalizedLabelSize = g.MeasureString(normalizedLabel, normalizedLabelFont); + float normalizedLabelX = (targetWidth - normalizedLabelSize.Width) / 2; + // Leave a gap before drawing the label + labelY += 130; + // Adjust the vertical position down by 10 units (you can modify this value) + labelY += 70; + g.DrawString(normalizedLabel, normalizedLabelFont, Brushes.Black, new PointF(normalizedLabelX, labelY)); + + var normalizedArr = normalizedData[i]; + for (int Xcount = 0; Xcount < normalizedArr.Length; Xcount++) + { + // Format the integer as string + string formattedNumber = normalizedArr[Xcount].ToString(); + // Adjusted position for top middle + float textX = (float)(i * scaleX) + (float)(Xcount * scaleX) + (float)(scaleX / 2) - 5; + // Adjusted vertical position for label + float textY = (float)(bmpHeight * scaleY) + 25; + g.DrawString(formattedNumber, font, Brushes.Black, new PointF(textX, textY)); + + // Draw a line from the top middle of the number to the corresponding heatmap pixel + // Adjusted starting point for the line + float lineStartX = textX + 5; + // Adjusted starting point for the line + float lineStartY = textY - 20; + float lineEndX = (float)(i * scaleX) + (float)(Xcount * scaleX) + (float)(scaleX / 2); + float lineEndY = 300; + g.DrawLine(Pens.Black, lineStartX, lineStartY, lineEndX, lineEndY); + + } + // Draw the label for encoded values + string encodedLabel = "Encoded Inputs"; + Font encodedLabelFont = new Font("Arial", 10); + SizeF encodedLabelSize = g.MeasureString(encodedLabel, encodedLabelFont); + float encodedLabelX = (targetWidth - encodedLabelSize.Width) / 2; + // Leave a gap before drawing the label + labelY = 120; + // Adjust the vertical position down by 10 units (you can modify this value) + labelY += -50; + g.DrawString(encodedLabel, encodedLabelFont, Brushes.Black, new PointF(encodedLabelX, labelY)); + + // Draw encoded values + var encodedArr = encodedData[i]; + for (int Xcount = 0; Xcount < encodedArr.Length; Xcount++) + { + // Format the integer as string + string formattedNumber = encodedArr[Xcount].ToString(); + // Adjusted position for top middle + float textX = (float)(i * scaleX) + (float)(Xcount * scaleX) + (float)(scaleX / 2) - 5; + float textY = 175; // Adjusted vertical position for label + g.DrawString(formattedNumber, font, Brushes.Black, new PointF(textX, textY)); + // Draw a line from the top middle of the number to the corresponding heatmap pixel + // Adjusted starting point for the line + float lineStartX = textX + 5; + // Adjusted starting point for the line + float lineStartY = textY - 20; + float lineEndX = (float)(i * scaleX) + (float)(Xcount * scaleX) + (float)(scaleX / 2); + // Adjusted ending point for the line + float lineEndY = 100; + g.DrawLine(Pens.Black, lineStartX, lineStartY, lineEndX, lineEndY); + } + } + } + } + + // Save the combined image with heatmap and text row + myBitmap.Save(filePath, ImageFormat.Png); + } + + /// /// Drawas bitmaps from list of arrays. @@ -264,7 +411,154 @@ public static void DrawHeatmaps(List twoDimArrays, String filePath, myBitmap.Save(filePath, ImageFormat.Png); } + /// + /// Draws a combined similarity plot based on the given list of similarity values. + /// This graph can Visulaze the Similarity Bar graph of multiple inputs between the Encoded inputs + /// and the Reconsturced Inputs using Reconstruct Method. + /// + /// The list of similarity values to be plotted. + /// The file path where the plot image will be saved. + /// Width of the graph. + /// Height of the graph. + /// + /// The plot includes bars representing similarity values, indexed from left to right. Each bar's height corresponds to its similarity value. + /// Axis labels, a title, a scale indicating similarity values, and text indicating the similarity range are added to the plot. + /// + + public static void DrawCombinedSimilarityPlot(List similarities, string filePath, int imageWidth, int imageHeight) + { + // Create a new bitmap + Bitmap bitmap = new Bitmap(imageWidth, imageHeight); + // Create a graphics object from the bitmap + using (Graphics graphics = Graphics.FromImage(bitmap)) + { + // Clear the bitmap with a white background + graphics.Clear(Color.White); + + // Define the maximum similarity value + double maxSimilarity = similarities.Max(); + + // Calculate the maximum bar height based on the plot height and scale + // Adjusted for title position + int maxBarHeight = imageHeight - 200; + + // Determine the number of bars + int barCount = similarities.Count; + + // Calculate the total width occupied by bars and spacing + // minimum bar width is 10 pixels + int totalBarWidth = barCount * 10; + //20 pixels of spacing between bars + int totalSpacing = 20 * (barCount + 1); + + // Calculate the maximum available width for bars (excluding margins) + // Adjusted for margins + int maxAvailableWidth = imageWidth - totalSpacing - 200; + + // Calculate the bar width based on the available space and number of bars + // Minimum width for each bar + int minBarWidth = 20; + int barWidth = Math.Max(minBarWidth, maxAvailableWidth / barCount); + + // Define the width of the scale + int scaleWidth = 100; + + // Draw each bar + for (int i = 0; i < barCount; i++) + { + // Calculate the height of the bar based on the similarity value + int barHeight = (int)(similarities[i] / maxSimilarity * maxBarHeight); + + // Determine the position and size of the bar + // Adjusted x position and spacing between bars + int x = scaleWidth + (i + 1) * 20 + i * barWidth; + // Adjusted for title position and space at the bottom for labels + int y = imageHeight - barHeight - 100; + + // Draw the bar with a minimum width of 1 pixel to avoid disappearance + // Subtracting 1 to leave a small gap between bars + int w = Math.Max(1, barWidth - 1); + + // Determine the color based on the similarity level + Color color = GetColorForSimilarity(similarities[i]); + + // Create a solid brush with the determined color + Brush brush = new SolidBrush(color); + + // Draw the bar + graphics.FillRectangle(brush, x, y, w, barHeight); + + // Add labels for each bar + // Format the similarity value + string label = similarities[i].ToString("0.0"); + Font font = new Font(FontFamily.GenericSansSerif, 10, FontStyle.Bold); + SizeF labelSize = graphics.MeasureString(label, font); + // Draw the label above the bar + graphics.DrawString(label, font, Brushes.Black, x + (barWidth - labelSize.Width) / 2, y - 20); + // Draw input label below the bar + graphics.DrawString($"{i + 1}", font, Brushes.Black, x + (barWidth - labelSize.Width) / 2, imageHeight - 50); + } + // Add axis labels + Font axisFont = new Font(FontFamily.GenericSansSerif, 14, FontStyle.Bold); + graphics.DrawString("X - Axis (Input) Index", axisFont, Brushes.Black, scaleWidth + (imageWidth - scaleWidth) / 2, imageHeight - 20); + // Add a title + string title = "Similarity Graph"; + Font titleFont = new Font(FontFamily.GenericSansSerif, 18, FontStyle.Bold); + SizeF titleSize = graphics.MeasureString(title, titleFont); + // Adjusted title position + graphics.DrawString(title, titleFont, Brushes.Black, (imageWidth - titleSize.Width) / 2, 20); + + // Add a scale indicating values from 0 to 1 + Font scaleFont = new Font(FontFamily.GenericSansSerif, 12, FontStyle.Bold); + // Draw 11 tick marks + for (int i = 0; i <= 10; i++) + { + double value = i / 10.0; + // Invert value and map to plot height + int y = (int)((1 - value) * maxBarHeight) + 100; + // Draw tick mark + graphics.DrawLine(Pens.Black, scaleWidth - 10, y, scaleWidth, y); + // Draw value label + graphics.DrawString(value.ToString("0.0"), scaleFont, Brushes.Black, 0, y - 8); + } + + // Add text indicating the similarity test + string similarityText = "Y axis-Similarity Range"; + // Larger and bold font for similarity text + Font similarityFont = new Font(FontFamily.GenericSansSerif, 14, FontStyle.Bold); + SizeF similaritySize = graphics.MeasureString(similarityText, similarityFont); + graphics.DrawString(similarityText, similarityFont, Brushes.Black, 50, imageHeight / 2 - similaritySize.Height / 2, new StringFormat { FormatFlags = StringFormatFlags.DirectionVertical }); + } + + // Save the bitmap to a file as PNG format + bitmap.Save(filePath, ImageFormat.Png); + } + + /// + /// Determines the color based on the given similarity level. + /// + /// The similarity level to determine the color for (range: 0 to 1). + /// The color corresponding to the similarity level, ranging from light gray to dark orange. + + private static Color GetColorForSimilarity(double similarity) + { + // Define the color range + // Light gray + int minColorValue = 100; + // Dark orange + int maxColorValue = 255; + + // Map the similarity value to the color range + int colorValue = (int)(minColorValue + (maxColorValue - minColorValue) * similarity); + + // Ensure the color value is within the valid range + colorValue = Math.Max(minColorValue, Math.Min(maxColorValue, colorValue)); + + // Create a color with the determined value + // Orange gradient + return Color.FromArgb(colorValue, colorValue / 2, 0); + } private static Color GetColor(decimal redStartVal, decimal yellowStartVal, decimal greenStartVal, decimal val) { @@ -357,6 +651,29 @@ private static int[] GetColorValues(decimal highBound, decimal lowBound, int[] h return rgb; } + /// + /// Determines the color of a bar based on the given similarity level. + /// + /// The similarity level to determine the color for. + /// The color corresponding to the similarity level. + + private static Color GetBarColor(double similarity) + { + // Assign color based on similarity level + // High similarity (90% or higher) + if (similarity >= 0.9) + return Color.DarkOrange; + // Medium similarity (70% or higher) + else if (similarity >= 0.7) + return Color.Orange; + // Low similarity (50% or higher) + else if (similarity >= 0.5) + return Color.LightSalmon; + // Very low similarity (below 50%) + else + return Color.LightGray; + } + /// /// /// @@ -587,4 +904,4 @@ public static double[] Softmax(double[] input) return exponentials.Select(x => x / sum).ToArray(); } } -} +} \ No newline at end of file diff --git a/source/Samples/NeoCortexApiSample/ImageBinarizerSpatialPattern.cs b/source/Samples/NeoCortexApiSample/ImageBinarizerSpatialPattern.cs new file mode 100644 index 000000000..e6f1c185a --- /dev/null +++ b/source/Samples/NeoCortexApiSample/ImageBinarizerSpatialPattern.cs @@ -0,0 +1,332 @@ +using NeoCortex; +using NeoCortexApi.Entities; +using NeoCortexApi.Utility; +using NeoCortexApi; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; + +namespace NeoCortexApiSample +{ + internal class ImageBinarizerSpatialPattern + { + public string inputPrefix { get; private set; } + + /// + /// Implements an experiment that demonstrates how to learn spatial patterns. + /// SP will learn every presented Image input in multiple iterations. + /// + public void Run() + { + Console.WriteLine($"Hello NeocortexApi! Experiment {nameof(ImageBinarizerSpatialPattern)}"); + + double minOctOverlapCycles = 1.0; + double maxBoost = 5.0; + // We will build a slice of the cortex with the given number of mini-columns + int numColumns = 64 * 64; + // The Size of the Image Height and width is 28 pixel + int imageSize = 28; + var colDims = new int[] { 64, 64 }; + + // This is a set of configuration parameters used in the experiment. + HtmConfig cfg = new HtmConfig(new int[] { imageSize, imageSize }, new int[] { numColumns }) + { + CellsPerColumn = 10, + InputDimensions = new int[] { imageSize, imageSize }, + NumInputs = imageSize * imageSize, + ColumnDimensions = colDims, + MaxBoost = maxBoost, + DutyCyclePeriod = 100, + MinPctOverlapDutyCycles = minOctOverlapCycles, + GlobalInhibition = false, + NumActiveColumnsPerInhArea = 0.02 * numColumns, + PotentialRadius = (int)(0.15 * imageSize * imageSize), + LocalAreaDensity = -1, + ActivationThreshold = 10, + MaxSynapsesPerSegment = (int)(0.01 * numColumns), + Random = new ThreadSafeRandom(42), + StimulusThreshold = 10, + }; + + //Runnig the Experiment + var sp = RunExperiment(cfg, inputPrefix); + //Runing the Reconstruction Method Experiment + RunRustructuringExperiment(sp); + + } + + /// + /// Implements the experiment. + /// + /// + /// The name of the images + /// The trained bersion of the SP. + private SpatialPooler RunExperiment(HtmConfig cfg, string inputPrefix) + { + + var mem = new Connections(cfg); + + bool isInStableState = false; + + int numColumns = 64 * 64; + //Accessing the Image Folder form the Cureent Directory + string trainingFolder = "Sample\\TestFiles"; + //Accessing the Image Folder form the Cureent Directory Foldfer + var trainingImages = Directory.GetFiles(trainingFolder, $"{inputPrefix}*.png"); + //Image Size + int imageSize = 28; + //Folder Name in the Directorty + string testName = "test_image"; + + HomeostaticPlasticityController hpa = new HomeostaticPlasticityController(mem, trainingImages.Length * 50, (isStable, numPatterns, actColAvg, seenInputs) => + { + // Event should only be fired when entering the stable state. + if (isStable) + { + isInStableState = true; + Debug.WriteLine($"Entered STABLE state: Patterns: {numPatterns}, Inputs: {seenInputs}, iteration: {seenInputs / numPatterns}"); + } + else + { + isInStableState = false; + Debug.WriteLine($"INSTABLE STATE"); + } + // Ideal SP should never enter unstable state after stable state. + Debug.WriteLine($"Entered STABLE state: Patterns: {numPatterns}, Inputs: {seenInputs}, iteration: {seenInputs / numPatterns}"); + }, requiredSimilarityThreshold: 0.975); + + // It creates the instance of Spatial Pooler Multithreaded version. + SpatialPooler sp = new SpatialPooler(hpa); + + //Initializing the Spatial Pooler Algorithm + sp.Init(mem, new DistributedMemory() { ColumnDictionary = new InMemoryDistributedDictionary(1) }); + + //Image Size + int imgSize = 28; + int[] activeArray = new int[numColumns]; + + int numStableCycles = 0; + // Runnig the Traning Cycle for 5 times + int maxCycles = 5; + int currentCycle = 0; + + while (!isInStableState && currentCycle < maxCycles) + { + foreach (var Image in trainingImages) + { + //Binarizing the Images before taking Inputs for the Sp + string inputBinaryImageFile = NeoCortexUtils.BinarizeImage($"{Image}", imgSize, testName); + + // Read Binarized and Encoded input csv file into array + int[] inputVector = NeoCortexUtils.ReadCsvIntegers(inputBinaryImageFile).ToArray(); + + int[] oldArray = new int[activeArray.Length]; + List overlapArrays = new List(); + List bostArrays = new List(); + + sp.compute(inputVector, activeArray, true); + //Getting the Active Columns + var activeCols = ArrayUtils.IndexWhere(activeArray, (el) => el == 1); + + Debug.WriteLine($"'Cycle: {currentCycle} - Image-Input: {Image}'"); + Debug.WriteLine($"INPUT :{Helpers.StringifyVector(inputVector)}"); + Debug.WriteLine($"SDR:{Helpers.StringifyVector(activeCols)}\n"); + } + + currentCycle++; + + // Check if the desired number of cycles is reached + if (currentCycle >= maxCycles) + break; + + // Increment numStableCycles only when it's in a stable state + if (isInStableState) + numStableCycles++; + } + + return sp; + } + /// + /// Runs the restructuring experiment using the provided spatial pooler. + /// This method iterates through a set of training images, computes spatial pooling, + /// reconstructs permanence values, and generates heatmaps and similarity graphs based on the results. + /// + /// The spatial pooler to use for the experiment. + private void RunRustructuringExperiment(SpatialPooler sp) + { + // Path to the folder containing training images + string trainingFolder = "Sample\\TestFiles"; + // Get all image files matching the specified prefix + var trainingImages = Directory.GetFiles(trainingFolder, $"{inputPrefix}*.png"); + // Size of the images + int imgSize = 28; + // Name for the test image + string testName = "test_image"; + // Array to hold active columns + int[] activeArray = new int[64 * 64]; + // List to store heatmap data + List> heatmapData = new List>(); + // Initialize a list to get normalized permanence values. + List BinarizedencodedInputs = new List(); + // List to store normalized permanence values + List normalizedPermanence = new List(); + // List to store similarity values + List similarityList = new List(); + foreach (var Image in trainingImages) + { + string inputBinaryImageFile = NeoCortexUtils.BinarizeImage($"{Image}", imgSize, testName); + + // Read input csv file into array + int[] inputVector = NeoCortexUtils.ReadCsvIntegers(inputBinaryImageFile).ToArray(); + + // Initialize arrays and lists for computations + int[] oldArray = new int[activeArray.Length]; + List overlapArrays = new List(); + List bostArrays = new List(); + + // Compute spatial pooling on the input vector + sp.compute(inputVector, activeArray, true); + var activeCols = ArrayUtils.IndexWhere(activeArray, (el) => el == 1); + + Dictionary reconstructedPermanence = sp.Reconstruct(activeCols); + + int maxInput = inputVector.Length; + + // Create a new dictionary to store extended probabilities + Dictionary allPermanenceDictionary = new Dictionary(); + // Iterate through all possible inputs using a foreach loop + foreach (var kvp in reconstructedPermanence) + { + int inputIndex = kvp.Key; + double probability = kvp.Value; + + // Use the existing probability + allPermanenceDictionary[inputIndex] = probability; + } + + //Assinginig the inactive columns Permanence 0 + for (int inputIndex = 0; inputIndex < maxInput; inputIndex++) + { + if (!reconstructedPermanence.ContainsKey(inputIndex)) + { + // Key doesn't exist, set the probability to 0 + allPermanenceDictionary[inputIndex] = 0.0; + } + } + + // Sort the dictionary by keys + var sortedAllPermanenceDictionary = allPermanenceDictionary.OrderBy(kvp => kvp.Key); + // Convert the sorted dictionary of allpermanences to a list + List permanenceValuesList = sortedAllPermanenceDictionary.Select(kvp => kvp.Value).ToList(); + + //Collecting Heatmap Data for Visualization + heatmapData.Add(permanenceValuesList); + + //Collecting Encoded Data for Visualization + BinarizedencodedInputs.Add(inputVector); + + //Normalizing Permanence Threshold + var ThresholdValue = 30.5; + + // Normalize permanences (0 and 1) based on the threshold value and convert them to a list of integers. + List normalizePermanenceList = Helpers.ThresholdingProbabilities(permanenceValuesList, ThresholdValue); + + //Collecting Normalized Permanence List for Visualizing + normalizedPermanence.Add(normalizePermanenceList.ToArray()); + + //Calculating Similarity with encoded Inputs and Reconstructed Inputs + var similarity = MathHelpers.JaccardSimilarityofBinaryArrays(inputVector, normalizePermanenceList.ToArray()); + + double[] similarityArray = new double[] { similarity }; + + //Collecting Similarity Data for visualizing + similarityList.Add(similarityArray); + } + // Generate the 1D heatmaps using the heatmapData list + Generate1DHeatmaps(heatmapData, BinarizedencodedInputs, normalizedPermanence); + // Generate the Similarity graph using the Similarity list + DrawSimilarityPlots(similarityList); + } + + /// + /// Generates 1D heatmaps based on the provided heatmap data and normalized permanence values. + /// + /// List of lists containing heatmap data. + /// List of arrays containing normalized permanence values. + private void Generate1DHeatmaps(List> heatmapData, List normalizedPermanence, List BinarizedencodedInputs) + { + int i = 1; + + foreach (var values in heatmapData) + { + // Define the folder path based on your requirements + string folderPath = Path.Combine(Environment.CurrentDirectory, "1DHeatMap_Image_Inputs"); + + // Create the folder if it doesn't exist + if (!Directory.Exists(folderPath)) + { + Directory.CreateDirectory(folderPath); + } + // Define the file path with the folder path + string filePath = Path.Combine(folderPath, $"heatmap_{i}.png"); + Debug.WriteLine($"FilePath: {filePath}"); + + // Convert the probabilitiesList to a 1D array using ToArray + double[] array1D = values.ToArray(); + + // Call the Draw1DHeatmap function with the dynamically generated file path along with all necessary Perameters + NeoCortexUtils.Draw1dHeatmap(new List() { array1D }, new List() { normalizedPermanence[i - 1] }, new List() { BinarizedencodedInputs[i - 1] }, filePath, 784, 15, 30, 15, 5, 30); + + Debug.WriteLine("Heatmap generated and saved successfully."); + i++; + } + + } + + // + /// Draws a combined similarity plot based on the provided list of arrays containing similarity values. + /// The combined similarity plot is generated by combining all similarity values from the list of arrays, + /// creating a single list of similarities, and then drawing the plot. + /// + /// List of arrays containing similarity values. + public static void DrawSimilarityPlots(List similaritiesList) + { + // Combine all similarities from the list of arrays + + List combinedSimilarities = new List(); + foreach (var similarities in similaritiesList) + + { + combinedSimilarities.AddRange(similarities); + } + + // Define the folder path based on the current directory + + string folderPath = Path.Combine(Environment.CurrentDirectory, "SimilarityPlots_Image_Inputs"); + + + // Create the folder if it doesn't exist + + if (!Directory.Exists(folderPath)) + { + Directory.CreateDirectory(folderPath); + } + + // Define the file name + string fileName = "combined_similarity_plot_Image_Inputs.png"; + + // Define the file path with the folder path and file name + + string filePath = Path.Combine(folderPath, fileName); + + // Draw the combined similarity plot + NeoCortexUtils.DrawCombinedSimilarityPlot(combinedSimilarities, filePath, 1000, 850); + + Debug.WriteLine($"Combined similarity plot generated and saved successfully."); + + } + } +} + diff --git a/source/Samples/NeoCortexApiSample/Program.cs b/source/Samples/NeoCortexApiSample/Program.cs index 4bb2cae18..506faf675 100644 --- a/source/Samples/NeoCortexApiSample/Program.cs +++ b/source/Samples/NeoCortexApiSample/Program.cs @@ -23,6 +23,11 @@ static void Main(string[] args) SpatialPatternLearning experiment = new SpatialPatternLearning(); experiment.Run(); + // Starts experiment For the Image Inputs how to learn spatial patterns. + // ImageBinarizerSpatialPattern experiment = new ImageBinarizerSpatialPattern(); + // experiment.Run(); + + // // Starts experiment that demonstrates how to learn spatial patterns. //SequenceLearning experiment = new SequenceLearning(); diff --git a/source/Samples/NeoCortexApiSample/SpatialPatternLearning.cs b/source/Samples/NeoCortexApiSample/SpatialPatternLearning.cs index b68d2af66..ab4b29c0b 100644 --- a/source/Samples/NeoCortexApiSample/SpatialPatternLearning.cs +++ b/source/Samples/NeoCortexApiSample/SpatialPatternLearning.cs @@ -1,4 +1,5 @@ -using NeoCortexApi; +using NeoCortex; +using NeoCortexApi; using NeoCortexApi.Encoders; using NeoCortexApi.Entities; using NeoCortexApi.Network; @@ -7,6 +8,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.IO; namespace NeoCortexApiSample { @@ -208,21 +210,200 @@ private static SpatialPooler RunExperiment(HtmConfig cfg, EncoderBase encoder, L return sp; } - + /// + /// Executes an experiment to analyze and visualize the behavior of a Spatial Pooler (SP) in response to a sequence of encoded input values. + /// This method systematically encodes each input value into a Sparse Distributed Representation (SDR) using the specified encoder (Scaler Encoder), + /// then processes these SDRs through the SP (Saptial Pooler Algorithm) to identify active columns. It reconstructs permanence values for these active columns, + /// normalizes them against a predefined threshold, and aggregates this data to generate visual heatmaps. These heatmaps illustrate + /// how the SP's internal representations of inputs evolve over time, enabling a deeper understanding of its learning and memory processes. + /// Additionally, the method assesses the SP's ability to adapt its synaptic connections (permanences) in response to the inputs, + /// thereby effectively 'training' the SP through exposure to the dataset. The experiment aims to shed light on the dynamics of synaptic + /// plasticity within the SP framework, offering insights that could guide the tuning of its parameters for improved performance in specific tasks. + /// + /// The Spatial Pooler instance to be used for the experiment. It processes input SDRs to simulate neural activity and synaptic plasticity. + /// The encoder used for converting raw input values into SDRs. The quality of encoding directly influences the SP's performance and the experiment's outcomes. + /// A list of input values to be encoded and processed through the SP. These values serve as the experimental dataset, exposing the SP to various patterns and contexts. + /// + /// The "maxInput" is the Maximum number of inputs to consider for reconstruction according to the size of Encoded Inputs. + /// The "thresholdValue" is Threshold value for permanence normalization to convert them as 0 and 1 Like the encoded Inputs. + /// Initializes lists to store heatmap data and normalized permanence values. + /// Iterates over each input value, encoding it, computing active columns, and reconstructing permanence values. + /// Populates a dictionary with all reconstructed permanence values and ensures representation up to a maximum input index. + /// Converts permanence values to a list and adds it to the heatmap data. + /// Outputs debug information about input values and corresponding Sparse Distributed Representation (SDR). + /// Defines a threshold for permanence normalization and normalizes permanences based on this threshold. + /// Adds normalized permanences to a list. + /// Calls a method to generate 1D heatmaps using the heatmap data and normalized permanences. + /// + + + + // Define a method to run the restructuring experiment, which takes a spatial pooler, an encoder, and a list of input values as arguments. private void RunRustructuringExperiment(SpatialPooler sp, EncoderBase encoder, List inputValues) { + // Initialize a list to get heatmap data for all input values. + List> heatmapData = new List>(); + + // Initialize a list to get normalized permanence values. + List normalizedPermanence = new List(); + + // Initialize a list to get normalized permanence values. + List encodedInputs = new List(); + + // Initialize a list to measure the similarities. + List similarityList = new List(); + + // Loop through each input value in the list of input values. foreach (var input in inputValues) { + // Encode the current input value using the provided encoder, resulting in an SDR var inpSdr = encoder.Encode(input); + // Compute the active columns in the spatial pooler for the given input SDR, without learning. var actCols = sp.Compute(inpSdr, false); - var probabilities = sp.Reconstruct(actCols); + // Reconstruct the permanence values for the active columns. + Dictionary reconstructedPermanence = sp.Reconstruct(actCols); - Debug.WriteLine($"Input: {input} SDR: {Helpers.StringifyVector(actCols)}"); + // Define the maximum number of inputs (Same size of encoded Inputs) to consider. + int maxInput = inpSdr.Length; + + // Initialize a dictionary to hold all permanence values, including those not reconstructed becuase of Inactive columns. + Dictionary allPermanenceDictionary = new Dictionary(); + + // Populate the all permanence dictionary with reconstructed permanence values. + foreach (var kvp in reconstructedPermanence) + { + int inputIndex = kvp.Key; + + double probability = kvp.Value; + + allPermanenceDictionary[inputIndex] = probability; + + } + // Ensure that all input indices up to the maximum are represented in the dictionary, even if their permanence is 0. + for (int inputIndex = 0; inputIndex < maxInput; inputIndex++) + { + + if (!reconstructedPermanence.ContainsKey(inputIndex)) + { + + allPermanenceDictionary[inputIndex] = 0.0; + } + } + // Sort the dictionary by keys + var sortedAllPermanenceDictionary = allPermanenceDictionary.OrderBy(kvp => kvp.Key); + + // Convert the sorted dictionary of all permanences to a list + List permanenceValuesList = sortedAllPermanenceDictionary.Select(kvp => kvp.Value).ToList(); + + heatmapData.Add(permanenceValuesList); + // Output debug information showing the input value and its corresponding SDR as a string. Debug.WriteLine($"Input: {input} SDR: {Helpers.StringifyVector(actCols)}"); + + // Define a threshold value for normalizing permanences, this value provides best Reconstructed Input + var ThresholdValue = 8.3; + + // Normalize permanences (0 and 1) based on the threshold value and convert them to a list of integers. + List normalizePermanenceList = Helpers.ThresholdingProbabilities(permanenceValuesList, ThresholdValue); + + // Add the normalized permanences to the list of all normalized permanences. + normalizedPermanence.Add(normalizePermanenceList.ToArray()); + + // Add the encoded bits to the list of all original encoded Inputs. + encodedInputs.Add(inpSdr); + + //Calling JaccardSimilarityofBinaryArrays function to measure the similarities + var similarity = MathHelpers.JaccardSimilarityofBinaryArrays(inpSdr, normalizePermanenceList.ToArray()); + double[] similarityArray = new double[] { similarity }; + // Add the Similarity Arrays to the list. + similarityList.Add(similarityArray); + } + // Generate 1D heatmaps using the heatmap data and the normalized permanences To plot Heatmap, Encoded Inputs and Normalize Image combined. + Generate1DHeatmaps(heatmapData, normalizedPermanence, encodedInputs); + // Plotting Graphs to Visualize Smililarities of Encoded Inputs and Reconstructed Inputs + DrawSimilarityPlots(similarityList); + } + + /// + /// Generates 1D heatmaps based on the provided heatmap data and normalized permanence values (Combined Image), and saves them to local Drive. + /// + /// A list containing the heatmap data for each input value. + /// A list containing the normalized permanence values for each input value. + private void Generate1DHeatmaps(List> heatmapData, List normalizedPermanence, List encodedInputs) + { + int i = 1; + + foreach (var values in heatmapData) + { + // Define the folder path from current Directory + string folderPath = Path.Combine(Environment.CurrentDirectory, "1DHeatMap"); + + // Create the folder if it doesn't exist + if (!Directory.Exists(folderPath)) + { + Directory.CreateDirectory(folderPath); + } + + // Define the file path with the folder path + string filePath = Path.Combine(folderPath, $"heatmap_{i}.png"); + //Debugging the Filepath + Debug.WriteLine($"FilePath: {filePath}"); + + // Convert the Values to a 1D array using ToArray + double[] array1D = values.ToArray(); + + // Call the Draw1DHeatmap function with the dynamically generated file path + NeoCortexUtils.Draw1dHeatmap(new List() { array1D }, new List() { normalizedPermanence[i - 1] }, new List() { encodedInputs[i - 1] }, filePath, 200, 12, 9, 4, 0, 30); + + //Debugging the Message + Debug.WriteLine("Heatmap generated and saved successfully."); + i++; + } + } + + /// + /// Draws a combined similarity plot based on the list of similarity arrays. + /// + /// The list of arrays containing similarity values to be combined and plotted. + /// + /// The method combines all similarity values from the list of arrays and generates a plot representing the combined data. + /// It creates a folder named "SimilarityPlots" in the current directory if it doesn't exist and saves the plot as a PNG image file named "combined_similarity_plot.png" within this folder. + /// Debugging information, including the generated file path and successful plot generation confirmation, is output using Debug.WriteLine. + /// + + public static void DrawSimilarityPlots(List similaritiesList) + { + // Combine all similarities from the list of arrays + List combinedSimilarities = new List(); + foreach (var similarities in similaritiesList) + { + combinedSimilarities.AddRange(similarities); + } + + // Define the folder path based on the current directory + string folderPath = Path.Combine(Environment.CurrentDirectory, "SimilarityPlots"); + + // Create the folder if it doesn't exist + if (!Directory.Exists(folderPath)) + { + Directory.CreateDirectory(folderPath); + } + + // Define the file name + string fileName = "combined_similarity_plot.png"; + + // Define the file path with the folder path and file name + string filePath = Path.Combine(folderPath, fileName); + + // Draw the combined similarity plot + NeoCortexUtils.DrawCombinedSimilarityPlot(combinedSimilarities, filePath, 4500, 1100); + //Debugging the Filepath + Debug.WriteLine($"FilePath: {filePath}"); + + Debug.WriteLine($"Combined similarity plot generated and saved successfully."); } } } diff --git a/source/UnitTestsProject/SdrReconstructionTests.cs b/source/UnitTestsProject/SdrReconstructionTests.cs index f16932865..59fa19208 100644 --- a/source/UnitTestsProject/SdrReconstructionTests.cs +++ b/source/UnitTestsProject/SdrReconstructionTests.cs @@ -9,21 +9,407 @@ using System.Net.Http.Headers; using Naa = NeoCortexApi.NeuralAssociationAlgorithm; using System.Diagnostics; +using NeoCortexEntities.NeuroVisualizer; +using Newtonsoft.Json.Linq; namespace UnitTestsProject { - /// - /// UnitTests for the Cell. - /// [TestClass] public class SdrReconstructionTests { + /// + /// Test Case Summary: + /// This test verifies the behavior of the Reconstruct method in the SPSdrReconstructor class under valid input conditions. + /// It ensures that the method returns a dictionary containing keys for all provided active mini-columns, with corresponding permanence values. + /// Additionally, it confirms that the method properly handles the case where a key is not present in the dictionary. + /// + + [TestCategory("SpatialPoolerReconstruction")] + [TestMethod] + [TestCategory("Prod")] + public void Reconstruct_ValidInput_ReturnsResult() + { + // Get HTM configuration + var cfg = UnitTestHelpers.GetHtmConfig(200, 1024); + // Create connections object with the specified configuration. + Connections mem = new Connections(cfg); + // Initialize a SpatialPoolerMT instance. + SpatialPoolerMT sp = new SpatialPoolerMT(); + sp.Init(mem); + // Create an instance of SPSdrReconstructor with the connections. + SPSdrReconstructor reconstructor = new SPSdrReconstructor(mem); + // Define active mini-columns. + int[] activeMiniColumns = new int[] { 0, 7, 2, 12, 24, 29, 37, 39, 46 }; + // Call the Reconstruct method with the defined scenario. + // Reconstruct the permanence values for the active columns. + Dictionary permanences = reconstructor.Reconstruct(activeMiniColumns); + + // Verify whether the dictionary returned by the Reconstruct method contains the expected keys and values. + + Assert.IsNotNull(permanences); + Assert.IsTrue(permanences.ContainsKey(0)); + Assert.AreEqual(2.8, permanences[0], 5.0); + Assert.IsTrue(permanences.ContainsKey(1)); + Assert.AreEqual(4.1, permanences[1], 5.5); + Assert.IsTrue(permanences.ContainsKey(2)); + Assert.AreEqual(3.5, permanences[2], 4.0); + Assert.IsTrue(permanences.ContainsKey(3)); + Assert.AreEqual(3.4, permanences[3], 7.0); + Assert.IsTrue(permanences.ContainsKey(4)); + Assert.AreEqual(5.5, permanences[4], 4.5); + // Validate whether a specific key is not present in the dictionary. + Assert.IsFalse(permanences.ContainsKey(101)); + } + + /// + /// Test Case Summary: + /// This test verifies that the Reconstruct method in the SPSdrReconstructor class correctly throws an ArgumentNullException + /// when invoked with a null input parameter. + /// + [TestCategory("ReconstructionExceptionHandling")] + [TestMethod] + [TestCategory("Prod")] + [ExpectedException(typeof(ArgumentNullException))] + public void Reconstruct_NullInput_ThrowsArgumentNullException() + { + // Test setup + var connections = new Connections(); + var reconstructor = new SPSdrReconstructor(connections); + + // Execution + reconstructor.Reconstruct(null); + + } + + /// + /// Test Case Summary: + /// This test checked that the Reconstruct method in the SPSdrReconstructor class returns an empty dictionary + /// + [TestCategory("ReconstructionEdgeCases")] + [TestMethod] + [TestCategory("Prod")] + public void Reconstruct_EmptyInput_ReturnsEmptyResult() + { + + var cfg = UnitTestHelpers.GetHtmConfig(200, 1024); + Connections mem = new Connections(cfg); + SpatialPoolerMT sp = new SpatialPoolerMT(); + sp.Init(mem); + SPSdrReconstructor reconstructor = new SPSdrReconstructor(mem); + + + Dictionary permanences = reconstructor.Reconstruct(new int[0]); + + + Assert.IsNotNull(permanences); + Assert.AreEqual(0, permanences.Count); + } + /// + /// This test falls under the category of "ReconstructionAllPositiveValues" and ensures that the Reconstruct method handles + /// a scenario where all mini-column indices provided as input are positive integers. The test checks whether the returned + /// permanence values are all non-negative, as expected. + /// + [TestCategory("ReconstructionAllPositiveValues")] + [TestMethod] + [TestCategory("Prod")] + public void Reconstruct_AllPositivePermanences_ReturnsExpectedValues() + { + // Get HTM configuration + var cfg = UnitTestHelpers.GetHtmConfig(200, 1024); + // Initialize Connections object + Connections mem = new Connections(cfg); + // Initialize SpatialPoolerMT object + SpatialPoolerMT sp = new SpatialPoolerMT(); + sp.Init(mem); + SPSdrReconstructor reconstructor = new SPSdrReconstructor(mem); + // Define active mini-columns array + int[] activeMiniColumns = new int[] { 1, 2, 3, 4, 5 }; + // Reconstruct permanences for active mini-columns + Dictionary permanences = reconstructor.Reconstruct(activeMiniColumns); + + Assert.IsNotNull(permanences); + + foreach (var value in permanences.Values) + { + Assert.IsTrue(value >= 0, $"Expected positive value, but got {value}"); + } + } + + /// + /// Tests whether SPSdrReconstructor's Reconstruct method adds a key to the dictionary if it doesn't already exist. + /// + /// + [TestCategory("ReconstructionAddingKey If not Exist")] [TestMethod] [TestCategory("Prod")] - public void CellCompareTest() + public void Reconstruct_AddsKeyIfNotExists() { + // Get HTM configuration + var cfg = UnitTestHelpers.GetHtmConfig(200, 1024); + + + // Initialize Connections object + Connections mem = new Connections(cfg); + + + // Initialize SpatialPoolerMT object + SpatialPoolerMT sp = new SpatialPoolerMT(); + sp.Init(mem); + + + // Initialize SPSdrReconstructor object + SPSdrReconstructor reconstructor = new SPSdrReconstructor(mem); + + + // Define active mini-columns array + int[] activeMiniColumns = new int[] { 1, 2, 3, 4, 5, 7, 20, 54, 700 }; + + + // Reconstruct permanences for active mini-columns + Dictionary permanences = reconstructor.Reconstruct(activeMiniColumns); + + + // Assert that the returned dictionary is not null + Assert.IsNotNull(permanences); + + + // Assert that the dictionary contains the key 1 + Assert.IsTrue(permanences.ContainsKey(1)); + + } + /// + /// Tests the behavior of SPSdrReconstructor's Reconstruct method to ensure it returns a valid dictionary. + /// + + + [TestCategory("ReconstructionReturnsKvP")] + [TestMethod] + [TestCategory("Prod")] + public void Reconstruct_ReturnsValidDictionary() + { + // Get HTM configuration + var cfg = UnitTestHelpers.GetHtmConfig(200, 1024); + + // Initialize Connections object + Connections mem = new Connections(cfg); + + + // Initialize SpatialPoolerMT object + SpatialPoolerMT sp = new SpatialPoolerMT(); + sp.Init(mem); + + // Initialize SPSdrReconstructor object + SPSdrReconstructor reconstructor = new SPSdrReconstructor(mem); + + + // Define active mini-columns array + int[] activeMiniColumns = new int[] { 1, 2, 3 }; + + + // Reconstruct permanences for active mini-columns + Dictionary permanences = reconstructor.Reconstruct(activeMiniColumns); + + + // Assert that the returned dictionary is not null + Assert.IsNotNull(permanences); + + + // Assert that all keys in the dictionary are of type int + Assert.IsTrue(permanences.Keys.All(key => key is int)); + + + // Assert that all values in the dictionary are of type double + Assert.IsTrue(permanences.Values.All(value => value is double)); + } + + /// + /// Tests the behavior of SPSdrReconstructor's Reconstruct method when negative permanences are encountered. + /// + /// + + [TestCategory("ReconstructedNegativePermanenceRetunsFalse")] + [TestMethod] + [TestCategory("Prod")] + public void Reconstruct_NegativePermanences_ReturnsFalse() + + { + // Get HTM configuration + var cfg = UnitTestHelpers.GetHtmConfig(200, 1024); + + // Initialize Connections object + Connections mem = new Connections(cfg); + + // Initialize SpatialPoolerMT object + SpatialPoolerMT sp = new SpatialPoolerMT(); + sp.Init(mem); + + // Initialize SPSdrReconstructor object + SPSdrReconstructor reconstructor = new SPSdrReconstructor(mem); + + // Define active mini-columns array + int[] activeMiniColumns = new int[] { 1, 2, 3, 4, 5 }; + + // Reconstruct permanences for active mini-columns + Dictionary permanences = reconstructor.Reconstruct(activeMiniColumns); + + // Assert that no negative permanences are present in the reconstructed values + Assert.IsFalse(permanences.Values.Any(value => value < 0), "Result should be false due to negative permanence values"); + } + /// + /// Tests the behavior of reconstructing permanences when at least one permanence value is negative. + /// The test initializes a spatial pooler configuration and connections, then reconstructs permanences using a set of active mini-columns. + /// It checks if the resulting dictionary of permanences contains any negative values, and asserts that at least one permanence value should be negative. + /// + [TestCategory("ReconstructedNegativePermanenceRetunsFalse")] + [TestMethod] + [TestCategory("Prod")] + public void Reconstruct_AtLeastOneNegativePermanence_ReturnsFalse() + { + // Initialize spatial pooler configuration and connections + var cfg = UnitTestHelpers.GetHtmConfig(200, 1024); + Connections mem = new Connections(cfg); + SpatialPoolerMT sp = new SpatialPoolerMT(); + sp.Init(mem); + // Initialize SPSdrReconstructor for reconstructing permanences + SPSdrReconstructor reconstructor = new SPSdrReconstructor(mem); + // Define a set of active mini-columns + int[] activeMiniColumns = new int[] { 1, 2, 3, 4, 5 }; + + // Reconstruct permanences based on the active mini-columns + Dictionary permanences = reconstructor.Reconstruct(activeMiniColumns); + // Assert that the reconstructed dictionary is not null + Assert.IsNotNull(permanences); + // Assert that at least one permanence value is negative + Assert.IsFalse(permanences.Values.Any(value => value < 0), "At least one permanence value should be negative"); + + + } + /// + /// Tests the behavior of reconstructing permanences when provided with an invalid dictionary. + /// The test initializes a spatial pooler configuration and connections, then reconstructs permanences using a set of active mini-columns. + /// It checks if the resulting dictionary of permanences is considered invalid, and asserts that the result should be false for an invalid dictionary. + /// + [TestCategory("DataIntegrityValidation")] + [TestMethod] + [TestCategory("Prod")] + public void Reconstruct_InvalidDictionary_ReturnsFalse() + { + + // Initialize spatial pooler configuration and connections + var cfg = UnitTestHelpers.GetHtmConfig(200, 1024); + + Connections mem = new Connections(cfg); + SpatialPoolerMT sp = new SpatialPoolerMT(); + sp.Init(mem); + // Initialize SPSdrReconstructor for reconstructing permanences + SPSdrReconstructor reconstructor = new SPSdrReconstructor(mem); + + // Define a set of active mini-columns + int[] activeMiniColumns = new int[] { 1, 2, 3 }; + + + // Reconstruct permanences based on the active mini-columns + Dictionary permanences = reconstructor.Reconstruct(activeMiniColumns); + + // Debug trace for reconstructed permanences + Debug.WriteLine("Reconstructed Permanences:"); + foreach (var kvp in permanences) + { + Debug.WriteLine($"Key: {kvp.Key}, Value: {kvp.Value}"); + } + // Assert that the reconstructed dictionary is not considered invalid + Assert.IsFalse(IsDictionaryInvalid(permanences), "Result should be false for an invalid dictionary"); + + + + } + /// + /// Determines whether a dictionary is considered invalid based on specific criteria. + /// + /// The dictionary to be checked. + /// True if the dictionary is invalid, otherwise false. + + [TestCategory("DictionaryValidityTests")] + [TestMethod] + [TestCategory("Prod")] + private bool IsDictionaryInvalid(Dictionary dictionary) + { + // Check if the dictionary reference is null, indicating invalidity. + if (dictionary == null) + { + return true; + } + // Check for invalid values or keys in the dictionary. + // Values containing NaN (Not-a-Number) are considered invalid. + // Additionally, any keys less than 0 are also considered invalid. + + + if (dictionary.Values.Any(value => double.IsNaN(value)) || dictionary.Keys.Any(key => key < 0)) + { + return true; + } + + return false; + } + + public class SPSdrReconstructor + { + private readonly Connections _mem; + + /// + /// Creates the constructor for SDR reconstruction. + /// + /// The HTM memory state. + public SPSdrReconstructor(Connections mem) + { + _mem = mem; + } + + /// + /// Reconstructs the input from the SDR produced by Spatial Pooler. + /// + /// The array of active mini columns. + /// Dictionary of inputs, with permanences resulted from acurrently active mini-columns. + /// + public Dictionary Reconstruct(int[] activeMiniColumns) + { + if (activeMiniColumns == null) + { + throw new ArgumentNullException(nameof(activeMiniColumns)); + } + + var cols = _mem.GetColumnList(activeMiniColumns); + + Dictionary result = new Dictionary(); + + // + // Iterate through all columns and collect all synapses. + foreach (var col in cols) + { + col.ProximalDendrite.Synapses.ForEach(s => + { + double currPerm = 0.0; + + // Check if the key already exists + if (result.TryGetValue(s.InputIndex, out currPerm)) + { + // Key exists, update the value + result[s.InputIndex] = s.Permanence + currPerm; + } + else + { + // Key doesn't exist, add a new key-value pair + result[s.InputIndex] = s.Permanence; + } + }); + } + + return result; + } + + } } }