Blog

Archive for the ‘NMath Tutorial’ Category

Using NMath with Microsoft Chart Controls for .NET

Wednesday, March 9th, 2011

In 2007, Microsoft acquired the Dundas chart components, in order to deliver data visualization directly within Microsoft products. In October 2008, they released the Microsoft Chart Controls for .NET, which includes the Dundas ASP.NET and Windows Forms Chart controls. The Chart controls are available as a separate download for .NET 3.5. Beginning in .NET 4.0, the Chart controls are part of the .NET Framework. To use the Chart controls, add a reference to System.Windows.Forms.DataVisualization.

A Chart object contains one or more ChartAreas, each of which contain one or more data Series. Each Series has an associated chart type, and a DataPoint collection. DataPoints can be manually appended or inserted into the collection, or added automatically when a series is bound to a datasource using either the DataBindY() or DataBindXY() method. Since any IEnumerable can act as a datasource, it’s easy to use an NMath vector or vector view (of a matrix row or column, for example) as a datasource.

For example, suppose we want to create a scatter plot of the first two columns of a 20 x 5 DoubleMatrix (that is, column 0 vs. column 1).

DoubleMatrix data = new DoubleMatrix( 20, 5, new RandGenUniform() );
DoubleVector x = data.Col( 0 );
DoubleVector y = data.Col( 1 );

Begin by creating a new Chart object, and optionally adding a Title.

Chart chart = new Chart()
{
  Size = new Size( 500, 500 ),
};
 
Title title = new Title()
{
  Name = chart.Titles.NextUniqueName(),
  Text = "My Data",
  Font = new Font( "Trebuchet MS", 12F, FontStyle.Bold ),
};
chart.Titles.Add( title );

Next, add a ChartArea.

ChartArea area = new ChartArea()
{
  Name = chart.ChartAreas.NextUniqueName(),
};
area.AxisX.Title = "Col 0";
area.AxisX.TitleFont = new Font( "Trebuchet MS", 10F, FontStyle.Bold );
area.AxisX.MajorGrid.LineColor = Color.LightGray;
area.AxisX.RoundAxisValues();
area.AxisY.Title = "Col 1";
area.AxisY.TitleFont = new Font( "Trebuchet MS", 10F, FontStyle.Bold );
area.AxisY.MajorGrid.LineColor = Color.LightGray;
area.AxisY.RoundAxisValues();
chart.ChartAreas.Add( area );

Finally, add a new data Series, and bind the datasource to the NMath x,y vectors.

Series series = new Series()
{
  Name = "Points",
  ChartType = SeriesChartType.Point,
  MarkerStyle = MarkerStyle.Circle,
  MarkerSize = 8,
};
series.Points.DataBindXY( x, y );
chart.Series.Add( series );

To display the chart, we can use a utility function like this, which shows the given chart in a default form running in a new thread.

public static void Show( Chart chart )
{
  Form form = new Form();
  form.Size = new Size( chart.Size.Width + 20, chart.Size.Height + 40 );
  form.Controls.Add( chart );
  Thread t = new Thread( () => Application.Run( form ) );
   t.Start();
}

After calling

Show( chart );

the result looks like this:

Microsoft Chart Controls for .NET

Of course, you can easily plot more complicated data as well. For instance, suppose we fit a 4th degree polynomial to this (random) data:

int degree = 4;
PolynomialLeastSquares pls = new PolynomialLeastSquares( degree, x, y );

To add the fitted polynomial to the cart, just add a new data Series interpolating over the range of x values.

Series series2 = new Series()
{
  Name = "Polynomial",
  ChartType = SeriesChartType.Line,
  MarkerStyle = MarkerStyle.None,
};
int numInterpolatedValues = 100;
double xmin = NMathFunctions.MinValue( x );
double xmax = NMathFunctions.MaxValue( x );
double step = ( xmax - xmin ) / ( numInterpolatedValues - 1 );
DoubleVector xi = new DoubleVector( numInterpolatedValues, xmin, step );
series2.Points.DataBindXY( xi, pls.FittedPolynomial.Evaluate( xi ) );
chart.Series.Add( series2 );

Let’s also add a subtitle with the fitted function, and a Legend.

Title subtitle = new Title()
{
  Name = chart.Titles.NextUniqueName(),
  Text = "f(x) = " + pls.FittedPolynomial.ToString( "N2" ),
};
chart.Titles.Add( subtitle );
 
