• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

TRI-AMDD / mpet / 8476080312

29 Mar 2024 01:46AM UTC coverage: 55.461% (-7.2%) from 62.648%
8476080312

Pull #126

github

d-cogswell
Adds a benchmark section to docs.
Pull Request #126: v1.0.0

2351 of 4239 relevant lines covered (55.46%)

2.22 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

75.73
/mpet/utils.py
1
import subprocess as subp
4✔
2

3
import os
4✔
4
import sys
4✔
5
import importlib
4✔
6
import numpy as np
4✔
7
import h5py
4✔
8
import scipy.io as sio
4✔
9

10
import daetools.pyDAE as dae
4✔
11

12

13
def mean_linear(a):
4✔
14
    """Calculate the linear mean along a vector."""
15
    return 0.5*(a[1:] + a[:-1])
4✔
16

17

18
def weighted_linear_mean(a, wt):
4✔
19
    return (wt[1:]*a[1:] + wt[:-1]*a[:-1])/(wt[1:]+wt[:-1])
4✔
20

21

22
def mean_harmonic(a):
4✔
23
    """Calculate the harmonic mean along a vector."""
24
    return (2 * a[1:] * a[:-1]) / (a[1:] + a[:-1] + 1e-20)
4✔
25

26

27
def weighted_harmonic_mean(a, wt):
4✔
28
    return ((wt[1:]+wt[:-1])/(wt[1:]/a[1:]+wt[:-1]/a[:-1]))
4✔
29

30

31
def add_gp_to_vec(vec):
4✔
32
    """Add ghost points to the beginning and end of a vector for applying boundary conditions."""
33
    out = np.empty(len(vec) + 2, dtype=object)
4✔
34
    out[1:-1] = vec
4✔
35
    return out
4✔
36

37

38
def pad_vec(vec):
4✔
39
    """Repeat a vector's first and last values, extending its length by two."""
40
    out = add_gp_to_vec(vec)
4✔
41
    out[0] = out[1]
4✔
42
    out[-1] = out[-2]
4✔
43
    return out
4✔
44

45

46
def get_const_vec(val, N):
4✔
47
    """Convert a constant to an array of length N."""
48
    out = np.array([val for indx in range(N)], dtype=object)
4✔
49
    return out
4✔
50

51

52
def get_var_vec(var, N, dt=False):
4✔
53
    """Convert a dae tools variable to a numpy array. Optionally return the time derivative of the
54
    variable.
55
    """
56
    if dt is True:
4✔
57
        out = np.array([var.dt(indx) for indx in range(N)])
4✔
58
    else:
59
        out = np.array([var(indx) for indx in range(N)])
4✔
60
    return out
4✔
61

62

63
def get_asc_vec(var, Nvol, dt=False):
4✔
64
    """Get a numpy array for a variable spanning the anode, separator, and cathode."""
65
    varout = {}
4✔
66
    for sectn in ["a", "s", "c"]:
4✔
67
        # If we have information within this battery section
68
        if sectn in var.keys():
4✔
69
            # If it's an array of dae variable objects
70
            if isinstance(var[sectn], dae.pyCore.daeVariable):
4✔
71
                varout[sectn] = get_var_vec(var[sectn], Nvol[sectn], dt)
4✔
72
            # Otherwise, it's a parameter that varies with electrode section
73
            elif isinstance(var[sectn], np.ndarray):
4✔
74
                varout[sectn] = var[sectn]
×
75
            else:
76
                varout[sectn] = get_const_vec(var[sectn], Nvol[sectn])
4✔
77
        # Otherwise, fill with zeros
78
        else:
79
            try:
4✔
80
                varout[sectn] = np.zeros(Nvol[sectn])
4✔
81
            except KeyError:
4✔
82
                varout[sectn] = np.zeros(0)
4✔
83
    out = np.hstack((varout["a"], varout["s"], varout["c"]))
4✔
84
    return out
4✔
85

86

87
def get_dxvec(L, Nvol):
4✔
88
    """Get a vector of cell widths spanning the full cell."""
89
    if "a" in Nvol:
4✔
90
        dxa = Nvol["a"] * [L["a"]/Nvol["a"]]
4✔
91
    else:
92
        dxa = []
4✔
93
    if Nvol["s"]:
4✔
94
        dxs = Nvol["s"] * [L["s"]/Nvol["s"]]
4✔
95
    else:
96
        dxs = []
4✔
97
    dxc = Nvol["c"] * [L["c"]/Nvol["c"]]
4✔
98
    out = np.array(dxa + dxs + dxc)
4✔
99
    return out
4✔
100

101

102
def get_git_info(local_dir, shell=False):
4✔
103
    commit_hash = subp.check_output(['git', '-C', local_dir, 'rev-parse', '--short', 'HEAD'],
4✔
104
                                    stderr=subp.STDOUT, universal_newlines=True, shell=shell)
105
    commit_diff = subp.check_output(['git', '-C', local_dir, 'diff'],
4✔
106
                                    stderr=subp.STDOUT, universal_newlines=True, shell=shell)
