Completed
Push — master ( 2553a1...c56626 )
by Ryan
18s
created

PintConverter   A

Complexity

Total Complexity 5

Size/Duplication

Total Lines 19
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 5
c 1
b 0
f 0
dl 0
loc 19
rs 10

1 Method

Rating   Name   Duplication   Size   Complexity  
B convert() 0 16 5
1
# Copyright (c) 2008-2016 MetPy Developers.
2
# Distributed under the terms of the BSD 3-Clause License.
3
# SPDX-License-Identifier: BSD-3-Clause
4
r"""Module to provide unit support.
5
6
This makes use of the :mod:`pint` library and sets up the default settings
7
for good temperature support.
8
9
Attributes
10
----------
11
units : :class:`pint.UnitRegistry`
12
    The unit registry used throughout the package. Any use of units in MetPy should
13
    import this registry and use it to grab units.
14
"""
15
16
from __future__ import division
17
18
import matplotlib.units as munits
19
import numpy as np
20
import pint
21
import pint.unit
22
23
from .cbook import iterable
24
25
UndefinedUnitError = pint.UndefinedUnitError
26
27
units = pint.UnitRegistry(autoconvert_offset_to_baseunit=True)
28
29
# For pint 0.6, this is the best way to define a dimensionless unit. See pint #185
30
units.define(pint.unit.UnitDefinition('percent', '%', (),
31
             pint.converters.ScaleConverter(0.01)))
32
33
34
def concatenate(arrs, axis=0):
35
    r"""Concatenate multiple values into a new unitized object.
36
37
    This is essentially a unit-aware version of `numpy.concatenate`. All items
38
    must be able to be converted to the same units. If an item has no units, it will be given
39
    those of the rest of the collection, without conversion. The first units found in the
40
    arguments is used as the final output units.
41
42
    Parameters
43
    ----------
44
    arrs : Sequence of arrays
45
        The items to be joined together
46
47
    axis : integer, optional
48
        The array axis along which to join the arrays. Defaults to 0 (the first dimension)
49
50
    Returns
51
    -------
52
    `pint.Quantity`
53
        New container with the value passed in and units corresponding to the first item.
54
    """
55
    dest = 'dimensionless'
56
    for a in arrs:
57
        if hasattr(a, 'units'):
58
            dest = a.units
59
            break
60
61
    data = []
62
    for a in arrs:
63
        if hasattr(a, 'to'):
64
            a = a.to(dest).magnitude
65
        data.append(np.atleast_1d(a))
66
67
    return units.Quantity(np.concatenate(data, axis=axis), dest)
68
69
70
def atleast_1d(*arrs):
71
    r"""Convert inputs to arrays with at least one dimension.
72
73
    Scalars are converted to 1-dimensional arrays, whilst other
74
    higher-dimensional inputs are preserved. This is a thin wrapper
75
    around `numpy.atleast_1d` to preserve units.
76
77
    Parameters
78
    ----------
79
    arrs : arbitrary positional arguments
80
        Input arrays to be converted if necessary
81
82
    Returns
83
    -------
84
    `pint.Quantity`
85
        A single quantity or a list of quantities, matching the number of inputs.
86
    """
87
    mags = [a.magnitude for a in arrs]
88
    orig_units = [a.units for a in arrs]
89
    ret = np.atleast_1d(*mags)
90
    if len(mags) == 1:
91
        return units.Quantity(ret, orig_units[0])
92
    return [units.Quantity(m, u) for m, u in zip(ret, orig_units)]
93
94
95
def atleast_2d(*arrs):
96
    r"""Convert inputs to arrays with at least two dimensions.
97
98
    Scalars and 1-dimensional arrays are converted to 2-dimensional arrays,
99
    whilst other higher-dimensional inputs are preserved. This is a thin wrapper
100
    around `numpy.atleast_2d` to preserve units.
101
102
    Parameters
103
    ----------
104
    arrs : arbitrary positional arguments
105
        Input arrays to be converted if necessary
106
107
    Returns
108
    -------
109
    `pint.Quantity`
110
        A single quantity or a list of quantities, matching the number of inputs.
111
    """
112
    mags = [a.magnitude for a in arrs]
113
    orig_units = [a.units for a in arrs]
114
    ret = np.atleast_2d(*mags)
115
    if len(mags) == 1:
116
        return units.Quantity(ret, orig_units[0])
117
    return [units.Quantity(m, u) for m, u in zip(ret, orig_units)]
118
119
120
def masked_array(data, data_units=None, **kwargs):
121
    """Create a :class:`numpy.ma.MaskedArray` with units attached.
122
123
    This is a thin wrapper around :func:`numpy.ma.masked_array` that ensures that
124
    units are properly attached to the result (otherwise units are silently lost). Units
125
    are taken from the ``units`` argument, or if this is ``None``, the units on ``data``
126
    are used.
127
128
    Parameters
129
    ----------
130
    data : array_like
131
        The source data. If ``units`` is `None`, this should be a `pint.Quantity` with
132
        the desired units.
133
    data_units : str or `pint.Unit`
134
        The units for the resulting `pint.Quantity`
135
    **kwargs : Arbitrary keyword arguments passed to `numpy.ma.masked_array`
136
137
    Returns
138
    -------
139
    `pint.Quantity`
140
    """
141
    if data_units is None:
142
        data_units = data.units
143
    return units.Quantity(np.ma.masked_array(data, **kwargs), data_units)
144
145
146
class PintConverter(munits.ConversionInterface):
147
    """Implement support for pint within matplotlib's unit conversion framework."""
148
149
    @staticmethod
150
    def convert(value, unit, axis):
151
        """Convert pint :`Quantity` instances for matplotlib to use.
152
153
        Currently only strips off the units to avoid matplotlib errors since we can't reliably
154
        have pint.Quantity instances not decay to numpy arrays.
155
        """
156
        if hasattr(value, 'magnitude'):
157
            return value.magnitude
158
        elif iterable(value):
159
            try:
160
                return [v.magnitude for v in value]
161
            except AttributeError:
162
                return value
163
        else:
164
            return value
165
166
    # TODO: Once we get things properly squared away between pint and everything else
167
    # these will need to be functional.
168
    # @staticmethod
169
    # def axisinfo(unit, axis):
170
    #     return None
171
    #
172
    # @staticmethod
173
    # def default_units(x, axis):
174
    #     return x.to_base_units()
175
176
177
# Register the class
178
munits.registry[units.Quantity] = PintConverter()
179
180
del munits, pint
181