Imports System.IO
Imports System.Globalization

Public Class SOM
    Private outputs As Neuron(,)
    ' Collection of weights.
    Private iteration As Integer
    ' Current iteration.
    Private length As Integer
    ' Side length of output grid.
    Private dimensions As Integer
    ' Number of input dimensions.
    Private rnd As New Random()

    Private labels As New List(Of String)()
    Private patterns As New List(Of Double())()

    Public Sub New(ByVal dimensions As Integer, ByVal length As Integer, ByVal file As String)
        Me.length = length
        Me.dimensions = dimensions
        Initialise()
        LoadData(file)
        NormalisePatterns()
        Train(0.0000001)
        DumpCoordinates()
    End Sub

    Private Sub Initialise()
        outputs = New Neuron(length - 1, length - 1) {}

        For i As Integer = 0 To length - 1
            For j As Integer = 0 To length - 1
                outputs(i, j) = New Neuron(i, j, length)
                outputs(i, j).Weights = New Double(dimensions - 1) {}

                For k As Integer = 0 To dimensions - 1
                    outputs(i, j).Weights(k) = rnd.NextDouble()
                Next
            Next
        Next
    End Sub

    Private Sub LoadData(ByVal file__1 As String)
        Dim reader As StreamReader = File.OpenText(file__1)
        reader.ReadLine()
        ' Ignore first line.
        While Not reader.EndOfStream
            Dim line As String() = reader.ReadLine().Split(","c)
            labels.Add(line(0))
            Dim inputs As Double() = New Double(dimensions - 1) {}

            For i As Integer = 0 To dimensions - 1
                inputs(i) = Double.Parse(line(i + 1), CultureInfo.GetCultureInfo("en-US"))
            Next
            patterns.Add(inputs)
        End While
        reader.Close()
    End Sub

    Private Sub NormalisePatterns()
        For j As Integer = 0 To dimensions - 1
            Dim sum As Double = 0
            For i As Integer = 0 To patterns.Count - 1
                sum += patterns(i)(j)
            Next

            Dim average As Double = sum / patterns.Count

            For i As Integer = 0 To patterns.Count - 1
                patterns(i)(j) = patterns(i)(j) / average
            Next
        Next
    End Sub

    Private Sub Train(ByVal maxError As Double)
        Dim currentError As Double = Double.MaxValue

        While currentError > maxError
            currentError = 0
            Dim TrainingSet As New List(Of Double())()
            For Each pattern As Double() In patterns
                TrainingSet.Add(pattern)
            Next

            For i As Integer = 0 To patterns.Count - 1
                Dim pattern As Double() = TrainingSet(rnd.[Next](patterns.Count - i))
                currentError += TrainPattern(pattern)
                TrainingSet.Remove(pattern)
            Next
            'Console.WriteLine(currentError.ToString("0.0000000"))
        End While
    End Sub

    Private Function TrainPattern(ByVal pattern As Double()) As Double
        Dim [error] As Double = 0
        Dim winner__1 As Neuron = Winner(pattern)
        For i As Integer = 0 To length - 1
            For j As Integer = 0 To length - 1
                [error] += outputs(i, j).UpdateWeights(pattern, winner__1, iteration)
            Next
        Next
        iteration += 1
        Return Math.Abs([error] / (length * length))
    End Function

    Private Sub DumpCoordinates()
        For i As Integer = 0 To patterns.Count - 1
            Dim n As Neuron = Winner(patterns(i))
            'Console.WriteLine("{0},{1},{2}", labels(i), n.X, n.Y)
        Next
    End Sub

    Private Function Winner(ByVal pattern As Double()) As Neuron
        Dim owinner As Neuron = Nothing
        Dim min As Double = Double.MaxValue

        For i As Integer = 0 To length - 1
            For j As Integer = 0 To length - 1
                Dim d As Double = Distance(pattern, outputs(i, j).Weights)
                If d < min Then
                    min = d
                    owinner = outputs(i, j)
                End If
            Next
        Next
        Return owinner
    End Function

    Private Function Distance(ByVal vector1 As Double(), ByVal vector2 As Double()) As Double
        Dim value As Double = 0
        For i As Integer = 0 To vector1.Length - 1
            value += Math.Pow((vector1(i) - vector2(i)), 2)
        Next
        Return Math.Sqrt(value)
    End Function
End Class

