I was planning to try out an idea with the spatial pooler and the temporal pooler, and in the process I wrote a version of the spatial pooler in dot-net (vb.net). It does seem to work, the SDRs are different for the various inputs and related inputs have more similar SDRs than non-related inputs. I’m pasting the classes below, if anyone wants the full program I can post it on GitHub.
It does have limitations - I just wrote it for a the global neighborhood case, and to try it out I use a scalar encoder where the number 7 might consist (for example) of 20 bits and the number 8 might also be 20 bits, but shifted to the right by 1.
Anyway, since the idea I’m planning to try out has difficulties (trying to recreate the input of the SDR by looking at the active columns and the projections to them is unlikely to work), at least I’ve produced some working code, for anyone who wants to carry it further. Here it is:
' c:\MakeSDR\MakeSDR\SpatialPoolClass.vb
Public Class SpatialPoolClass
Structure TwoMaxStruct
Dim colindexMax As Integer
Dim MaxAverageOfOverlap As Double
Dim colindexSecondToMax As Integer
Dim SecondToMaxAverageOfOverlap As Double
Dim colindexMin As Integer
Dim MinAverageOfOverlap As Double
End Structure
Public columns() As spColumn
Public activeColumnIndices As List(Of Integer)
Public Sub New()
' every dendrite should have synapses connected to the input encoder. Their permanences should be set.
Dim HowManySynapses As Integer
Dim i As Integer
Dim j As Integer
Dim r As New Random
If ClassConstants.pEncoderType = ClassConstants.EnumEncoderType.GlobalEncode Then
ClassConstants.PotentialAreaMagnitude = Math.Round(ClassConstants.PotentialFraction * ClassConstants.pEncoderLength)
ClassConstants.PotentialStartIndexMax = ClassConstants.pEncoderLength - ClassConstants.PotentialAreaMagnitude
HowManySynapses = ClassConstants.bitsPerPattern
ReDim columns(ClassConstants.columnCount - 1)
For i = 0 To ClassConstants.columnCount - 1
Dim c As New spColumn(i, r)
For j = 0 To HowManySynapses - 1
c.addNewSynapse(r)
Next
c.initPermanences(r)
c.pDendrite.Sort()
columns(i) = c
Next
' columns(0).ClampDendriteForPatternZero() ' diagnostic!!!!
Else
Throw New Exception("Spatial Pooler 'Initialize' routine has not been implemented for non-scalar input-encodings")
End If
End Sub
Sub ComputeOverlapWithInput()
Dim c As spColumn
Dim s As Synapse
Dim numones As Integer
For Each c In columns
numones = 0
For Each s In c.pDendrite
If s.permanence >= ClassConstants.connectedPerm Then
If s.active Then
numones = numones + 1
End If
End If
Next
c.pOverlap = numones * c.pBoost
Next
End Sub
Sub UpdateSynapsePermanenceAndMore()
Dim index As Integer
Dim c As spColumn
Dim minMovingAverageOfOverlaps As Double
Dim s As Synapse
Dim maxesStruct As New TwoMaxStruct
Dim MovingAverageOfOverlaps As Double
Dim MovingAverageOfActivityOfNeighbors As Double
Dim sumMovingAveragesOfActiveCols As Double = 0
For Each index In activeColumnIndices
c = columns(index)
For Each s In c.pDendrite
If s.active Then
s.permanence = s.permanence + ClassConstants.synPermActiveInc
If s.permanence > 1 Then
s.permanence = 1
End If
Else
s.permanence = s.permanence - ClassConstants.synPermInactiveDec
If s.permanence < 0 Then
s.permanence = 0
End If
End If
Next
c.AddActiveValueToMemoryBuffer()
c.AddOverlapValueToMemoryBuffer()
Next
If ClassConstants.pEncoderType = ClassConstants.EnumEncoderType.GlobalEncode Then
GetSumActivesAndMovingAveragesOfOverlap(sumMovingAveragesOfActiveCols, maxesStruct)
End If
For Each c In columns
If ClassConstants.pEncoderType = ClassConstants.EnumEncoderType.GlobalEncode Then
MovingAverageOfActivityOfNeighbors = (sumMovingAveragesOfActiveCols - c.pMovingAverageOfActivity) / (columns.Length - 1)
minMovingAverageOfOverlaps = maxesStruct.MinAverageOfOverlap
Else
MovingAverageOfActivityOfNeighbors = getActiveMovingAverageOfNeighbors(c.pColindex)
minMovingAverageOfOverlaps = 0.01 * maxMovingAverageOfOverlap(getNeighbors(c.pNeighborIndices))
End If
c.pBoost = c.boostFunction(MovingAverageOfActivityOfNeighbors)
If MovingAverageOfOverlaps <= minMovingAverageOfOverlaps Then
c.increasePermanences(0.1 * ClassConstants.connectedPerm)
End If
Next
If ClassConstants.pEncoderType = ClassConstants.EnumEncoderType.GlobalEncode Then
ClassConstants.inhibitionRadius = ClassConstants.pEncoderLength
Else
ClassConstants.inhibitionRadius = averageReceptiveFieldSize()
End If
End Sub
Function getActiveMovingAverageOfNeighbors(ByVal cColindex As Integer) As Double
Dim i As Integer
Dim sum = 0
For i = 0 To columns.Count - 1
If i = cColindex Then
Continue For
End If
sum = sum + columns(i).pMovingAverageOfActivity
Next
Return sum / (columns.Length - 1)
End Function
Sub GetSumActivesAndMovingAveragesOfOverlap(ByRef sum As Double, ByRef thestruct As TwoMaxStruct)
Dim i As Integer
With thestruct
.colindexMax = 0
.MaxAverageOfOverlap = 0
.colindexSecondToMax = 0
.SecondToMaxAverageOfOverlap = 0
.colindexMin = 0
.MinAverageOfOverlap = 0
End With
sum = 0
thestruct.MaxAverageOfOverlap = columns(0).pMovingAverageOfOverlap
thestruct.MinAverageOfOverlap = columns(0).pMovingAverageOfOverlap
For i = 1 To columns.Count - 1
If columns(i).pMovingAverageOfOverlap > thestruct.MaxAverageOfOverlap Then
thestruct.MaxAverageOfOverlap = columns(i).pMovingAverageOfOverlap
thestruct.colindexMax = i
End If
If columns(i).pMovingAverageOfOverlap < thestruct.MinAverageOfOverlap Then
thestruct.MinAverageOfOverlap = columns(i).pMovingAverageOfOverlap
thestruct.colindexMin = i
End If
Next
For i = 0 To columns.Count - 1
If columns(i).pMovingAverageOfOverlap > thestruct.SecondToMaxAverageOfOverlap And
columns(i).pMovingAverageOfOverlap <> thestruct.MaxAverageOfOverlap Then
thestruct.SecondToMaxAverageOfOverlap = columns(i).pMovingAverageOfOverlap
thestruct.colindexSecondToMax = i
End If
Next
For i = 0 To columns.Count - 1
sum = sum + columns(i).pMovingAverageOfOverlap
Next
End Sub
Function getNeighbors(ByRef neighborindices As List(Of Integer)) As List(Of spColumn)
Dim nc As New List(Of spColumn)
Dim i As Integer
For i = 0 To neighborindices.Count - 1
nc.Add(columns(i))
Next
Return nc
End Function
Public Sub ComputeOnce(ByVal train As Boolean)
ComputeOverlapWithInput()
' MessageBox.Show(classPrint.printOverlaps(15))
ComputeWinningColumns()
If train Then
UpdateSynapsePermanenceAndMore()
End If
End Sub
Function kthScore(ByRef cols As List(Of spColumn), ByVal k As Integer, ByVal colindex As Integer) As Double
' return k'th highest overlap value.
' this assumes that overlap was calculated earlier for each column in the list
' it assumes k starts at 1
' it assumes columns are sorted by activity in increasing order
Dim i As Integer
Dim j As Integer
If ClassConstants.pEncoderType = ClassConstants.EnumEncoderType.GlobalEncode Then
If cols.Count = 0 Then
Return 0
End If
i = cols.Count - k
If i < 0 Then
Return 0
End If
If colindex > -1 Then
For j = i To cols.Count - 1
If cols(j).pColindex = colindex Then
i = i - 1
Exit For
End If
Next
End If
Else
Throw New Exception("kth score not implemented for non global case")
End If
Return cols(i).pOverlap
End Function
Public Function getMinimumOverlapOfActiveCols() As Integer
Dim collist As List(Of spColumn)
collist = columns.ToList
collist.Sort()
Dim li As List(Of Integer)
Dim colindex As Integer
li = GetActiveColumnsUsingkthScoreGlobal(collist)
If li.Count = 0 Then
Throw New Exception("no active columns in getMinimumOverlapOfActiveCols")
End If
colindex = li(0)
Return columns(colindex).pOverlap
End Function
Public Function GetActiveColumnsUsingkthScoreGlobal(collist As List(Of spColumn)) As List(Of Integer)
Dim activelist As New List(Of Integer)
Dim startI As Integer
Dim i As Integer
If collist.Count = 0 Then
Return activelist
End If
startI = collist.Count - ClassConstants.numActiveColumnsPerinhArea
If startI < 0 Then
startI = 0
End If
For i = startI To collist.Count - 1
activelist.Add(collist(i).pColindex)
Next
Return activelist
End Function
Sub ComputeWinningColumns()
' assuming global inhibition area.
Dim c As spColumn
Dim minLocalActivity As Double
Dim collist As List(Of spColumn)
Dim index As Integer
Dim locallist As New List(Of Integer)
activeColumnIndices = New List(Of Integer)
If ClassConstants.pEncoderType = ClassConstants.EnumEncoderType.GlobalEncode Then
collist = columns.ToList
collist.Sort()
locallist = GetActiveColumnsUsingkthScoreGlobal(collist)
' the above gets the top 10 columns (in overlap)
For Each c In columns
c.pActive = False
Next
For Each index In locallist
c = columns(index)
If c.pOverlap > ClassConstants.stimulusThreshold And c.pOverlap >= minLocalActivity Then
c.pActive = True
activeColumnIndices.Add(c.pColindex)
End If
Next
Else
Throw New Exception("'ComputeWinningColumns' is not implemented for non-global case.")
End If
End Sub
Public Function averageReceptiveFieldSize() As Double
Dim c As spColumn
Dim sum As Double
For Each c In columns
sum = sum + c.pReceptiveFieldSize
Next
Return (sum / columns.Count)
End Function
Function maxMovingAverageOfOverlap(ByVal cols As List(Of spColumn)) As Double
Dim i As Integer
Dim maxdc As Double = 0
For i = 0 To cols.Count - 1
If maxdc < cols(i).pMovingAverageOfOverlap Then
maxdc = cols(i).pMovingAverageOfOverlap
End If
Next
Return maxdc
End Function
End Class
' c:\MakeSDR\MakeSDR\spColumn.vb
Imports MakeSDR
Imports System.Runtime.Serialization.Formatters.Binary
Imports System.IO
Imports System.Text
<Serializable()>
Public Class spColumn
Implements IComparable(Of spColumn)
Private _boost As Double = 1 ' to start
Private _overlap As Integer
Private _movingAverageOfActivity As Double
Private _movingAverageOfOverlap As Double
Private _active As Boolean
Private _activeCbuf As New CircularBufferBoolean(1000)
Private _overlapCbuf As New CircularBufferInteger(1000)
Private _dendrite As New List(Of Synapse)
Private _colindex As Integer
Private PotentialAreaStartIndex As Integer = 0
' Private _naturalCenter As Integer
Public Sub New(ByVal colindex As Integer, ByRef theRandom As Random)
' Dim bitspercol As Double
PotentialAreaStartIndex = theRandom.Next(0, ClassConstants.PotentialStartIndexMax + 1) ' have to add one because 'next' doesn't include upper bound
_colindex = colindex
' comment out for now:
'If ClassConstants.pEncoderType = ClassConstants.EnumEncoderType.GlobalEncode Then
'
' ' If ClassConstants.columnCount = ClassConstants.pEncoderLength Then
' ' _naturalCenter = colindex
' 'Else
' ' bitspercol = ClassConstants.pEncoderLength / ClassConstants.columnCount
' ' _naturalCenter = bitspercol * colindex + bitspercol / 2.0 ' wait, not have PotentialAreaStartIndex and Area
' 'End If
'Else
' Throw New Exception("have not implemented 'natural center' for non-global case")
'End If
End Sub
Public ReadOnly Property pMovingAverageOfActivity As Double
Get
_movingAverageOfActivity = _activeCbuf.MovingAverage
Return _movingAverageOfActivity
End Get
End Property
Public Sub addNewSynapse(ByRef r As Random)
Dim se As New Synapse
Dim sii As Integer
With se
Do While True
sii = r.Next(PotentialAreaStartIndex, ClassConstants.PotentialAreaMagnitude + PotentialAreaStartIndex)
If sii >= ClassConstants.pEncoderLength Then ' this would happen with a wrap-around encoder
sii = sii - ClassConstants.pEncoderLength
End If
If Not DendriteAlreadyContainsInput(sii) Then
Exit Do
End If
Loop
.sourceInputIndex = sii
End With
_dendrite.Add(se)
End Sub
Public Function DendriteAlreadyContainsInput(ByVal sourceindex As Integer) As Boolean
Dim i As Integer
For i = 0 To pDendrite.Count - 1
If pDendrite(i).sourceInputIndex = sourceindex Then
Return True
End If
Next
Return False
End Function
Public Function printDendrite() As String
Dim sb As New StringBuilder("")
Dim i As Integer
For i = 0 To _dendrite.Count - 1
sb.AppendLine(_dendrite(i).printself)
Next
Return sb.ToString
End Function
Public Sub initPermanences(ByRef r As Random)
' _naturalCenter = getNaturalCenter()
Dim i As Integer
Dim nd As Double
Dim variance As Double
variance = ClassConstants.connectedPerm * 0.7
' Dim distance As Double
For i = 0 To _dendrite.Count - 1
With _dendrite(i)
' distance = Math.Abs(.sourceInputIndex - _naturalCenter) / (ClassConstants.columnCount - 1)
'distance = Math.Ceiling(distance * 10)
'If distance > 10 Then
' distance = 10
'End If
nd = r.NextDouble() * variance * 2 - variance ' nextdouble >= 0, but less than 1. Suppose variance is .2.
' in that case, the maximum 'nd' can be is 1 * .2 * 2 - .2 or .2
' the least it can be is 0 - .2 or -.2
.permanence = ClassConstants.connectedPerm + nd ' + 0.02 / distance
End With
Next
End Sub
Public ReadOnly Property pReceptiveFieldSize As Integer
Get
Dim i As Integer
Dim sum As Integer = 0
For i = 0 To _dendrite.Count - 1
If _dendrite(i).permanence >= ClassConstants.connectedPerm Then
sum = sum + 1
End If
Next
Return sum
End Get
End Property
Public Sub increasePermanences(ByVal scalefactor As Double)
Dim i As Integer
For i = 0 To _dendrite.Count - 1
With _dendrite(i)
.permanence = .permanence * (1 + scalefactor)
End With
Next
End Sub
Public Function boostFunction(ByVal MovingAverageOfActivityOfNeighbors) As Double
' if activity of column equals activity of neighbors, return 1
' if activity of column is greater than activity of neighbors, return a number less than 1
' otherwise, return a number greater than 1. (at most it could be 'e' which is about 2.7183))
If ClassConstants.pEncoderType <> ClassConstants.EnumEncoderType.GlobalEncode Then
Throw New Exception("boostFunction is not implemented for columns that do not have global inputs")
End If
Dim diff As Double
Dim expdiff As Double
diff = pMovingAverageOfActivity - MovingAverageOfActivityOfNeighbors
expdiff = Math.Exp(diff * ClassConstants.boostStrength * -1)
Return expdiff
End Function
Public Property pBoost As Double
Get
Return _boost
End Get
Set(value As Double)
_boost = value
End Set
End Property
Public Function UpdateOverlapMovingAverage() As Double
' some routine before this must have set .poverlap, or this will not work
AddOverlapValueToMemoryBuffer()
Return pMovingAverageOfOverlap
End Function
Sub New()
End Sub
Public Function DeepCopyMe() As spColumn
Dim m As MemoryStream = New MemoryStream()
Dim b As BinaryFormatter = New BinaryFormatter()
b.Serialize(m, Me)
m.Position = 0
Return CType(b.Deserialize(m), spColumn)
End Function
Public ReadOnly Property pDendrite As List(Of Synapse)
Get
Return _dendrite
End Get
End Property
Public ReadOnly Property pColindex As Integer
Get
Return _colindex
End Get
End Property
Public ReadOnly Property pNeighborIndices As List(Of Integer)
Get
Dim neighborindices As New List(Of Integer)
If ClassConstants.pEncoderType = ClassConstants.EnumEncoderType.GlobalEncode Then
Dim newlist As New List(Of Integer)
Dim i As Integer
For i = 0 To ClassConstants.columnCount - 1
If i = pColindex Then
Continue For
End If
newlist.Add(i)
Next
neighborindices = newlist
Return neighborindices
Else
Throw New Exception("pNeighborIndices property has been coded only for scalar encodings (global)")
End If
End Get
End Property
Public Property pOverlap As Integer
Get
Return _overlap
End Get
Set(value As Integer)
_overlap = value
End Set
End Property
Public Property pActive As Boolean
Get
Return _active
End Get
Set(value As Boolean)
_active = value
End Set
End Property
Public ReadOnly Property pMovingAverageOfOverlap As Double
Get
_movingAverageOfOverlap = _overlapCbuf.MovingAverage(ClassConstants.stimulusThreshold)
Return _movingAverageOfOverlap
End Get
End Property
Public Sub AddActiveValueToMemoryBuffer()
_activeCbuf.AddToBuffer(pActive)
End Sub
Public Sub AddOverlapValueToMemoryBuffer()
_overlapCbuf.AddToBuffer(pOverlap)
End Sub
Public Function CompareTo(other As spColumn) As Integer Implements IComparable(Of spColumn).CompareTo
If _overlap = other.pOverlap Then
Return 0
Else
If _overlap < other.pOverlap Then
Return -1
Else
Return 1
End If
End If
End Function
Private Function getNaturalCenter() As Integer
Dim i As Integer
Dim listindices As New List(Of Integer)
For i = 0 To pDendrite.Count - 1
listindices.Add(pDendrite(i).sourceInputIndex)
Next
Return utils.median(listindices)
End Function
Public Function showColumnFields() As String
Dim sb As New StringBuilder("")
sb.AppendLine("column # " & _colindex)
sb.AppendLine("boost is " & Math.Round(_boost, 2))
sb.AppendLine("overlap is " & _overlap)
sb.AppendLine("movingAverageOfActivity is " & Math.Round(_movingAverageOfActivity, 2))
sb.AppendLine("movingAverageOfOverlap is " & Math.Round(_movingAverageOfOverlap, 2))
sb.AppendLine("_active is " & _active)
sb.AppendLine("_activeCbuf is " & _activeCbuf.pMostRecentValues)
sb.AppendLine("_overlapCbuf is " & _overlapCbuf.pMostRecentValues)
sb.AppendLine("dendrite follows: ")
sb.AppendLine(printDendrite())
sb.AppendLine("PotentialAreaStartIndex is " & PotentialAreaStartIndex)
Return sb.ToString
End Function
Public Sub ClampDendriteForPatternZero()
Dim i As Integer
For i = 0 To pDendrite.Count - 1
If i < ClassConstants.bitsPerPattern Then
pDendrite(i).sourceInputIndex = i
Else
pDendrite(i).sourceInputIndex = 0
pDendrite(i).permanence = 0
End If
Next
End Sub
End Class
' c:\MakeSDR\MakeSDR\Synapse.vb
<Serializable()>
Public Class Synapse
Implements IComparable(Of Synapse)
Public permanence As Double
Public sourceInputIndex As Integer
Public Function active() As Boolean
If ClassConstants.encodedinput(sourceInputIndex) Then
Return True
Else
Return False
End If
End Function
Public Function printself() As String
Return ("input[" & sourceInputIndex & "] - permanence is " & Math.Round(permanence, 1) & " " & utils.ShowBoolAsAsterix(active))
End Function
Public Function CompareTo(other As Synapse) As Integer Implements IComparable(Of Synapse).CompareTo
If sourceInputIndex = other.sourceInputIndex Then
Return 0
Else
If sourceInputIndex < other.sourceInputIndex Then
Return -1
Else
Return 1
End If
End If
End Function
End Class
' c:\MakeSDR\MakeSDR\ClassConstants.vb
Imports System.Text
Public Class ClassConstants
Private Shared _previousListOfAskIndices As New List(Of Integer)
Private Shared _previousListOfAskIndices2 As New List(Of Integer)
Private Shared _encoderType As EnumEncoderType
Public Shared PotentialAreaMagnitude As Integer
Public Shared CopyOfColumns() As spColumn = Nothing
Public Shared PotentialStartIndexMax As Integer = 0
Public Shared columnCount As Integer = 2048
Public Shared numActiveColumnsPerinhArea As Integer = 40 ' dependent on inhibition radius
Public Shared maxBoost As Double = 1 ' for simple scenarios
Public Shared PotentialFraction As Double = 0.8 ' dependent on how many on-bits are in a input, and how many synapses are originally connected
Public Shared connectedPerm As Double = 0.3
Public Shared synPermActiveInc As Double = 0.03 ' data dependent
Public Shared synPermInactiveDec As Double = 0.015 ' data dependent
Public Shared excludeList As List(Of Double)
Public Shared encoderDimensions As Integer = 1
Public Shared encodedinput() As Boolean
' Public Shared reconstructedInput() As Boolean = Nothing
Public Shared stimulusThreshold As Integer = 0
Public Shared buckets As Integer = 100
Public Shared MaxPattern As Double = 100
Public Shared MinPattern As Double = 0
Public Shared bitsPerPattern As Integer = 21
Public Shared boostStrength As Double = 1
Public Shared sp As SpatialPoolClass
' calculated values:
Public Shared inhibitionRadius As Integer
Private Shared _encoderlength As Integer = 0
Public Enum EnumEncoderType
Other = 0
GlobalEncode = 1
localEncode = 2
End Enum
Public Shared Property pPreviousListOfAskIndices2 As List(Of Integer)
Get
Return _previousListOfAskIndices2
End Get
Set(value As List(Of Integer))
_previousListOfAskIndices2 = value
End Set
End Property
Public Shared Property pPreviousListOfAskIndices As List(Of Integer)
Get
Return _previousListOfAskIndices
End Get
Set(value As List(Of Integer))
_previousListOfAskIndices = value
End Set
End Property
Public Shared Property pEncoderType As EnumEncoderType
Get
Return _encoderType
End Get
Set(value As EnumEncoderType)
_encoderType = value
End Set
End Property
Public Shared Property pEncoderLength As Integer
Get
Return _encoderlength
End Get
Set(value As Integer)
_encoderlength = value
inhibitionRadius = value
ReDim encodedinput(_encoderlength)
End Set
End Property
End Class
' c:\MakeSDR\MakeSDR\encoder.vb
Imports System.Text
Public Class encoder
Public Shared Function CalculateEncoderLength() As Integer
Dim n As Integer
n = ClassConstants.buckets + ClassConstants.bitsPerPattern - 1
Return n
End Function
Public Shared Sub createglobalEncoderPattern(ByVal encodethis As Integer, ByRef encodedinput() As Boolean)
Dim i As Integer
Dim range As Double
Dim startat As Integer
range = ClassConstants.MaxPattern - ClassConstants.MinPattern
startat = Math.Floor(ClassConstants.buckets * (encodethis - ClassConstants.MinPattern) / range)
cleartheinputpattern(encodedinput)
For i = startat To startat + ClassConstants.bitsPerPattern - 1
encodedinput(i) = True
Next
End Sub
Public Shared Sub cleartheinputpattern(ByRef encodedinput() As Boolean)
Dim i As Integer
For i = 0 To encodedinput.Length - 1
encodedinput(i) = False
Next
End Sub
Public Shared Sub DisplayEncodedInput()
Dim sb As New StringBuilder("")
For i = 0 To ClassConstants.encodedinput.Length - 1
If i > 0 Then
sb.Append(",")
End If
If ClassConstants.encodedinput(i) Then
sb.Append("1")
Else
sb.Append("0")
End If
Next
MessageBox.Show(sb.ToString)
End Sub
End Class
' c:\MakeSDR\MakeSDR\utils.vb
Public Class utils
Public Shared Function ShowBoolAsAsterix(ByVal inBool As Boolean) As String
If inBool Then
Return "*"
Else
Return ""
End If
End Function
Public Shared Function median(ByVal thelist As List(Of Integer)) As Double
thelist.Sort()
Dim thecount As Double = thelist.Count
thecount = thecount / 2
If thecount = 1 Then
Return thelist(0)
End If
If isEven(thecount) Then
Return (thelist(thecount) + thelist(thecount - 1)) / 2
Else
Return thelist(Math.Floor(thecount))
End If
End Function
Public Shared Function isEven(ByVal theCount As Double) As Boolean
If theCount / 2 = Math.Floor(theCount / 2) Then
Return True
Else
Return False
End If
End Function
End Class