Industrial Data Science
in C# and .NET:
Simple. Fast. Reliable.
 
 

ILNumerics - Technical Computing

Modern High Performance Tools for Technical

Computing and Visualization in Industry and Science

Controlling Array properties with Descriptors

The ILNumerics Array Visualizer is a powerful live data plotting and visualization tool for Visual Studio debug sessions and available for many popular languages. It allows to visualize arrays of arbitrary dimensionality and element type. Supported array types range from common 1D double[] System.Arrays in .NET, arrays of derived types in FORTRAN to void* pointers in C. General documentation of the Array Visualizer is found here. This section deals with advanced options to control the way your memory data are interpreted via array descriptors.

Read on this page: 

Two Modes for the Array Visualizer

In order to extract and visualize array data from memory the Array Visualizer requires the following info:

  • The type of the elements,
  • Number and lengths of the array dimensions,
  • The starting address in memory and
  • The storage format (row- vers. column major order / element strides for each dimension).

The Array Visualizer will collect all available information automatically. If it is not possible to acquire a needed piece of information the user is asked for more details. This is where array descriptors come into play. Array descriptors provide a way to let the developer specify certain array properties by use of a simple, yet flexible descriptor language.

The expressions are entered in one of the two available modes:

  1. Array descriptor disabled: No array descriptors are displayed (default).
  2. Array descriptor enabled: The array expression is prefixed by an array descriptor pattern.

The mode is toogled via "Show Array Descriptor" from the top right menu of the Array Visualizer Tool Window:

In order to access and control the properties of an array, the "Show Array Descriptor" mode must be

enabled. Note, that the tool window activates this mode automatically if the array lacks meta information.

When to use Array Descriptors?

Next to common array objects the Array Visualizer is used to describe, reinterpret and reshape arbitrary memory segments and to plot the values in a graphical way. The array descriptor supports the user by defining extended properties of the array. Here, the term 'array' may or may not correspond to a concrete array instance. The descriptor provides the information on how to interprete your debug memory in a way which makes it useful and suitable for visualization. It allows to explicitly define any array property.

Array descriptors come in handy in the following situations:

  • Visualizing from plain memory addresses.
  • Plotting arrays of structs / recordsets. E.g.: plotting the real parts of complex[], see below.
  • Reshaping N-D arrays from memory, including slicing and subarray plotting.
  • Handling array data which were created in foreign storage schemes (row major / column major). 
  • Plotting data referenced via pointer variables.

Array Descriptor Format

                   {Type[Dimensions]StridesOffset}

Where

  • Type [required] must be any valid predefined numeric element type in the current language. For C#, the list includes doubleint, short, long,... For Visual Basic Single, Double, and Integer are common examples. In C/C++ unsigned long, and long long would be some valid TYPE patterns. Consult the language specific documentation for a more comprehensive list of supported element types.
  • Dimensions [required] specifies the list of dimension lengths. Positive integer(s) or 0 (zero). If the array assembles more than one dimension Dimensions is a comma separated list of integers.
  • Strides [optional] specifies the storage format, i.e.: the spacing in memory between individual elements within each dimension. The strides specifier can be one of:
    • The single character 'R' for row-major storage. This is the default for all .NET System.Arrays and C arrays.
    • The single character 'C' for column-major storage. This is the default for all FORTRAN arrays.
    • A comma separated list of unsigned integer values. The number of integers provided must match the number of dimensions specified by Dimensions. The list must be enclosed in '[ ]' brackets. Ex.: [1,5] without any white space characters. Integers can define arbitrary spacings between elements for each dimension. The unit for each dimension is elements (just the integer) or bytes (integer, prefixed by; 'b'). Ex.: [8b,4,702b] defines 8 bytes spacing along the 1st dimension, 4 elements spacing along the second dimension and 702 bytes spacing along the 3rd dimension. 
  • Offset [optional] is a single integer number, optionally signed. It shifts the starting address of the array by the number of elements of Type. Offset may end with the character 'b'. In this case the unit of the offset is bytes instead of elements. Negative offsets are allowed. The default offset (0) is omitted.

Examples of valid array descriptor expressions:

  • {int[3]R} – 1D int32 array, 3 elements, row major storage layout.
  • {double[1000,20,30]} – 3D double array, 1000 x 20 x 30. language dependant storage layout.
  • {Integer[100,200,30]C} – 3D Int32 array in Visual Basic, 100 x 200 x 30. Providing C as storage layout overrides the language default value and considers this array as stored in column major order.
  • {short[10,20][2,20b]40b} – 2D short array in C#, 10 x 20 elements, elements start is offset by 40 bytes. Elements in dimension 1 are spaced by 2 short elements (4 bytes) apart. In the second dimension element spacing is 20 bytes.
  • {Single[10,20]R-40b} – 2D float array in Visual Basic, 10 x 20 elements, elements start is offsetted by -40 bytes.
  • {short[10000]C100} – 1D C# array with 10.000 elements, starting with the 100th element.
  • {char[100,100][4,100]} – visualizing interleaved elements from a 400x100 bytes memory block in C/C++.

Array Descriptor Usage

The array descriptor gives complete control over how your data is read from memory. It allows to reshape / re-orient arrays, to slice parts from arrays, transpose data, reinterpret the element type – all what is required to bring the data into a shape, suitable for plotting. In this section we give a general (mixed-language) overview of handling array descriptors. Consult the language specific documentation for more details: C#, F#, Visual Basic, C/C++, FORTRAN.

C# Example: Array descriptors for Full DEBUG DATA control

Consider the following C# array definition. It creates an array of 20 complex values as a row vector.

In the following we are going to use this System.Array to learn some useful visualizations. By default, no array descriptor is used when we enter the name of the variable into the Array Visualizer expression text box:

The text output gives the best details here. While other options (bar plots, scatter plots) are available, they are not able to handle complex numbers and would utilize the real parts only. At first, let's reshape the extracted array to form a column vector instead of a row vector. We enable the array descriptor mode from the menu:

The descriptor shows all details which are known for the array: the element type, the number and length of its dimensions and the storage layout – .NET arrays are stored in row major order as is indicated by "R".

We can now go ahead and change the array properties arbitrarily. In order to create a column vector we just have to flip the dimensions in the array descriptor. From [1,20] to [20,1]:

Now the array is fully visible without scrolling in the Array Visualizer tool window.

Next we want to extract real and imaginary data as individual elements. Recall the memory layout of an array of complex values. Real and imaginary parts are stored interleaved in memory, starting with the real part of the first complex number:

In order to plot the real part of the array we need to modify the array descriptor to form a vector of double elements of length 20. The spacing between individual elements (strides) is 2 double elements. So we change the array descriptor from:

to:

.

Here, the "R" (row major storage layout, as read from actual array meta data) was replaced by a more detailed strides expression. "[2,1]" means: elements in the first dimension are separated by a memory offset corresponding to the size of 2 elements. In the second dimension adjacent elements are found by computing the offset based on a single element ("1"). The resulting double[] array is suitable for plotting the real part:

What if we want the imaginary part instead? This can be achieved by utilizing of the 'OFFSET' component of the array descriptor. The required vector of double[20] must start 8 bytes later, which corresponds to the length of 1 element:

Note, that more fine grained control over the offset value is possible by defining the offset unit as 'bytes': 

This extracts a plot of the imaginary part: 

Learn how to achieve the above more easily

Plotting the real and imaginary parts by handling strides and offsets is only one option for plotting complex data as in this example. The same could be achieved without using array descriptors at all! Learn how in the language specific tutorials for C# and Visual Basic. The purpose of this manual is to show the options of the array descriptor. Moreover, while easier ways exist, it is recommended to become familiar with the array descriptor since this brings the most flexibility in advanced visualization scenarios.

Finally, array descriptors can be used to combine both: real and imaginary parts together to form an array which is directly suitable for plotting XY line plots, surface plots and combined bar plots in higher numbers of dimensions. Next we are going to transform the interleaved array of stucts into a double matrix of real values in the first row and imaginary values as the second row. 

Steps needed:

  1. Select the rank, dimension lengths and element type: the target array ought to be a 2 x 20 matrix of Double elements.
  2. Choose the spacing between individual elements in each dimension of the matrix. The 1st dimension stores the real and imaginary values; real values come first, imaginary values next. Both are stored subsequentially in memory, hence have a spacing of 1 element. The 2nd dimension corresponds to each complex number instance / elements of complex_sys array. Individual elements of the complex_sys array are stored with a spacing of 16 bytes, or 2 double elements apart from each other. The needed strides are: [1,2]

The full array descriptor + array expression therefore becomes:

Note how the matrix form of the complex array immediately enables the full set of plotting options. Next to regular text output the 2D matrix of complex data can be visualized by the following chart types:

XY-Line Plot Scatter Plot Rows Plot
Surface Plot ImageSC Plot Bar Plot

Read more about: 

Visual Basic Example: Debugging structs & recordsets

Array of custom structs / recordsets can easily and flexibly be plotted by picking individual elements or fields from the struct. The Array Visualizer extracts corresponding values from all struct elements within the array. The next example demonstrates this feature.  

At first a custom structure Person is created. It holds a string variable and 3 Integer variables each. The storage layout was explicitly defined, which simplifies things but is not required for the Array Visualizer. Here, we create a 32 bit application (pointer size: 4 bytes). Next, an array of Persons is created which holds infos about the presidents of the United States, ordered by their heights. These heights can easily be plotted with the Array Visualizer. Just stop at some debug position and enter 'Presidents(0).Height' into the Array Visualizer:

We could use the same concept to plot any data member of the struct by exchanging its name for the 'Height' term in the current expression. However, here we want to play with advanced array descriptor options. Let's change the expression to read 'presidents(3)' and press ENTER.

Three things are worth noticing here:

  1. The array descriptor was enabled automatically, since we are required to explicitly enter the element type we want to plot. Expect this to happen whenever array property data for plotting are missing. You will find the missing component selected in the array descriptor expression, ready for you to enter the missing detail.
  2. The new array descriptor exposes some predefined data: the rank of the array (1), the length of the only dimension [41] and the spacing / stride for elements in this dimension [16 bytes]. All those data have been determined automatically from the entered expression and by querying the debug engine. 
  3. The third noteworthy info relates to the number of presidents existing. By time of writing, Barack Obama is the 44th US president. 44 presidents have been defined in the Presidents array. However, the length of the only dimension as predefined by the array descriptor is 41!? Where are the missing elements? The answer: they are to be excluded from the extracted array. The reason is that we had entered an expression pointing to the 4th element (index 3). The array descriptor recognizes that there are 44 struct elements in the array but subtracts 3 from it to start with the 4th element without reaching over the end of the array.

Redefining Element Types

Continuing the example we enter UInteger as element type. We know that all numbers we have stored are positive. So we can be sure that unsigned integer is a matching type. The Array Visualizer will not check for it! It will simply believe in you that you know what you are doing. Failing to do so will cause useless values at best. Here, we had defined the integer fields of the struct as signed integers. However, they indeed are compatible and we will get the same values as if we had choosen Integer instead.  Take away: redefining the element type has the same effect as a reinterpreting cast of a pointer type.

Visualizing multiple Variables at ones

Just like for complex numbers it is possible to combine multiple data from the struct into the same extraction array for plotting. We could plot the Year element against the Height values. Therefore, we combine the corresponding fields from the structs into a matrix of size 2 x 44:

Steps needed:

  • Start as if plotting the Number fields. Enter 'presidents(0).Year' into the textbox. We choose 'Year' because this field comes first of the Height field.
  • If you have modified the existing expression without clearing the whole text box first make sure to hold down the CTRL key when pressing ENTER. This will reset the array descriptor and re-evaluate the properties of the new array. Otherwise an existing array descriptor takes precedence over the array expression.
  • We receive a column vector of all Year fields. The array descriptor reads: {Integer[44][16b]8b}Presidents(0).Year
  • Combining data often means: adding a new dimension to the extracted array and to specify the strides for the new data. This can be done by changing the array descriptor as follows:​​

We have added a new dimension of length 2 (Year + Height = 2 Integers) and defined the strides for them. The Height field comes directly after the Year field. Therefore the stride is 1. With the newly extracted matrix we can choose from the full spectra of plotting types. Let's plot the Heights data against the Years by selecting the scattered data plot:

This creates a nice 2D X/Y plot. Since all plots in ILNumerics Visualizations are capable of handling 3 dimensions one may add another data element as the Z dimension. This is left to the reader as exercise. 

 

Read more in the language specific manual for Visual Basic

C/C++ Example: Fun with pointer variables and addresses

In C/C++ several ways exist to create and store arrays. Next to dedicated datatypes (std::vector, std:array, std:valarray) one common way is to use fixed length arrays and/or pointers. The Array Visualizer is well prepared to visualize any of these data. Some introduction is provided here. Consult the language specific manual for more details. 

Consider the following array definitions.

The properties of the 'SHORT_A3' variable can be completely acquired from the debug engine. Therefore, we can simply visualize this array by typing its name in the Array Visualizer. Array Descriptors are not required:

The array descriptor mode is enabled via the top right menu of the Array Visualizer Tool Window. This makes the element type and dimension configurable:

Let's extract the data as matrix instead of a 3 dimensional array! The 2x3x4 data elements fit into a 6 x 4 matrix:

which gives: 

We can switch to alternative views to have the data rows of the matrix plotted as lines, as image or bars:

The array descriptor becomes even more useful when visualizing data with less information attached to it, namely: pointers.

C/C++ Example: image data from pointers

Consider data being a pointer unsigned char*. It addresses the bytes of a 24bpp bitmap image (of the surface of the pluto dwarf planet), i.e.: each pixel has 3 bytes attached to it with RGB color channel information.

In order to make sense of the bytes stored in data and to check if the file input worked as expected we can visualize the image which is assembled out of data's bytes. Therefore, we need to pick the correct bytes for each color channel and form the image pixels matrix for plotting. The Array visualizer will detect the element type for us. The height and width of the image must be known in advance or is extracted from the image header as in the above example. The date needed for the array descriptor is easily determined from the memory layout:

This gives the following result:

Note, how the array dimensions / strides can be selected arbitrarily. By knowing the data the user can choose the shape / extraction parameters for the best result. This flexibility is most helpful for pointer types and arrays, where some properties are not stored within the array (due to language specifications). However, the flexibility of the array descriptor is available for all array types and in all supported languages, even for such arrays, which do carry all size information with them. Array descriptors allow us to bring every array into the form which is suitable / required for plotting the array with a certain plot type.

The next section deals with conflicts created by ambiguous sources for array meta data.  

Array Property Sources

The way arrays are stored can become quite complex. Arrays associate a number of meta information to a memory location / area. This meta information is required in order to read the memory and to build-up a useful representation of the array, suitable for visualization. Array descriptors define the complete storage scheme and allow to control all of this meta information. A comprehensive list of all information required for arrays: 

Array Meta Info Stored with.... ?
  .NET System.Array, double[], int[,,,], ... C# pointer C++ pointer double*  FORTRAN  ALLOCATABLE
Memory Address  Yes Yes Yes Yes
Global Adress Offset Yes: 0 No No No
Element Type Yes No Yes Yes
Number of Dimensions Yes No No No
Lengths of Dimensions Yes No No No
Strides of Dimensions Yes No No No

Locked Array Descriptor Components 

When the ILNumerics Array Visualizer is about to plot the data for an array it first decides where to take the meta information from. As we see from above scheme, there are two potential sources: 

  • Information stored with the array. This varies with the array type and the language / the framework. Only some array types (including ILNumerics arrays) store all meta data with them. 
  • Information entered by the user. 

Array descriptors are initialized with the information stored with the array - if any. Any information not stored with the array must be entered by the user. Therefore, array descriptors are editable. Manually changed parts are from now on considered 'locked': they will not change automatically, even though when the underlying array may change and now expose different properties. Locked array descriptor parts are marked by a bold, underlined font.

 

For reading the array the Array Visualizer prioritizes locked meta information parts of the array descriptor over actual array properties, which are stored with the array.  

Unlocking Array Descriptor Components

Clicking on a locked component with the left mouse while holding down the CTRL key will unlock the component. Actual, corresponding array properties for the component are re-read from the array storage - if any. Unlocked components are automatically updated with each expression evaluation / each debug step / each array change by interactive actions in debug windows.

In order to unlock all array descriptor components at once simply hold down the CTRL key and press ENTER when the cursor is in the Array Visualizer expression text field. 

 

Further readings: