6 Compound Mappings (CmpMaps)

We now turn to a rather special form of Mapping, the CmpMap. The Mappings we have considered so far have been atomic, in the sense that they perform pre-defined elementary transformations. A CmpMap, however, is a compound Mapping. In essence, it is a framework for containing other Mappings and its purpose is to allow those Mappings to work together in various combinations while appearing as a single Object. A CmpMap’s behaviour is therefore not pre-defined, but is determined by the other Mappings it contains.

6.1 Combining Mappings in Series

Consider a simple example based on two 2-dimensional coordinate systems. Suppose that to convert from one to the other we must swap the coordinate order and multiply both coordinates by 5, so that the coordinates (${x}_{1},{x}_{2}$) transform into ($5{x}_{2},5{x}_{1}$). This can be done in two stages:

(1)
Apply a PermMap5.11) to swap the coordinate order.
(2)
Apply a ZoomMap4.8) to multiply both coordinate values by the constant 5.

The PermMap and ZoomMap are then said to operate in series, because they are applied sequentially (c.f. Figure 2). We can create a CmpMap that applies these Mappings in series as follows:

#include "star/ast.h"
AstCmpMap *cmpmap;
AstPermMap *permmap;
AstZoomMap *zoommap;

...

/* Create the individual Mappings. */
{
int inperm[ 2 ] = { 2, 1 };
int outperm[ 2 ] = { 2, 1 };
permmap = astPermMap( 2, inperm, 2, outperm, NULL, "" );
}
zoommap = astZoomMap( 2, 5.0, "" )

/* Combine them in series. */
cmpmap = astCmpMap( permmap, zoommap, 1, "" );

/* Annul the individual Mapping pointers. */
permmap = astAnnul( permmap );
zoommap = astAnnul( zoommap );

Here, the third argument (1) of the constructor function astCmpMap indicates “in series”.

When used to transform coordinates in the forward direction, the resulting CmpMap will apply the first component Mapping (the PermMap) and then the second one (the ZoomMap). When transforming in the inverse direction, it will apply the second one (in the inverse direction) and then the first one (also in the inverse direction). In general, although not in this particular example, the order in which the two component Mappings are supplied is significant. Clearly, also, the Nout attribute (number of output coordinates) for the first Mapping must equal the Nin attribute (number of input coordinates) for the second one.

6.2 Combining Mappings in Parallel

Connecting two Mappings in series (§6.1) is not the only way of combining them. The alternative, in parallel, involves applying the two Mappings at once but on different subsets of the coordinate values.

Consider, for example, a set of 3-dimensional coordinates and suppose we wish to transform them by swapping the first two coordinate values and multiplying the final one by 5, so that (${x}_{1},{x}_{2},{x}_{3}$) transforms into (${x}_{2},{x}_{1},5{x}_{3}$). Again, we can perform each of these steps individually using Mappings similar to the PermMap and ZoomMap used earlier (§6.1). In this case, however, the ZoomMap is 1-dimensional and the individual Mappings are applied in parallel (c.f. Figure 3).

Creating a CmpMap for this purpose is also very simple:

cmpmap = astCmpMap( permmap, zoommap, 0, "" );

The only difference is that the third argument of astCmpMap is now zero, meaning “in parallel”.

As before, the order in which the two component Mappings are supplied is significant. The first one acts on the lower-numbered input coordinate values (however many it needs) and produces the lower-numbered output coordinates, while the second Mapping acts on the higher-numbered input coordinates (however many remain) and generates the remaining higher-numbered output coordinates. When the CmpMap transforms coordinates in the inverse direction, both component Mappings are applied to the same coordinates, but in the inverse direction.

Note that the Nin and Nout attributes of the component Mappings (i.e. the numbers of input and output coordinates) will sum to give the Nin and Nout attributes of the overall CmpMap.

6.3 The Component Mappings

A CmpMap does not store copies of its component Mappings, but simply holds pointers to them. In the example above (§6.1), we were free to annul the individual Mapping pointers after creating the CmpMap because the pointers held internally by the CmpMap increased the reference count (RefCount attribute) of each component Mapping by one. The individual components are therefore not deleted by astAnnul, but retained until the CmpMap itself is deleted and annuls the pointers it holds. Consistent use of astAnnul (§4.9) and/or pointer contexts (§4.10) will therefore ensure that all Objects are deleted at the appropriate time.

Note that access to a CmpMap’s component Mappings is not generally available unless pointers to them are retained when the CmpMap is created. If such pointers are retained, then subsequent modifications to the individual components can be used to indirectly modify the behaviour of the overall CmpMap.

There is an important exception to this, however, because a CmpMap retains a copy of the initial Invert flag settings of each of its components and uses these in order to ignore any subsequent external changes. This means that you may invert either component Mapping before inserting it into a CmpMap and need not worry if you un-invert it again later. The CmpMap’s behaviour will not be affected by the later action.

6.4 Creating More Complex Mappings

Because a CmpMap is itself a Mapping, any existing CmpMap can substitute (§4.3) as a component Mapping when constructing a new CmpMap using astCmpMap. This has the effect of nesting one CmpMap inside another and opens up many new possibilities. For example, combining three Mappings in series can be accomplished as follows:

AstMapping *map1, *map2, *map3;

...

cmpmap = astCmpMap( map1, astCmpMap( map2, map3, 1, "" ), 1, "" );

The way in which the individual component Mappings are grouped within the nested CmpMaps is not usually important.

A similar technique can be used to combine multiple Mappings in parallel and, of course, mixed series and parallel combinations are also possible (Figure 4). There is no built-in limit to how many CmpMaps may be nested in this way, so this mechanism provides an indefinitely extensible method of building complex Mappings out of the elemental building blocks provided by AST.

In practice, you might not need to construct such complex CmpMaps yourself very frequently, but they will often be returned by AST routines. Nested CmpMaps underlie the library’s entire ability to represent a wide range of different coordinate transformations.

6.5 Example—Transforming Between Two Calibrated Images

Consider, as a practical example of CmpMaps, two images of the sky. Suppose that for each image we have a Mapping which converts from pixel coordinates to a standard celestial coordinate system, say FK5 (J2000.0). If we wish to inter-compare these images, we can do so by using this celestial coordinate system to align them. That is, we first convert from pixel coordinates in the first image into FK5 coordinates and we then convert from FK5 coordinates into pixel coordinates in the second image.

If “mapa” and “mapb” are pointers to our two original Mappings, we could form a CmpMap which transforms directly between the pixel coordinates of the first and second images by combining these Mappings, as follows:

AstCmpMap *alignmap;
AstMapping *mapa, *mapb;

...

astInvert( mapb );
alignmap = astCmpMap( mapa, mapb, 1, "" );
astInvert( mapb );

Here, we have used astInvert5.6) to invert “mapb” before inserting it into the CmpMap because, as supplied, it converted in the wrong direction. Afterwards, we invert it again to return it to its original state. The CmpMap, however, will ignore this subsequent change (§6.3).

The forward transformation of the resulting CmpMap will now transform from pixel coordinates in the first image to pixel coordinates in the second image, while its inverse transformation will convert in the opposite direction.

6.6 Over-Complex Compound Mappings

While a CmpMap provides a very flexible way of constructing arbitrarily complex Mappings (§6.4), it unfortunately also provides an opportunity for representing simple Mappings in complex ways. Sometimes, unnecessary complexity can be difficult to avoid but can obscure important simplifications.

Consider the example above (§6.5), in which we inter-related two images of the sky via a CmpMap. If the two images turned out to be simply offset from each other by a shift along each pixel axis, then this approach would align them correctly, but it would be inefficient. This is because it would introduce unnecessary and expensive transformations to and from an intermediate celestial coordinate system, whereas a simple shift of pixel origin would suffice.

Recognising that a simpler and more efficient solution exists obviously requires a little more than simply joining two Mappings end-to-end. We must also determine whether the resulting CmpMap is more complex than it needs to be, i.e. contains redundant information. If it is, we then need a way to simplify it.

The problem is not always just one of efficiency, however. Sometimes we may also need to know something about the actual form a Mapping takes—i.e. the nature of the operations it performs. Unnecessary complexity can obscure this, but such complexity can easily accumulate during normal data processing.

For example, a Mapping that transforms pixel coordinates into positions on the sky might be repeatedly modified as changes are made to the shape and size of the image. Typically, on each occasion, another Mapping will be concatenated to reflect what has happened to the image. This could soon make it difficult to discern the overall nature of the transformation from the complex CmpMap that accumulates. If only shifts of origin were involved on each occasion, however, they could be combined into a single shift which could be represented much more simply.

Suppose we now wanted to represent our image’s celestial coordinate calibration using FITS conventions (§17). This requires AST to determine whether the Mapping which relates pixel coordinate to sky positions conforms to the FITS model (for example, whether it is equivalent to applying a single set of shifts and scale factors followed by a map projection). Clearly, there is an important use here for some means of simplifying the internal structure of a CmpMap.

6.7 Simplifying Compound Mappings

The ability to simplify compound Mappings is provided by the astSimplify function. This function encapsulates a number of heuristics for converting Mappings, or combinations of Mappings within a CmpMap, into simpler, equivalent ones. When applied to a CmpMap, astSimplify tries to reduce the number of individual Mappings within it by merging neighbouring component Mappings together. It will do this with both series and parallel combinations of Mappings, or both, and will handle CmpMaps nested to any depth (§6.4).

To illustrate how astSimplify works, consider the combination of Mappings shown in Figure 10.

If this were contained in a CmpMap, it could be simplified as follows:

AstMapping *simpler;

...

simpler = astSimplify( cmpmap );

In this case, the result would be a simple 3-dimensional UnitMap (the identity Mapping). To reach this conclusion, astSimplify will have made a number of deductions, roughly as follows:

(1)
The two 2-dimensional ZoomMaps in series are equivalent to a single ZoomMap with a combined Zoom factor of unity. This, in turn, is equivalent to a 2-dimensional UnitMap.
(2)
This UnitMap in parallel with the other 1-dimensional UnitMap is equivalent to a single 3-dimensional UnitMap. This UnitMap, sandwiched between any other pair of Mappings, can then be eliminated.
(3)
The remaining two PermMaps in series are equivalent to a single 3-dimensional PermMap. When these are combined, the resulting PermMap is found to be equivalent to a 3-dimensional UnitMap.

This example is a little contrived, but illustrates how astSimplify can deal with even quite complicated compound Mappings through a series of incremental simplifications. Where possible, this will result in either a simpler compound Mapping or, if feasible, an atomic (non-compound) Mapping, as here. If no simplification is possible, astSimplify will just return a pointer to the original Mapping.

Although astSimplify cannot identify every simplification that is theoretically possible, sufficient rules are included to deal with the most common and important cases.