5 Inter-Relating Coordinate Systems (Mappings)

In §4 we used the ZoomMap as an example of a Mapping. We saw how it could be used to transform coordinates from its input to its output and back again (§4.8). We also saw how its behaviour could be controlled by setting various attributes, such as the Zoom factor and the Report attribute that made it display coordinate values as it transformed them.

In this section, we will look at Mappings a bit more thoroughly and explore the behaviour which is common to all the Mappings provided by AST. This is good background for what follows, because many of the Objects we discuss later will also turn out to be Mappings in various disguises.

5.1 The Mapping Class

Before we start, it is worth taking a quick look at the Mapping class as a whole and some of the sub-classes it contains:

Mapping
CmpMap
DssMap
GrismMap
IntraMap
LutMap
MathMap
MatrixMap
PermMap
PolyMap
ChebyMap
SlaMap
SpecMap
TimeMap
UnitMap
WcsMap
ZoomMap

Frame
<various types of Frame>

The Frame sub-class has been separated out here because it is covered in detail in §7. We start by looking at the parent class, Mapping.

AST does not provide a function to create a basic Mapping (i.e. the astMapping constructor does not exist). This is because the Mapping class itself is “virtual” and basic Mappings are of no use in themselves. The Mapping class serves simply to contain the various specialised Mappings that exist. However, it provides more than just a convenient heading for them because it bestows all classes of Mapping with common properties (e.g. attributes) and behaviour. By examining the Mapping class, we are therefore examining the things that all other Mappings have in common.

5.2 The Mapping Model

The concept of a Mapping was illustrated in Figure 1. It is a black box which you can supply with a set of coordinate values in return for a set of transformed coordinates. The two sets are termed input and output coordinates. You can also go back the other way and transform output coordinates back into input coordinates, as we saw in §4.8.

5.3 Changing Attributes of a Mapping

Many classes of Mapping have attributes that provide values for parameter used within the transformation. For instance, the ZoomMap class has an attribute called “Zoom” that gives the scalar value by which each coordinate is to be multiplied. These attribute values should be set when the Mapping is created and should not be changed afterwards. Indeed, the AST library will report an error if an attempt is made to change the value of a Mapping attribute. This is because, once created, Mappings are often later included within other objects such as FrameSets and CmpMaps. This means that in general there could be many active references to a single Mapping object within a program. Changing an attribute of the Mapping via one particular reference (i.e pointer) would cause all the other references to change too, with often undesirable or unpredictable consequences. To avoid this, Mappings are considered immutable in most situations. The one exception is if the Mapping has not yet been cloned or included in another Object (i.e. it has a reference couint of one) - changing the attributes of such a Mapping is allowed, and will not generate an error.

Note, the Invert attribute of a Mapping is not subject to this rule and can be changed at any time.

5.4 Input and Output Coordinate Numbers

In general, the number of coordinates you feed into a Mapping to represent a single point need not be the same as the number that comes out. Often these numbers will be the same, and often they will both equal 2 (because 2-dimensional coordinate systems are common), but this needn’t necessarily be the case.

The number of coordinates required to specify an input point is represented by the integer attribute Nin and the number required to specify an output point is represented by Nout. These are read-only attributes common to all Mappings. Generally, their values are fixed when a Mapping is created.

In §4.2, we saw how the Nin attribute for a ZoomMap was initialised by the call to the constructor function astZoomMap which created it. In this case, the Nout attribute was not needed and it implicitly took the same value as Nin, but we could have enquired about its value had we wanted, as follows:

#include "star/ast.h"
AstZoomMap *zoommap;
int nout;

...

nout = astGetI( zoommap, "Nout" );

5.5 Forward and Inverse Transformations

We stated earlier that a Mapping may be used to transform coordinates either from input to output, or vice versa. These are termed its forward and inverse transformations.

This statement was not quite accurate, however, because in general Mappings are only potentially capable of working in both directions. In practice, coordinate transformation may only be feasible in one direction or the other because some functions are not easily inverted (they may be multi-valued, for instance). Allowance must be made for this, so each Mapping has two read-only boolean (integer) attributes, TranForward and TranInverse, which indicate whether each transformation is available.

A transformation is available if the corresponding attribute is non-zero, otherwise it is not.11 If you enquire about the value of these attributes, a value of 0 or 1 is returned. Attempting to use a Mapping to apply a transformation which is not available will result in an error.

5.6 Inverting Mappings

An important attribute, common to all Mappings, is the Invert flag. This is a boolean (integer) attribute that can be assigned a new value at any time. If it is non-zero, it has the effect of interchanging the Mapping’s input and output coordinates and the Mapping is then said to be inverted. By default, the Invert attribute is zero.