Legend legend = new Legend()
{
  Name = chart.Legends.NextUniqueName(),
  DockedToChartArea = chart.ChartAreas[0].Name,
};
chart.Legends.Add( legend );

Now the chart looks like this:

If you add a Chart control to your application using the visual designer (by dragging a Chart control from the Data category in the Toolbox), this generates code to create a default Chart, with a single ChartArea, Series, and Legend. You can then edit the properties in the Properties tab, and bind the generated Series to an NMath datasource. For example:

public Form1()
{
  InitializeComponent();
 
  DoubleMatrix data = new DoubleMatrix( 20, 5, new RandGenUniform() );
  this.chart1.Series[0].Points.DataBindXY( data.Col(0), data.Col(1) );
}

The Microsoft Chart Controls for .NET provide an easy (and free) solution for visualizing NMath numerical types.

Ken

Share

Quasirandom Points

Wednesday, February 9th, 2011

A quasirandom sequence is a set of n-tuples that fills n-space more uniformly than uncorrelated random points. NMath provides both Sobol and Niederreiter quasirandom number generators for .NET applications.

For example, this C# code creates a Niederreiter quasirandom number generator object to generate quasirandom vectors of length 2:

int dim = 2;
NiederreiterQuasiRandomGenerator nqrg =
  new NiederreiterQuasiRandomGenerator(dim);

You can use an NMath quasirandom generator to fill a matrix or array. The points are the columns of the matrix, so the number of rows in the given matrix must be equal to the dimension of the quasirandom number generator. Here we create and fill a 2 x 500 matrix with quasirandom numbers from a uniform (0,1) distribution.

int n = 500;
DoubleMatrix A = new DoubleMatrix(dim, n);
nqrg.Fill(A);

We can plot the points to see the distribution.

Quasirandom Points

Let’s compare the results above with the distribution from a standard pseudorandom generator.

int seed = 0x345;
RandomNumberStream stream = new RandomNumberStream( seed, RandomNumberStream.BasicRandGenType.MersenneTwister );
DoubleRandomUniformDistribution unifDist = new DoubleRandomUniformDistribution();
DoubleMatrix B = new DoubleMatrix( dim, n, stream, unifDist );

Plotting the results shows that the pseudorandom points are much less uniformly distributed. Regions of higher and lower density are clearly evident.

Quasirandom numbers have many applications, especially in Monte Carlo simulations. For instance, a classic use of the Monte Carlo method is for the evaluation of definite integrals. Let’s use an NMath Sobol generator to approximate the integral of a function F over a 6-dimensional unit cube. The function is defined by

F(x) = 1*cos(1*x1) * 2*cos(2*x2) * 3*cos(3*x3) *…* 6*cos(6*x6)

where x = (x1, x2,…, x6) is a point in 6-dimensional space.

This is the implementation of the function F:

static double F( DoubleVector x )
{
  double y = 1;
  for ( int i = 1; i <= x.Length; i++ )
  {
    y *= i * Math.Cos( i * x[i - 1] );
  }
  return y;
}

In the following C# code we provide our own primitive polynomials for initializing the quasirandom generator. Primitive polynomials have coefficients in {0, 1} and are specified by BitArray’s containing the polynomial coefficients with the leading coefficient at index 0. We can supply either dim polynomials, or dim – 1 polynomials. If we specify dim – 1 polynomials, the primitive polynomial for the first dimension will be initialized with a default value.

dim = 6;
 
BitArray[] primitivePolys = new BitArray[dim];
primitivePolys[0] = new BitArray( new bool[] { true, true } ); // x + 1
primitivePolys[1] = new BitArray( new bool[] { true, true, true } ); // x^2 + x + 1
primitivePolys[2] = new BitArray( new bool[] { true, false, true, true } ); // x^3 + x + 1
primitivePolys[3] = new BitArray( new bool[] { true, true, false, true } ); // x^3 + x^2 + 1
primitivePolys[4] = new BitArray( new bool[] { true, false, false, true, true } ); // x^4 + x + 1
primitivePolys[5] = new BitArray( new bool[] { true, true, false, false, true } ); // x^4 + x^3 + 1
 
SobolQuasiRandomGenerator sobol = new SobolQuasiRandomGenerator( dim, primitivePolys );

Now let’s use this generator to approximate the integral:

int numPoints = 180000;
DoubleMatrix points = new DoubleMatrix( dim, numPoints );
sobol.Fill( points );
 
double sum = 0;
for ( int i = 0; i < numPoints; i++ )
{
  sum += F( points.Col( i ) );
}
sum /= numPoints;
 
double actualIntegralValue = -0.022;
Console.WriteLine( "Actual integral value = " + actualIntegralValue );
Console.WriteLine( "\nMonte-Carlo approximated integral value = " + sum );

The output is:

Actual integral value = -0.022
Monte-Carlo approximated integral value = -0.0232768655843053

Ken

Share

Using C# and ExcelDNA to call .NET Libraries

Thursday, January 13th, 2011

In my last post, I demonstrated calling NMath from Excel using ExcelDNA and Visual Basic (VB) code. In this blog post, we will duplicate that functionality using C# instead of Visual Basic. In addition we will use the functionality of NMath to enabled the marshaling of data between Excel and NMath, and provide some additional code examples.

The outcome of these blog articles should illustrate that Excel can become a powerful tool to quickly access a comprehensive .NET library such as CenterSpace’s NMath without needing a large supporting programming environment. This is all made possible by Govert van Drimmelen’s freeware tool ExcelDNA. ExcelDNA can interpret VB, C#, and F# instructions stored in a text file as Excel is loaded eliminating the need for a separate compiler. ExcelDNA does require the text file to have the extension .DNA. I recommend using an editor like NotePad++ to edit this file. In our last blog post, we named our file NMathExcel.dna to hold our VB code. Below, I have provided C# code that performs the same functions as the VB code in our previous post. This code below provides examples on how to use several basic routines in NMath.

 
<DnaLibrary Language="CS">
<Reference Name="CenterSpace.NMath.Core" />
<Reference Path="C:\Program Files\CenterSpace\NMath 4.1\Assemblies\NMath.dll" />
<Reference Path="C:\Program Files\CenterSpace\NMath 4.1\Assemblies\NMathShared.dll" />
<Reference Name="CenterSpace.NMath.Stats" />
<Reference Path="C:\Program Files\CenterSpace\NMath Stats 3.2\Assemblies\NMathStats.dll" />
 
<![CDATA[
 	 using ExcelDna.Integration;
 	 using CenterSpace.NMath.Core;
  	 using CenterSpace.NMath.Stats;
 
  	public class NMathExcelFunctions
  	{
  		[ExcelFunction(Description="Returns x raised to the y power")]
  		public static double NPower(double x, double y)
  		{
  			return NMathFunctions.PowFunction(x, y);
  		}
  		[ExcelFunction(IsVolatile=true)]
  		public static double NRand()
  		{
  			RandGenMTwist randomGenMTwist = new RandGenMTwist();
  			return randomGenMTwist.Next53BitRes();
  		}
  		[ExcelFunction(Description="Binomial Distribution: Number of Successes, Trials, Probability, Cumulative is True or False")]
  		public static double NBinomDist(int NSuccess, int NTrials, double Prob, bool Cumul)
  		{
 	 		BinomialDistribution binomialDistribution = new BinomialDistribution();
  			binomialDistribution.N = NTrials;
  			binomialDistribution.P = Prob;
  			if (Cumul)
  				return binomialDistribution.CDF(NSuccess);
  			else
  				return binomialDistribution.PDF(NSuccess);
  		}
  		[ExcelFunction(Description="Create a r by c matrix and fill with Random # between L and B")]
  		public static double[,] NDoubleMatrixRand(int rsize, int csize, int RandLBx, int RandUBx)
  		{
  			RandGenUniform randomGenUniform = new RandGenUniform(RandLBx,RandUBx);
  			randomGenUniform.Reset(0x124);
  			DoubleMatrix TempAr = new DoubleMatrix(rsize,csize,randomGenUniform);
  			return TempAr.ToArray();
  		}
  	}
]]>
</DnaLibrary>

In reviewing the differences between the VB and C# versions, the first order of business was to add the language specification to “CS” for C# so that ExcelDNA knows to switch from the default language of VB. Aside from the obvious differences between the two languages, we have added a reference to our NMathShared assembly and an explicit reference to using ExcelDNA.Integration. In general, the C# code is slightly simpler than VB and enables very simple code to call the library. In our C# sample code function for creating a DoubleMatrix with a random generation, we used the library’s built in conversion function ToArray() to marshal the data into an array format for Excel. Using NMath’s built-in array handling capabilities we can simply copy data in and out in the proper formats. This illustrates the ease by which we can utilize the NMath math libraries and Excel.

To further demonstrate this capability, we can add the following code to our NMathExcel.dna text file that calls the matrix transpose function in NMath. We will show how to create array data in Excel, pass it to the library in the proper format, and then return a result for display.

   public static double[,] NDoubleMatrixTranspose(double[,] InputAr)
   {
      DoubleMatrix TempAr = new DoubleMatrix(InputAr);
     TempAr = TempAr.Transpose();
      return TempAr.ToArray();
   }

Make sure you close Excel and reopen it after saving your code changes to the .DNA file so that ExcelDNA has the opportunity to incorporate the new code.

We can now send our library a range of data cells to be acted on and display the result. In the following screen shot I show setting up a 5 by 5 range with values to be transposed.

We can now select an available empty cell to insert our transpose computation. The following screenshot shows selecting the input range for calling our NMath transpose function.

As in our previous blog, the returned result is stored in a single cell and only one value is displayed. As a quick review to display the full result, first paint (select) the area for the data to be displayed with the function call in the upper left hand corner, then press the F2 key, next press Ctrl-Shift (hold), followed by the Enter key – this will build the array formula to populate the selected area with the result. Your result should look like the following screen.

As a further example of what is possible, we can tackle an least squares example from the NMath documentation. The problem is to calculate the residual norm square using the Cholesky method, something that would be awkward to do natively in Excel alone. In this example the inputs are the number of rows and columns of a DoubleMatrix uniformly filled with random numbers. To accomplish this we need to add the following code to our DNA text file.

using CenterSpace.NMath.Matrix;
  .
  .
public static object NDoubleCholeskyLeastSqRNS(int rsize, int csize, int RandLBx, int RandUBx)
{
	RandGenUniform randomGenUniform = new RandGenUniform(RandLBx,RandUBx);
	randomGenUniform.Reset(0x124);
	DoubleMatrix TempAr = new DoubleMatrix(rsize,csize,randomGenUniform);
	DoubleCholeskyLeastSq cholLsq = new DoubleCholeskyLeastSq(TempAr);
	if (cholLsq.IsGood)
	{
 		DoubleVector b = new DoubleVector(TempAr.Rows,randomGenUniform);
		DoubleVector x = cholLsq.Solve(b);
		return cholLsq.ResidualNormSqr(b);
	}
	else
		return "The random matrix doesn't have full rank";
}

Summary

Approaching the complex computations with Excel by performing the necessary computation by calling a C# library with data passed in from Excel, creates a robust approach to solving problems. The following screenshot shows how providing our function with the input parameters from the example produces the desired result.

If we tried to implement the solution line by line in Excel, we would be loosing the power of an object language like C# and powerful third-party .NET libraries, and increasing the generation of tedious Excel code without a strong development environment.

To wrap up, combining Excel, ExcelDNA, and the CenterSpace’s NMath libraries can be used to quickly generate solutions to complex problems without an extensive programming environment. In my next blog, I plan to solve a curve fitting example with this approach and using more of Excel features to display the results.

Mike Magee

Share

Calling External .NET Libraries from Excel

Wednesday, December 8th, 2010

There are many circumstances where you may need to access an external library of functions or routines from Excel.  For example, if you need a complex function such as fitting data to a surface, or portfolio optimization, that is not natively available in Excel.  There also may be a need to protect proprietary calculations by using user defined functions to process algorithms in a black box manner.  I was looking for a way to rapidly prototype some calculations without setting up a complex development environment.

Harking back to the old rule of development projects of  “two out of three”, when the three metrics are fast, cheap, and quality.  On any time limited project you only can plan to achieve two metrics, never all three. Initially I like to dive in and start with fast and cheap and work my way towards quality as necessary.  So, we’ll start with the quick and dirty approach to calling external libraries from Excel.

Project Setup

You must have a version of .NET Framework of 2.0 or greater.  The latest version is free from Microsoft at this link.
http://www.microsoft.com/net/download.aspx.

You’ll also need:

  • Excel 97 or later.
  • External library assemblies that you need to access from Excel. In our case we will use Centerspace’s NMath.dll and NMathStats.dll.
  • A freeware product called ExcelDNA written by Govert van Drimmelen that can be downloaded at http://exceldna.codeplex.com/ .

The first order of business is to unpack the downloaded file, ExcelDNA.zip, into a working directory.  For our example, we will use CenterSpaceExcel as our directory name. After unpacking you should have two folders Distribution and Source in our CenterSpaceExcel directory.  Inside the Distribution folder locate the file ExcelDNA.xll and rename it to NMathExcel.xll.

We now need to locate in the same directory the file ExcelDna.dna and rename it to NMathExcel.dna.  Then using notepad, or your favorite code editor, and open the file NMathExcel.dna.

You should see the following code:

<DnaLibrary>
<![CDATA[ 
     Public Module Module1   
       Function AddThem(x, y)      
          AddThem = x + y   
       End Function 
    End Module 
]]>
</DnaLibrary>
Assuming CenterSpace NMath and NStat are installed in the standard locations. Change it to read as follows and save:
<DnaLibrary>
 
<Reference Name="CenterSpace.NMath.Core" />
<Reference Path="C:\Program Files\CenterSpace\NMath 4.1\Assemblies\NMath.dll" />
<Reference Name="CenterSpace.NMath.Stats" />
<Reference Path="C:\Program Files\CenterSpace\NMath Stats 3.2\Assemblies\NMathStats.dll" />
 
<![CDATA[
	Imports NMath = CenterSpace.NMath.Core
	Imports Stats = CenterSpace.NMath.Stats
 
	Public Module NMathExcel
 
		<ExcelFunction(Description:="Returns x to the y power")> _
	    	Function NPower(x as double, y As double) As double
			NPower = NMath.NMathFunctions.PowFunction(x, y)
		End Function
 
		<ExcelFunction(IsMacroType:=True, IsVolatile:=True)> _
		Function NRand() As double
			dim rand As New NMath.RandGenMTwist
			NRand = rand.Next53BitRes()
		End Function
 
		<ExcelFunction(Description:="Binomial Distribution: Number of Successes, Trials, Probability, Cumulative is True or False")> _
		Function NBinomDist(NSuccess As Int32, NTrials As Int32, Prob As double, Cumul As Boolean) As double
			dim nbin As New Stats.BinomialDistribution
			nbin.N = NTrials
			nbin.P = Prob
			IF Cumul
				NBinomDist = nbin.CDF(NSuccess)
			Else
				NBinomDist = nbin.PDF(NSuccess)
			End If
		End Function
 
		Function NDoubleMatrixRand(rsize As integer, csize As integer, RandLBx As integer, RandUBy As integer) As Object(,)
			dim rng As New NMath.RandGenUniform(RandLBx,RandUBy)
			Rng.Reset(&#038;H124)
			dim TempA As New NMath.DoubleMatrix(rsize, csize, Rng)
			NDoubleMatrixRand = NCopyArray(TempA, rsize, csize)
 
		End Function
 
		Function NCopyArray(IMatrix As Object, rsize As integer, csize As integer) As Object(,)
			dim i As Integer
			dim j As Integer
			dim OArray(rsize, csize) As Object
			for i = 0 to rsize - 1
			   for j = 0 to csize - 1
				OArray(i,j) = IMatrix(i,j)
			   next j
			next i
			NCopyArray = OArray
		End Function		
 
	End Module
]]>

We now have created the VB code to call our CenterSpace Math and Statistics libraries with the following five functions.

  1. The first function shows a simple math library call to the Power function which takes a number x and raises it to the y power and returns the value.
  2. The second function shows a call to obtain a fast random number from the math library.  Since we want a new number each time the spreadsheet is re-calculated we have made the function volatile.
  3. The third function call shows how to set values that need to be accessed by a function in our .NET assemble; in this case, the Binomial Distribution.
  4. The fourth function demonstrates the creation of a DoubleMatrix that is the filled with random uniformly distributed numbers.
  5. The fifth function is a helper sub-routine to transfer data across the com interface.

Test our setup in Excel

Open Excel and move your cursor the Tools menu item.  Usually towards the bottom of the drop down menu you will find the selection Add-Ins.  After selecting Add-Ins, you see the pop-up window with the option to select Microsoft supplied Add-ins.  Choose the Browse option and go to the working directory we created at the beginning.  In our case, this will be the CenterSpaceExcel directory.  Next select the Distribution folder and you should see the renamed file: NMathExcel.xll.  Select it and you should now see the following screen.

Selecting a user created XLL as an Add-in for Excel

Make sure NMathExcel is checked and click OK. If you get an error and this point it is probably due to a typo in the DNA file, otherwise you will get the expected new sheet ready for entry.

Select an empty cell and then select from the menu bar Insert then from the pulldown Function.  You should see the following pop-up.

Selecting the category containing our NMath functions

At the bottom of the category pull down you should see our NMathExcel Functions;  Select it and you should have these options.:

NMath Excel Function Category

If we choose NPower, we will get the next screen,

Calling NMath Library Power function in Excel

I arbitrarily typed the value of 3.2 for x and 3.327 for y.  You can see the result of 47.9329301 before selecting OK.

Select OK and Excel will insert the value into the cell.  Select another blank cell and this time choose our NRand() function.  You will notice there is no opportunity to enter values and finish by selecting OK.  At this point you should see a number between 0 and 1 in the cell.  Each time you press F9 (sheet recalc) a new random number will appear.  If we had not made this function volatile the number would not change unless you edit the cell.

To test our Binomial Distribution function, again we will select a new cell and use the insert function option to insert the NBinomDist function with the following values.

Calling NMath Statistical function Binomial Distribution from Excel

At this point we have made successful calls into both of CenterSpace’s NMath and NMath Stats .NET math libraries.

In our fourth example, we will see how Excel handles matrices and look at issues passing array arguments across the COM interface.  Excel 2003 was limited to a maximum of 60,000 cells in an array, but Excel 2007 was expanded to handle 2 million.  Excel has some quirky ways of displaying matrices, and I’ll cover the in’s and out’s of these quirks.

We have written the basic code to set up a function called NDoubleMatrixRand for the purpose of creating a matrix with supplied dimensions and filled with uniform Random numbers over a specified distribution.  We will select another blank cell and again go to insert function and this time choose NDoubleMatrixRand.  Suppose we want to create a 6×6 matrix filled with random numbers between -2 and 2.  Our input will look like the following screen.

Creating a DoubleMatrix in Excel using NMath

Notice the equal sign in the middle right of the above screen is equal to {-0.994818527251482,-0.08

Values inclosed in curly brackets that are separated by commas indicates that an matrix was actually created, but you can see the Formula result is only displaying a partial value due to display size. At this point when you select OK, you will have a cell with a single value.  Here is where the fun begins.  Start at the cell and drag a 6×6 range as shown in the following screen.

Selecting the area the matrix is to be displayed in

Now get your fingers limbered. Here is where it gets a bit obscure – do exactly as follows.

  • Press the F2 key.  (pressing F2 may be  optional but is recommended by Excel as the cell leaves the edit mode)
  • Press and hold the Ctrl key followed by
  • pressing and holding the Shift key followed by
  • pressing the Enter key

and presto chango!  You should see a screen like this.

Displaying a Matrix in Excel

Notice that your cell’s formula is now enclosed in { }, indicating to Excel that the contained formula is an array function.  This is the only way to get matrices displayed.  Also, if you try to edit this cell you will get an error that changes are not allowed.  If  you want to change the dimensions simply reference the values from another cell when you create the function.

The fifth function NCopyArray copies the library matrix across the COM bridge into an Excel array object.  As I stated in the beginning this would be a quick and dirty approach and would leave room for improvement.

Summary

In my next post, I will provide the above code in C# and add more function calls with some matrices with hopefully an improved approach to NCopyArray.  Future posts will include creating a packaged XLL and a more complex example such as curve fitting.

Since time is our most precious asset, being able to quickly access complex math functions with a general purpose tool like Excel should save time and money!

At CenterSpace, we are interested if this blog is helpful and if there is a need for more examples of how our libraries can be accessed by Excel.  Let us know what areas are of interest to you.

Mike  Magee

Thanks and Resources
Also, a special thanks to Govert van Drimmelen for writing a wonderful tool such as ExcelDNA.

Share

Slices and Ranges in NMath Matricies

Friday, November 12th, 2010

NMath employs the data-view design pattern by distinguishing between data, and the different ways mathematical objects such as vectors and matrices view the data. For example, a contiguous array of numbers in memory might be viewed by one object as the elements of a vector, while another object might view the same data as the elements of a matrix, laid out row by row. Many different objects might share a given block of data. The data-view pattern has definite advantages for both storage efficiency and performance.

Combined with Slices and Ranges, the data-view pattern also offers a very rich set of matrix and vector manipulation semantics. We sometimes review or optimize customer code, and under-use or mis-use of Slices and Ranges is one of the most common things we find.

Creating Slices and Ranges

Slice and Ranges simply provide a way to specify a subset on non-negative integers with constant spacing, which you can then use as an indexing object into matrices and vectors. The difference between a Slice and a Range is only in how you specify the integer subset. You construct a Slice object by specifying

  • a beginning index
  • the total number of indices
  • a step increment, or stride

For example, to create a slice for the indices { 2, 4, 6, 8, 10 }, specify a start of 2, 5 total elements, and a stride of 2, like so:

Slice s = new Slice( 2, 5, 2 );

You construct a Range object by specifying:

  • a beginning index
  • an ending index
  • a stride

Thus, to create a range for the indices { 2, 4, 6, 8, 10 }, specify a starting point of 2, a stopping point of 10, and a stride of 2:

Range r = new Range( 2, 10, 2 );

But suppose you want to address the elements in a vector v from the third element to the last? You could do this by creating a Range like so:

Range r = new Range( 2, v.Length - 1, 1 );

but this is rather cumbersome. As a convenience, therefore, NMath provides the Position enumeration which lists different view positions of underlying data. You can use values in the Position enumeration in conjunction with ranges and slices to create abstract subsets. The precise meaning of an abstract subset is only determined when an indexing object is applied to a particular matrix or vector. The enumerated values are:

  • Position.Start indicates the starting position.
  • Position.MidPoint indicates the midpoint position, rounded down for data structures with an even number of elements.
  • Position.End indicates the ending position.

For instance, this code creates two ranges that could be used to specify the odd and even elements of a vector:

Range evenElements = new Range( Position.Start, Position.End, 2 );
Range oddElements = new Range( 1, Position.End, 2 );

The static All property on Slice and Range returns a new object indexing all elements:

Slice allElements = Slice.All;

Using Slices and Ranges

Now let’s look at how Slice and Range are used to create new views of matrix and vector data. The NMath vector and matrix classes provide standard indexing operators for getting and setting element values. Thus, v[i] always returns the ith element of vector v’s view of the data, and A[i,j] always returns the element in the ith row and jth column of matrix A’s view of the data. The indexers are also overloaded to accept Slice and Range indexing objects. For instance, this uses a Slice to create a new view of a vector’s data:

DoubleVector v = new DoubleVector( 10, 1, 1 );
Slice first3Elements = new Slice( 0, 3 );
DoubleVector u = v[first3Elements];

Both u and v reference the same data.

Using a Range object, this code creates a new 3×3 matrix view of the top left portion of an 8×8 matrix:

DoubleMatrix A = new DoubleMatrix( 8, 8 );
Range topLeft = new Range( 0, 3 );
DoubleMatrix B = A[ topLeft, topLeft ];

Both A and B reference the same data.

You can use negative strides too. Here is the matrix A, reversed.

Slice rowSlice = new Slice( A.Rows-1, A.Rows, -1 );
Slice colSlice = new Slice( A.Cols-1, A.Cols, -1 );
DoubleMatrix ARev = A[rowSlice,colSlice];

Any indexer or method that returns a view of the data referenced by a vector or matrix can be used to modify the values of the object:

DoubleMatrix A = new DoubleMatrix( 5, 5, 2);
Slice s = new Slice( 0, 2 );
A[s,s] = new DoubleMatrix( 2, 2, 1);

You can also use the Set() method to set elements to a specified value:

Slice col = new Slice( 3, 2 );
Slice row = Slice.All;
A.Set( col, row, 0 );

As a final example, here we change the contents of matrix A to alternate values 0 and 1 – like a checker board.

int numEvenElts = rows % 2 == 0 ? rows/2 : rows/2 + 1;
int numOddElts = rows/2;
Slice evenElts = new Slice( 0, numEvenElts, 2 );
Slice oddElts = new Slice( 1, numOddElts, 2 );
A.Set( evenElts, oddElts, 0 );
A.Set( evenElts, evenElts, 1 );
A.Set( oddElts, evenElts, 0 );
A.Set( oddElts, oddElts, 1 );

Ken

Share