107
    branch_name = subp.check_output(
4✔
108
        ['git', '-C', local_dir, 'rev-parse', '--abbrev-ref', 'HEAD'],
109
        stderr=subp.STDOUT, universal_newlines=True)
110
    return branch_name, commit_hash, commit_diff
4✔
111

112

113
def open_data_file(dataFile):
4✔
114
    """Load hdf5/mat file output.
115
    Always defaults to .mat file, else opens .hdf5 file.
116
    Takes in dataFile (path of file without .mat or .hdf5), returns data (output of array)"""
117
    data = []
4✔
118
    if os.path.isfile(dataFile + ".mat"):
4✔
119
        data = sio.loadmat(dataFile + ".mat")
4✔
120
    elif os.path.isfile(dataFile + ".hdf5"):
4✔
121
        data = h5py.File(dataFile + ".hdf5", 'r')
4✔
122
    else:
123
        raise Exception("Data output file not found for either mat or hdf5 in " + dataFile)
×
124
    return data
4✔
125

126

127
def get_dict_key(data, string, squeeze=True, final=False):
4✔
128
    """Gets the values in a 1D array, which is formatted slightly differently
129
    depending on whether it is a h5py file or a mat file
130
    Takes in data array and the string whose value we want to get. Final is a
131
    boolean that determines whether or not it only returns the final value of the array.
132
    If final is true, then it only returns the last value, otherwise it returns the entire array.
133
    Final overwrites squeeze--if final is true, then the array will always be squeezed.
134
    Squeeze squeezes into 1D array if is true, otherwise false"""
135
    # do not call both squeeze false and final true!!!
136
    if final:  # only returns last value
4✔
137
        return data[string][...,-1].item()
4✔
138
    elif squeeze:
×
139
        return np.squeeze(data[string][...])
×
140
    else:  # returns entire array
141
        return data[string][...]
×
142

143

144
def get_negative_sign_change_arrays(input_array):
4✔
145
    """This function takes an array of (+1, +1, +1, -1, -1, -1... +1, -1 ...) and splits it
146
       into a number of arrays in the y direction which are (0, 0, 0, 1, 1, 1... 0, 0)
147
       whenever the array hits a sign change. It should have the number of cycles as rows.
148
       Thus it will be size (N*M) for each output, where N is the number of cycles
149
       and M is the size of the array. In each ith row there are only 1's for the ith charging
150
       cycle.
151
       Returns beginning and end discharge and charge segments in order
152
       """
153
    sign_mults = np.zeros((len(input_array) - 1))  # is +1 if no sign change, -1 if sign change@i+1
×
154
    for i in range(len(input_array)-1):
×
155
        # ends up 0 if no sign change, +1 if sign change
156
        sign_mults[i] = (input_array[i] * input_array[i+1] - 1) / (-2)
×
157
    # if we have no outputs with sign change, then end
158
    if np.all(sign_mults == 0):
×
159
        print("ERROR: Did not complete a single cycle, cannot plot cycling plots")
×
160
        raise
×
161
    # the odd sign changes indicate the beginning of the discharge cycle
162
    indices = np.array(np.nonzero(sign_mults)).flatten()  # get indices of nonzero elements
×
163
    neg_indices_start = indices[::2] + 1
×
164
    neg_indices_end = indices[1::2] + 1
×
165
    pos_indices_start = indices[1::2] + 1
×
166
    pos_indices_start = np.delete(pos_indices_start, -1)
×
167
    pos_indices_start = np.insert(pos_indices_start, 0, 0)
×
168
    pos_indices_end = indices[::2] + 1
×
169
    return neg_indices_start, neg_indices_end, pos_indices_start, pos_indices_end
×
170

171

172
def import_function(filename, function, mpet_module=None):
4✔
173
    """Load a function from a file that is not part of MPET, with a fallback to MPET internal
174
    functions.
175

176
    :param Config config: MPET configuration
177
    :param str filename: .py file containing the function to import. None to load from mpet_module
178
                         instead
179
    :param str function: Name of the function to import
180
    :param str mpet_module: MPET module to import function from if no filename is set (optional)
181

182
    :return: A callable function
183
    """
184
    if filename is None:
4✔
185
        # no filename set, load function from mpet itself
186
        module = importlib.import_module(mpet_module)
4✔
187
    else:
188
        # Import module  which contains the function we seek,
189
        # we need to call import_module because the module import is dependent
190
        # on a variable name.
191
        # sys.path is used to temporarily have only the folder containig the module we
192
        # need to import in the Python search path for imports
193
        # the following lines can be interpreted as "import <module_name>"
194
        folder = os.path.dirname(os.path.abspath(filename))
×
195
        module_name = os.path.splitext(os.path.basename(filename))[0]
×
196
        old_path = sys.path
×
197
        sys.path = [folder]
×
198
        module = importlib.import_module(module_name)
×
199
        sys.path = old_path
×
200

201
    # import the function from the module
202
    callable_function = getattr(module, function)
4✔
203

204
    return callable_function
4✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc