Note: The default ITS GitLab runner is a shared resource and is subject to slowdowns during heavy usage.
You can run your own GitLab runner that is dedicated just to your group if you need to avoid processing delays.

tools.py 5.81 KB
Newer Older
1
2
3
4
5
6
7
#!/usr/bin/env python
"""Tools to be used in swmfpy functions and classes. Some of the functions are
*hidden functions*.
"""
__author__ = 'Qusai Al Shidi'
__email__ = 'qusai@umich.edu'

8
import datetime as dt
Qusai Al Shidi's avatar
Qusai Al Shidi committed
9
10
import numpy as np

Qusai Al Shidi's avatar
Qusai Al Shidi committed
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
def limit_growth(vector, factor=1.3, look_ahead=1, extensively=False):
    """This is a growth limiter for 1D vectors. It helps clean out spikes.

    Use this to clean out spikes in IMF data for example.

    Args:
        vector (numpy.array):
            A 1D array to clean.
        factor (float):
            The factor in which to limit growth. (default 1.3)
        look_ahead (int):
            How many frames to look ahead for growth.
        extensively (bool):
            Will keep refining until fully limited instead of one pass through.
            (default False)

    Returns:
        (numpy.array): Of limited vector.

    Raises:
        ValueError: If not given 1D array.

    Examples:
        ```python

        from datetime import datetime
        import matplotlib.pyplot as plt
        from swmfpy.web import get_omni_data
        from swmfpy.tools import limit_growth

        times = (datetime(2011, 8, 6), datetime(2011, 8, 7))
        data = get_omni_data(*times)
        plt.plot(data['times'], data['density'])
        plt.plot(data['times'], limit_growth(data['density'],
                                             extensively=True)
        ```
    """
    if sum(vector.shape) != vector.size:
        raise ValueError('limit_growth() only handles 1D arrays')

    limited = np.copy(vector)

    for i in range(look_ahead, len(limited)):
        if limited[i] > 0 and limited[i-look_ahead] > 0:
            limited[i] = min(limited[i-look_ahead]*factor,
                             max(limited[i], limited[i-look_ahead]/factor)
                            )
        if limited[i] < 0 and limited[i-look_ahead] < 0:
            limited[i] = min(limited[i-look_ahead]/factor,
                             max(limited[i], limited[i-look_ahead]*factor)
                            )

    return limited


def limit_changes(vector, change, constrictor=None, change2=None, look_ahead=1):
    """Limit the changes (jumps) of an array.

    This is different from #limit_growth() because growth works on a 
    multiplication factor but `limit_change()` works on absolute changes.

    Args:
        vector (numpy.array):
            Array in which to limit changes.
        change ((float, float)):
            Changes to limit `vector` by.
        constrictor (numpy.array):
            Array of same shape as `vector` in which if it is not growing
            then constrict further. This is useful for limiting compression
            behind shocks. If None, this won't apply (default None)
        change2 ((float, float)):
            Just like `change` but after constrictor has not grown. If None,
            this won't apply (default None)
        look_ahead (int):
            How many indeces ahead to check (default 1)

    Returns:
        (numpy.array): Of constricted `vector`.

    Raises:
        ValueError: If arrays are of different shapes or not 1D each.
        ValueError: If `change` tuple is not (negative, positive)

    Examples:
    """
    if sum(vector.shape) != vector.size:
        raise ValueError('vector must be 1D')
    if any(constrictor) and sum(constrictor.shape) != constrictor.size:
        raise ValueError('constrictor must be 1D')
    if change[0] > 0 or change[1] < 0:
        raise ValueError('change must be (negative, positive)')
    if change2[0] > 0 or change2[1] < 0:
        raise ValueError('change2 must be (negative, positive)')

    limited = np.copy(vector)
    for i in range(look_ahead, len(vector)):
        limited[i] = min(limited[i-1]+change[0],
                         max(limited[i-1]+change[1],
                             limited[i])
                        )

    if any(constrictor):
        for i in range(look_ahead, len(vector)):
            if constrictor[i] <= constrictor[i-1]:
                limited[i] = min(limited[i-1]+change2[0],
                                 max(limited[i-1]+change2[1],
                                     limited[i])
                                )

    return limited


Qusai Al Shidi's avatar
Qusai Al Shidi committed
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138

def interp_nans(x_vals, y_vals):
    """Returns a numpy array with NaNs interpolated.

    Args:
        x_vals (np.array):
            x values to interpolate.
        y_vals (np.array):
            y values including NaNs.

    Returns:
        (np.array): numpy array with NaNs interpolated.
    """

    nonans = np.nonzero(np.isnan(y_vals) == 0)
    return np.interp(x_vals, x_vals[nonans], y_vals[nonans])
139
140
141


def carrington_rotation_number(the_time='now'):
142
143
144
145
146
147
148
149
150
    """Returns the carrington rotation

    Args:
        the_time (datetime.datetime/str): The time to convert to carrington
                                          rotation.

    Returns:
        (int): Carrington Rotation
    """
151
152
153
154
155
156
    if the_time == 'now':
        return carrington_rotation_number(dt.datetime.now())
    if isinstance(the_time, str):
        return carrington_rotation_number(dt.datetime.fromisoformat(the_time))
    return int((the_time.toordinal() - 676715.2247)/27.2753)

157
158

def _import_error_string(library):
159
    return (
160
        f'Error importing {library}. '
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
        f'Maybe try `pip install -U {library} --user ` .'
        )


def _make_line(value):
    """Makes the paramin line based on value type recursively
    """
    if isinstance(value, str):
        return value
    if isinstance(value, (list, tuple)):
        return '\t\t\t'.join([_make_line(v) for v in value])
    try:
        str(value)
    except Exception as error:
        raise TypeError(error,
                        '\nMust reduce to a str or a method that has',
                        '__str__ or __repr__')
    return str(value)
179
180
181
182
183


def _nearest(pivot, items):
    """Returns nearest point"""
    return min(items, key=lambda x: abs(x - pivot))