There is no magic in this. There is no fancy arithmetic involved in inverting mathematical functions, for instance. The Invert flag is simply a switch that interchanges a Mapping’s input and output ports. If it is non-zero, the Mapping’s Nin and Nout attributes are swapped, its TranForward and TranInverse attributes are swapped, and when you ask for what was once the forward transformation you get the inverse transformation instead (and vice versa). When you return the Invert attribute to zero, or clear it, the Mapping returns to its original behaviour.

Often, the actual value of the Invert attribute is unimportant and you simply wish to invert its boolean sense, so that what was the Mapping’s input becomes its output and vice versa. This is most easily accomplished using astInvert, as follows:

AstMapping *mapping;

...

astInvert( mapping );

If the Mapping you have happens to be the wrong way around, astInvert allows you to correct the problem.

5.7 Finding the Rate of Change of a Mapping Output

The astRate function can be used to find the rate of change of any Mapping output with respect to any Mapping input, at a given input position. The method used produces good accuracy (typically a relative error of 10E-10 or less) but may require the Mapping to be evaluated 100 or more times. An estimate of the second derivative is also produced by this function.

5.8 Reporting Coordinate Transformations

We have already seen (§4.8) how the boolean (integer) Report attribute of a Mapping works. If it is non-zero, the operation of transforming a set of coordinates will result in a report being written to standard output. This will display the coordinate values before and after transformation. It can save considerable time during program development by eliminating the need to add loops and output statements to your program.

In a finished program, however, you should be careful that the Report attribute is not set to a non-zero value unless you want to see the output (there may often be rather a lot of this!). To help prevent unwanted output being produced by accident, the Report attribute is unusual in that its value is not preserved when a Mapping is copied using astCopy4.13). Instead, it reverts to its default of zero (i.e. un-set) in the copy. It also reverts to zero when a Mapping is written out, e.g. to a file using a Channel15).

5.9 Handling Missing (Bad) Coordinate Values

Even when coordinates can, in principle, be transformed in either direction by a Mapping, there may still be instances where specific coordinate values cannot be handled. For example, the Mapping may be mathematically intractable (e.g. singular) in certain places, or it may map a subset of one space on to another, so that some points in one space are not represented in the other. Sky projections often show this behaviour, since it is quite common to project only half of the celestial sphere on to two dimensions, omitting points on the opposite side of the sky. There are many other examples.

To indicate when coordinates cannot be transformed, for whatever reason, AST substitutes a special output coordinate value given by the macro AST__BAD (as defined in the “ast.h” header file). Before making use of coordinates generated by any of the AST transformation functions, therefore, you may need to check for the presence of this value.

Because coordinates with the value AST__BAD can be generated in this way, all other AST functions are also capable of recognising this value and handling it appropriately. The coordinate transformation functions do this by propagating any missing input coordinate information through to their output. This means that if you supply coordinates with the value AST__BAD, the returned coordinates are also likely to contain this value. Here, for example, is what happens if you use a ZoomMap (with Zoom factor 5) to transform such a set of coordinates:

(0, 0) --> (0, 0)
(<bad>, 2) --> (<bad>, 10)
(2, 4) --> (10, 20)
(3, 6) --> (15, 30)
(4, <bad>) --> (20, <bad>)
(5, 10) --> (25, 50)
(<bad>, <bad>) --> (<bad>, <bad>)
(7, 14) --> (35, 70)
(8, 16) --> (40, 80)
(9, 18) --> (45, 90)

The AST__BAD value is represented by the string “$<$bad$>$”. This is a case of “garbage in, garbage out” but at least it’s consistent garbage that you can recognise!

Note how the presence of the AST__BAD value in one input dimension does not necessarily result in the loss of information for all output dimensions. Sometimes, such loss will be unavoidable, but in general an attempt is made to preserve information as far as possible. The exact behaviour will depend on the Mapping involved.

5.10 Example—the UnitMap

The UnitMap is the simplest of Mappings. It is a null Mapping. Its purpose is simply to copy coordinate values, unaltered, from its input to its output and vice versa.

A UnitMap has no additional attributes beyond those of a basic Mapping. Its Nin and Nout attributes are always equal and are specified by the first argument supplied to its constructor. For example:

AstUnitMap *unitmap;

...

unitmap = astUnitMap( 2, "" );

will create a UnitMap that copies 2-dimensional coordinates. Inverting a UnitMap has no effect beyond changing the value of its Invert attribute.

The main use of a UnitMap is to allow a Mapping to be supplied when one is required (as an argument to a function, for example) but you wish it to leave coordinate values unchanged.

5.11 Example—the PermMap

The PermMap is a rather more complicated Mapping than we have met previously. Its purpose is to change the order, or number, of coordinates. It is also able to substitute fixed values for coordinates.

To illustrate its action, suppose our input coordinates are denoted by (${x}_{1},{x}_{2},{x}_{3},{x}_{4}$) in a 4-dimensional space and suppose our output coordinates are to be (${x}_{4},{x}_{1},{x}_{2},{x}_{3}$). Our PermMap, therefore, should rotate the coordinate values by one position.

To create such a PermMap, we first set up two integer arrays. One of these, “outperm”, controls the selection of input coordinates for use in the output and the other, “inperm”, controls selection of output coordinates for use in the input:

int outperm[ 4 ] = { 4, 1, 2, 3 };
int inperm[ 4 ] = { 2, 3, 4, 1 };

Note that the numbers we store in these arrays are the indices of the coordinates that we want to select. We have chosen these so that the forward and inverse transformations will perform complementary permutations on the coordinates.

The PermMap is then created by passing these arrays to its constructor, as follows:

AstPermMap *permmap;

...

permmap = astPermMap( 4, inperm, 4, outperm, NULL, "" );

Note that we specify the number of input and output coordinates separately, but set both to 4 in this example. The resulting PermMap would have the following effect when used to transform coordinates:

Forward:
(1, 2, 3, 4) --> (4, 1, 2, 3)
(2, 4, 6, 8) --> (8, 2, 4, 6)
(3, 6, 9, 12) --> (12, 3, 6, 9)
(4, 8, 12, 16) --> (16, 4, 8, 12)
(5, 10, 15, 20) --> (20, 5, 10, 15)

Inverse:
(4, 1, 2, 3) --> (1, 2, 3, 4)
(8, 2, 4, 6) --> (2, 4, 6, 8)
(12, 3, 6, 9) --> (3, 6, 9, 12)
(16, 4, 8, 12) --> (4, 8, 12, 16)
(20, 5, 10, 15) --> (5, 10, 15, 20)

If the number of input and output coordinates are unequal so, also, will be the size of the “outperm” and “inperm” arrays. This means, however, that we cannot fill them with coordinate indices so that they perform complementary permutations, because one transformation will lose information (discard a coordinate) that the other cannot recover. To give an example, consider the following:

int outperm[ 3 ] = { 4, 3, 2 };
int inperm[ 4 ] = { -1, 3, 2, 1 };
double con[ 1 ] = { 99.004 };

In this case, the forward transformation will change (${x}_{1},{x}_{2},{x}_{3},{x}_{4}$) into (${x}_{4},{x}_{3},{x}_{2}$) and will discard ${x}_{1}$. The inverse transformation restores the original coordinate order, but has no value to assign to the first coordinate. In this case, the number entered in the “inperm” array is $-$1.

This negative value indicates that the coordinate value should be obtained by addressing the first element of the “con” array (i.e. element zero). This array, ignored in the previous example, may then be used to supply a value for the missing coordinate.

The constructor function:

permmap = astPermMap( 4, inperm, 3, outperm, con, "" );

will then create a PermMap with the following effect when used to transform coordinates:

Forward:
(1, 2, 3, 4) --> (4, 3, 2)
(2, 4, 6, 8) --> (8, 6, 4)
(3, 6, 9, 12) --> (12, 9, 6)
(4, 8, 12, 16) --> (16, 12, 8)
(5, 10, 15, 20) --> (20, 15, 10)

Inverse:
(4, 3, 2) --> (99.004, 2, 3, 4)
(8, 6, 4) --> (99.004, 4, 6, 8)
(12, 9, 6) --> (99.004, 6, 9, 12)
(16, 12, 8) --> (99.004, 8, 12, 16)
(20, 15, 10) --> (99.004, 10, 15, 20)

The “con” array may contain more than one value if necessary and may be addressed by both the “inperm” and “outperm” arrays using coordinate indices $-$1, $-$2, $-$3, etc. to refer to the first, second, third, etc. elements.

If there is no suitable replacement value that can be supplied via the “con” array, a value of zero may be entered into the “outperm” and/or “inperm” arrays. This causes the value AST__BAD to be used for the affected coordinate (as defined in the “ast.h” header file), thus indicating a missing coordinate value (§5.9).

The principle use for a PermMap lies in matching a coordinate system to a data array where there is a choice of storage order for the data. PermMaps are also useful for discarding unwanted coordinates so as to reduce the number of dimensions, such as when selecting a “slice” from a multi-dimensional array.

11Most of the Mappings provided by the AST library work in both directions, although the LutMap can behave otherwise.