Generic Algorithms in Visual Basic QuickStart Sample

Illustrates how to write algorithms that are generic over the numerical type of the arguments in Visual Basic.

View this sample in: C# F#

Option Infer On

' Basic generic types live in Numerics.NET.Generics.
Imports Numerics.NET.Generic
' We'll also need the big number types.
Imports Numerics.NET

    ' Illustrates writing generic algorithms that can be 
    ' applied to different operand types using the types in the 
    ' Numerics.NET.Generic namespace.
    Module GenericAlgorithms

        Sub main()
            ' We will implement a simple Newton-Raphson solver class.
            ' The code for the solver is below.

            ' Here we will call the generic solver with three
            ' different operand types: BigFloat, BigRational and Double.

            ' First, let's compute pi to 100 digits
            ' by solving the equation sin(x) == 0 with
            ' an initual guess of 3.
            Console.WriteLine("Computing pi by solving sin(x) = 0 with x0 = 3 using BigFloat.")
            ' Create the solver object.
            Dim bigFloatSolver As New Solver(Of BigFloat)
            ' Set the function to solve, and its derivative.
            ' These functions are defined below.
            bigFloatSolver.TargetFunction = AddressOf fBigFloat
            bigFloatSolver.DerivativeOfTargetFunction = AddressOf dfBigFloat
            ' Now solve to within a tolerance of 10^-100.
            Dim pi As BigFloat = bigFloatSolver.Solve(3, BigFloat.Pow(10, -100))
            ' Print the results...
            Console.WriteLine($"Computed value: {pi:F100}")
            ' and verify:
            Console.WriteLine("Known value:    {0:F100}", _
                BigFloat.GetPi(AccuracyGoal.Absolute(100)))
            Console.WriteLine()

            ' Next, we will use rational numbers to compute
            ' an approximation to the square root of 2.
            Console.WriteLine("Computing sqrt(2) by solving x^2 = 2 using BigRational.")
            ' Create the solver...
            Dim bigRationalSolver As New Solver(Of BigRational)()
            ' Set properties...
            bigRationalSolver.TargetFunction = AddressOf fBigRational
            bigRationalSolver.DerivativeOfTargetFunction = AddressOf dfBigRational
            ' Compute the solution...
            Dim sqrt2 As BigRational = bigRationalSolver.Solve(1, BigRational.Pow(10, -100))
            ' And print the result.
            Console.WriteLine($"Rational approximation: {sqrt2}")
            ' To verify, we convert the BigRational to a BigFloat:
            Console.WriteLine("As real number: {0:F100}", _
                New BigFloat(sqrt2, AccuracyGoal.Absolute(100), RoundingMode.TowardsNearest))
            Console.WriteLine("Known value:    {0:F100}", _
                BigFloat.Sqrt(2, AccuracyGoal.Absolute(100), RoundingMode.TowardsNearest))
            Console.WriteLine()

            ' Finally, we compute the Lambert W function at x = 3.
            Console.WriteLine("Computing Lambert's W at x = 3 using Double.")
            ' Create the solver...
            Dim doubleSolver As New Solver(Of Double)()
            ' Set properties...
            doubleSolver.TargetFunction = AddressOf fDouble
            doubleSolver.DerivativeOfTargetFunction = AddressOf dfDouble
            ' Compute the solution...
            Dim W3 As Double = doubleSolver.Solve(1.0, 0.000000000000001)
            ' And print the result.
            Console.WriteLine($"Solution:    {W3}")
            Console.WriteLine($"Known value: {Elementary.LambertW(3.0)}")

            ' Finally, we use generic functions:
            Console.WriteLine("To 100 digits (using BigFloat):")
            bigFloatSolver.TargetFunction = AddressOf fGeneric(Of BigFloat)
            bigFloatSolver.DerivativeOfTargetFunction = AddressOf dfGeneric(Of BigFloat)
            Dim bigW3 As BigFloat = bigFloatSolver.Solve(1, BigFloat.Pow(10, -100))
            Console.WriteLine($"Solution:    {bigW3:F100}")

            Console.Write("Press Enter key to exit...")
            Console.ReadLine()
        End Sub

        ' Functions for solving sin(x) = 0
        Function fBigFloat(x As BigFloat) As BigFloat
            Return BigFloat.Sin(x)
        End Function
        Function dfBigFloat(x As BigFloat) As BigFloat
            Return BigFloat.Cos(x)
        End Function

        ' Functions for solving x^2 - 2 = 0
        Function fBigRational(x As BigRational) As BigRational
            Return x * x - 2
        End Function
        Function dfBigRational(x As BigRational) As BigRational
            Return 2 * x
        End Function

        ' Functions for solving x*exp(x) = 3 (i.e. W(3))
        Function fDouble(x As Double) As Double
            Return x * Math.Exp(x) - 3
        End Function
        Function dfDouble(x As Double) As Double
            Return Math.Exp(x) * (1 + x)
        End Function

        ' Generic versions of the above
        Function fGeneric(Of T)(x As T) As T
            Return Operations(Of T).Subtract( _
                Operations(Of T).Multiply(x, Operations(Of T).Exp(x)), _
                Operations(Of T).FromInt32(3))
        End Function
        Function dfGeneric(Of T)(x As T) As T
            Return Operations(Of T).Multiply( _
                Operations(Of T).Exp(x), _
                Operations(Of T).Add(x, Operations(Of T).One))
        End Function

    End Module

' Class that contains the generic Newton-Raphson algorithm.
Class Solver(Of T)

    ' Member fields:
    Dim f, df As Func(Of T, T)
    Dim maxIters As Integer = 100

    ' The function to solve:
    Public Property TargetFunction() As Func(Of T, T)
        Get
            Return f
        End Get
        Set(value As Func(Of T, T))
            f = value
        End Set
    End Property

    ' The derivative of the function to solve.
    Public Property DerivativeOfTargetFunction() As Func(Of T, T)
        Get
            Return df
        End Get
        Set(value As Func(Of T, T))
            df = value
        End Set
    End Property

    ' The maximum number of iterations.
    Public Property MaxIterations() As Integer
        Get
            Return maxIters
        End Get
        Set(value As Integer)
            maxIters = value
        End Set
    End Property

    ' The core algorithm.
    ' Arithmetic operations are replaced by calls to
    ' methods on the arithmetic object (ops).
    Public Function Solve(initialGuess As T, tolerance As T) As T

        Dim iterations As Integer = 0

        Dim x As T = initialGuess
        Dim dx As T = Operations(Of T).Zero
        Do
            iterations = iterations + 1
            ' Compute the denominator of the correction term.
            Dim dfx As T = df(x)
            ' Relational operators map to the Compare method.
            ' We also use the value of zero for the operand type.
            ' if (dfx == 0)
            If Operations(Of T).EqualTo(dfx, Operations(Of T).Zero) Then
                ' Change value by 2x tolerance.
                ' When multiplying by a power of two, it's more efficient 
                ' to use the ScaleByPowerOfTwo method.
                dx = Operations(Of T).ScaleByPowerOfTwo(tolerance, 1)
            Else
                ' dx = f(x) / df(x)
                dx = Operations(Of T).Divide(f(x), dfx)
            End If
            ' x -= dx
            x = Operations(Of T).Subtract(x, dx)

            ' if |dx|^2<tolerance
            ' Convergence is quadratic (in most cases), so we should be good here:
            If Operations(Of T).LessThan(Operations(Of T).Multiply(dx, dx), tolerance) Then
                Return x
            End If
        Loop While (iterations < MaxIterations)
        Return x
    End Function

End Class