Skip to main content
`Ctrl`+`K`

# Structured Units

Numpy arrays can be structured arrays, where each element consists of multiple fields. These can be used with |Quantity| using a |StructuredUnit|, which provides a |Unit| for each field. For example, this allows constructing a single |Quantity| object with position and velocity fields that have different units, but are contained within the same object (as is needed to support units in the |PyERFA| wrappers around the |ERFA| routines that use position-velocity arrays).

## Creating Structured Quantities

You can create structured quantities either directly or by multiplication with a |StructuredUnit|, with the latter in turn either created directly, or through `Unit`.

### Example

To create a structured quantity containing a position and velocity:

    >>> import astropy.units as u, numpy as np
    >>> pv_values = np.array([([1., 0., 0.], [0., 0.125, 0.]),
    ...                       ([0., 1., 0.], [-0.125, 0., 0.])],
    ...                      dtype=[('p', '(3,)f8'), ('v', '(3,)f8')])
    >>> pv = u.Quantity(pv_values, u.StructuredUnit((u.km, u.km/u.s)))
    >>> pv
    <Quantity [([1., 0., 0.], [ 0.   ,  0.125,  0.   ]),
               ([0., 1., 0.], [-0.125,  0.   ,  0.   ])] (km, km / s)>
    >>> pv_values * u.Unit('AU, AU/day')
    <Quantity [([1., 0., 0.], [ 0.   ,  0.125,  0.   ]),
               ([0., 1., 0.], [-0.125,  0.   ,  0.   ])] (AU, AU / d)>

As for normal |Quantity| objects, you can access the value and the unit with the `value` and `unit` attribute, respectively. In addition, you can index any given field using its name:

    >>> pv = pv_values * u.Unit('km, km/s')
    >>> pv.value
    array([([1., 0., 0.], [ 0.   ,  0.125,  0.   ]),
           ([0., 1., 0.], [-0.125,  0.   ,  0.   ])],
          dtype=[('p', '<f8', (3,)), ('v', '<f8', (3,))])
    >>> pv.unit
    Unit("(km, km / s)")
    >>> pv['v']
    <Quantity [[ 0.   ,  0.125,  0.   ],
               [-0.125,  0.   ,  0.   ]] km / s>

Structures can be nested, as in this example taken from an |PyERFA| test case for `erfa.ldn()`:

    >>> ldbody = [
    ...     (0.00028574, 3e-10, ([-7.81014427, -5.60956681, -1.98079819],
    ...                          [0.0030723249, -0.00406995477, -0.00181335842])),
    ...     (0.00095435, 3e-9, ([0.738098796, 4.63658692, 1.9693136],
    ...                         [-0.00755816922, 0.00126913722, 0.000727999001])),
    ...     (1.0, 6e-6, ([-0.000712174377, -0.00230478303, -0.00105865966],
    ...                  [6.29235213e-6, -3.30888387e-7, -2.96486623e-7]))
    ...     ] * u.Unit('Msun,radian,(AU,AU/day)')
    >>> ldbody
    <Quantity [(2.8574e-04, 3.e-10, ([-7.81014427e+00, -5.60956681e+00, -1.98079819e+00], [ 3.07232490e-03, -4.06995477e-03, -1.81335842e-03])),
               (9.5435e-04, 3.e-09, ([ 7.38098796e-01,  4.63658692e+00,  1.96931360e+00], [-7.55816922e-03,  1.26913722e-03,  7.27999001e-04])),
               (1.0000e+00, 6.e-06, ([-7.12174377e-04, -2.30478303e-03, -1.05865966e-03], [ 6.29235213e-06, -3.30888387e-07, -2.96486623e-07]))] (solMass, rad, (AU, AU / d))>

## Converting to Different Units

Like regular |Quantity| objects, structured quantities can be converted to different units, as long as they have the same structure and each unit is equivalent.

### Example

To convert a structured quantity to a different unit:

    >>> pv.to((u.m, u.m / u.s))
    <Quantity [([1000.,    0.,    0.], [   0.,  125.,    0.]),
               ([   0., 1000.,    0.], [-125.,    0.,    0.])] (m, m / s)>
    >>> pv.cgs
    <Quantity [([100000.,      0.,      0.], [     0.,  12500.,      0.]),
               ([     0., 100000.,      0.], [-12500.,      0.,      0.])] (cm, cm / s)>

## Use with ERFA

The |ERFA| C routines make use of structured types, and these are exposed in the |PyERFA| interface.

Warning

Not all |PyERFA| routines are wrapped yet. Help with adding wrappers will be appreciated.

### Example

To use a position-velocity structured array with |PyERFA|:

    >>> import erfa
    >>> pv_values = np.array([([1., 0., 0.], [0., 0.125, 0.]),
    ...                       ([0., 1., 0.], [-0.125, 0., 0.])],
    ...                      dtype=erfa.dt_pv)
    >>> pv = pv_values << u.Unit('AU,AU/day')
    >>> erfa.pvu(86400*u.s, pv)
    <Quantity [([ 1.   ,  0.125,  0.   ], [ 0.   ,  0.125,  0.   ]),
               ([-0.125,  1.   ,  0.   ], [-0.125,  0.   ,  0.   ])] (AU, AU / d)>
    >>> erfa.pv2s(pv)
    (<Quantity [0.        , 1.57079633] rad>,
     <Quantity [0., 0.] rad>,
     <Quantity [1., 1.] AU>,
     <Quantity [0.125, 0.125] rad / d>,
     <Quantity [0., 0.] rad / d>,
     <Quantity [0., 0.] AU / d>)
    >>> z_axis = np.array(([0, 0, 1], [0, 0, 0]), erfa.dt_pv) * u.Unit('1,1/s')
    >>> erfa.pvxpv(pv, z_axis)
    <Quantity [([ 0., -1.,  0.], [0.125, 0.   , 0.   ]),
               ([ 1.,  0.,  0.], [0.   , 0.125, 0.   ])] (AU, AU / d)>