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

TRI-AMDD / mpet / 6397333417

03 Oct 2023 07:04PM UTC coverage: 55.65% (-7.0%) from 62.648%
6397333417

Pull #126

github

d-cogswell
Merge branch 'master' into development to enable automatic building at
readthedocs.
Pull Request #126: v1.0.0

2359 of 4239 relevant lines covered (55.65%)

0.56 hits per line

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

1.81
/mpet/plot/plot_data.py
1
import os
1✔
2

3
import matplotlib as mpl
1✔
4
import matplotlib.animation as manim
1✔
5
import matplotlib.collections as mcollect
1✔
6
import matplotlib.pyplot as plt
1✔
7
import numpy as np
1✔
8
import scipy.integrate as integrate
1✔
9
from scipy.interpolate import interp1d
1✔
10

11
import mpet.geometry as geom
1✔
12
import mpet.mod_cell as mod_cell
1✔
13
import mpet.utils as utils
1✔
14
from mpet.config import Config, constants
1✔
15

16
"""Set list of matplotlib rc parameters to make more readable plots."""
1✔
17
# axtickfsize = 18
18
# labelfsize = 20
19
# mpl.rcParams['xtick.labelsize'] = axtickfsize
20
# mpl.rcParams['ytick.labelsize'] = axtickfsize
21
# mpl.rcParams['axes.labelsize'] = labelfsize
22
# mpl.rcParams['font.size'] = labelfsize - 2
23
# mpl.rcParams['legend.fontsize'] = labelfsize - 2
24
# mpl.rcParams['lines.linewidth'] = 3
25
# mpl.rcParams['lines.markersize'] = 10
26
# mpl.rcParams['lines.markeredgewidth'] = 0.1
27
# mpl.rcParams['text.usetex'] = True
28

29

30
def show_data(indir, plot_type, print_flag, save_flag, data_only, color_changes, smooth_type,
1✔
31
              vOut=None, pOut=None, tOut=None):
32
    pfx = 'mpet.'
×
33
    sStr = "_"
×
34
    ttl_fmt = "% = {perc:2.1f}"
×
35
    # Read in the simulation results and calcuations data
36
    dataFileName = "output_data"
×
37
    dataFile = os.path.join(indir, dataFileName)
×
38
    data = utils.open_data_file(dataFile)
×
39
    try:
×
40
        utils.get_dict_key(data, pfx + 'current')
×
41
    except KeyError:
×
42
        pfx = ''
×
43
    try:
×
44
        utils.get_dict_key(data, pfx + "partTrodecvol0part0" + sStr + "cbar")
×
45
    except KeyError:
×
46
        sStr = "."
×
47
    # Read in the parameters used to define the simulation
48
    config = Config.from_dicts(indir)
×
49
    # simulated (porous) electrodes
50
    trodes = config["trodes"]
×
51
    # Pick out some useful calculated values
52
    limtrode = config["limtrode"]
×
53
    tot_cycle = config["totalCycle"]
×
54
    k = constants.k                      # Boltzmann constant, J/(K Li)
×
55
    Tref = constants.T_ref               # Temp, K
×
56
    e = constants.e                      # Charge of proton, C
×
57
    F = constants.F                      # C/mol
×
58
    c_ref = constants.c_ref
×
59
    td = config["t_ref"]
×
60
    Etheta = {"a": 0.}
×
61
    cap = config[limtrode, "cap"]
×
62
    for trode in trodes:
×
63
        Etheta[trode] = -(k*Tref/e) * config[trode, "phiRef"]
×
64
    Vstd = Etheta["c"] - Etheta["a"]
×
65
    dataReporter = config["dataReporter"]
×
66
    Nvol = config["Nvol"]
×
67
    Npart = config["Npart"]
×
68
    psd_len = config["psd_len"]
×
69
    # Discretization (and associated porosity)
70
    Lfac = 1e6
×
71
    Lunit = r"$\mu$m"
×
72
    dxc = config["L"]["c"]/Nvol["c"]
×
73
    dxvec = np.array(Nvol["c"] * [dxc])
×
74
    porosvec = np.array(Nvol["c"] * [config["poros"]["c"]])
×
75
    cellsvec = dxc*np.arange(Nvol["c"]) + dxc/2.
×
76
    if config["Nvol"]["s"]:
×
77
        dxs = config["L"]["s"]/Nvol["s"]
×
78
        dxvec_s = np.array(Nvol["s"] * [dxs])
×
79
        dxvec = np.hstack((dxvec_s, dxvec))
×
80
        poros_s = np.array(Nvol["s"] * [config["poros"]["s"]])
×
81
        porosvec = np.hstack((poros_s, porosvec))
×
82
        cellsvec += config["L"]["s"] / config["L"]["c"]
×
83
        cellsvec_s = dxs*np.arange(Nvol["s"]) + dxs/2.
×
84
        cellsvec = np.hstack((cellsvec_s, cellsvec))
×
85
    if "a" in trodes:
×
86
        dxa = config["L"]["a"]/Nvol["a"]
×
87
        dxvec_a = np.array(Nvol["a"] * [dxa])
×
88
        dxvec = np.hstack((dxvec_a, dxvec))
×
89
        poros_a = np.array(Nvol["a"] * [config["poros"]["a"]])
×
90
        porosvec = np.hstack((poros_a, porosvec))
×
91
        cellsvec += config["L"]["a"] / config["L"]["c"]
×
92
        cellsvec_a = dxa*np.arange(Nvol["a"]) + dxa/2.
×
93
        cellsvec = np.hstack((cellsvec_a, cellsvec))
×
94
    cellsvec *= config["L_ref"] * Lfac
×
95
    facesvec = np.insert(np.cumsum(dxvec), 0, 0.) * config["L_ref"] * Lfac
×
96
    # Extract the reported simulation times
97
    times = utils.get_dict_key(data, pfx + 'phi_applied_times')
×
98
    numtimes = len(times)
×
99
    tmin = np.min(times)
×
100
    tmax = np.max(times)
×
101
    # Simulation type
102
    profileType = config['profileType']
×
103
    # Colors for plotting concentrations
104
    to_yellow = 0.3
×
105
    to_red = 0.7
×
106
    scl = 1.0  # static
×
107
#    scl = 1.2  # movies
108
    figsize = (scl*6, scl*4)
×
109

110
    # Print relevant simulation info
111
    if print_flag:
×
112
        print("profileType:", profileType)
×
113
#        for i in trodes:
114
#            print "PSD_{l}:".format(l=l)
115
#            print psd_len[l].transpose()
116
#            print "Actual psd_mean [nm]:", np.mean(psd_len[l])
117
#            print "Actual psd_stddev [nm]:", np.std(psd_len[l])
118
        print("Cell structure:")
×
119
        print(("porous anode | " if "a" in config["trodes"] else "flat anode | ")
×
120
              + ("sep | " if config["Nvol"]["s"] else "") + "porous cathode")
121
        if "a" in config["trodes"]:
×
122
            print("capacity ratio cathode:anode, 'z':", config["z"])
×
123
        for trode in trodes:
×
124
            print("solidType_{t}:".format(t=trode), config[trode, 'type'])
×
125
            print("solidShape_{t}".format(t=trode), config[trode, 'shape'])
×
126
            print("rxnType_{t}:".format(t=trode), config[trode, 'rxnType'])
×
127
        if profileType == "CC":
×
128
            print("C_rate:", config['Crate'])
×
129
            theoretical_1C_current = config[config['limtrode'], 'cap'] / 3600.
×
130
            currset_dim = config['currset'] * theoretical_1C_current * config['curr_ref']
×
131
            print("current:", currset_dim, "A/m^2")
×
132
        elif profileType == "CV":  # CV
×
133
            Vref = config['c', 'phiRef']
×
134
            if 'a' in config["trodes"]:
×
135
                Vref -= config['a', 'phiRef']
×
136
            Vset_dim = -(config['Vset'] * k * Tref / e - Vref)
×
137
            print("Vset:", Vset_dim)
×
138
        print("Specified psd_mean, c [{unit}]:".format(unit=Lunit),
×
139
              np.array(config['mean']["c"])*Lfac)
140
        print("Specified psd_stddev, c [{unit}]:".format(unit=Lunit),
×
141
              np.array(config['stddev']["c"])*Lfac)
142
        print("ndim B_c:", config["c", "B"])
×
143
        if config["Nvol"]["s"]:
×
144
            print("Nvol_s:", Nvol["s"])
×
145
        print("Nvol_c:", Nvol["c"])
×
146
        if 'a' in config["trodes"]:
×
147
            print("Nvol_a:", Nvol["a"])
×
148
        print("Npart_c:", Npart["c"])
×
149
        if 'a' in config["trodes"]:
×
150
            print("Npart_a:", Npart["a"])
×
151
        print("td [s]:", config["t_ref"])
×
152
        for trode in trodes:
×
153
            if trode == "a":
×
154
                k0 = config.D_a["k0"]
×
155
            elif trode == "c":
×
156
                k0 = config.D_c["k0"]
×
157
            else:
158
                raise Exception(f"Unknown trode: {trode}")
×
159
            print("k0_{t} [A/m^2]:".format(t=trode), k0)
×
160
            rxnType = config[trode, 'rxnType']
×
161
            if rxnType == "BV":
×
162
                print("alpha_" + trode + ":", config[trode, 'alpha'])
×
163
            elif rxnType in ["Marcus", "MHC"]:
×
164
                print("lambda_" + trode + "/(kTref):", config[trode, "lambda"]
×
165
                      * k * Tref)
166
            if config['simBulkCond'][trode]:
×
167
                print(trode + " bulk conductivity loss: Yes -- "
×
168
                      + "sigma_s [S/m]: " + str(config['sigma_s'][trode] * config['sigma_s_ref']))
169
            else:
170
                print(trode + " bulk conductivity loss: No")
×
171
            try:
×
172
                simSurfCond = config[trode, 'simSurfCond']
×
173
                if simSurfCond:
×
174
                    print(trode + " surface conductivity loss: Yes -- "
×
175
                          + "dim_scond [S]: " + str(config[trode, 'scond']))
176
                else:
177
                    print(trode + " surface conductivity loss: No")
×
178
            except Exception:
×
179
                pass
×
180
#            if ndD['simSurfCond'][l]:
181
#                print (l + " surface conductivity loss: Yes -- " +
182
#                        "dim_scond [S]: " + str(dD['scond'][l]))
183
#            else:
184
#                print l + " surface conductivity loss: No"
185

186
    if plot_type in ["params"]:
×
187
        # return ndD_s, dD_s, ndD_e, dD_e
188
        return config
×
189
    if plot_type in ["discData"]:
×
190
        return cellsvec/Lfac, facesvec/Lfac
×
191

192
    # Plot voltage profile
193
    if plot_type in ["v", "vt"]:
×
194
        voltage = (Vstd
×
195
                   - (k*Tref/e)*utils.get_dict_key(data, pfx + 'phi_applied'))
196
        ffvec = utils.get_dict_key(data, pfx + 'ffrac_c')
×
197
        fig, ax = plt.subplots(figsize=figsize)
×
198
        if plot_type == "v":
×
199
            if data_only:
×
200
                plt.close(fig)
×
201
                return ffvec, voltage
×
202
            ax.plot(ffvec, voltage)
×
203
            xmin = 0.
×
204
            xmax = 1.
×
205
            ax.set_xlim((xmin, xmax))
×
206
            ax.set_xlabel("Cathode Filling Fraction [dimensionless]")
×
207
        elif plot_type == "vt":
×
208
            if data_only:
×
209
                plt.close(fig)
×
210
                return times*td, voltage
×
211
            ax.plot(times*td, voltage)
×
212
            ax.set_xlabel("Time [s]")
×
213
        ax.set_ylabel("Voltage [V]")
×
214
        if save_flag:
×
215
            fig.savefig("mpet_v.pdf", bbox_inches="tight")
×
216
        return fig, ax
×
217

218
    # Plot surface conc.
219
    if plot_type[:-2] in ["surf"]:
×
220
        if dataReporter == "hdf5Fast":
×
221
            # hdf5Fast does not print internal particle concentrations
222
            raise Exception("hdf5Fast dataReporter does not print internal particle "
×
223
                            "concentrations, rerun simulation with another data reporter")
224
        trode = plot_type[-1]
×
225
        str_base = (pfx
×
226
                    + "partTrode{trode}vol{{vInd}}part{{pInd}}".format(trode=trode)
227
                    + sStr + "c")
228
        if data_only:
×
229
            sol_str = str_base.format(pInd=pOut, vInd=vOut)
×
230
            datay = utils.get_dict_key(data, sol_str, squeeze=False)[:,-1]
×
231
            return times*td, datay
×
232
        fig, ax = plt.subplots(Npart[trode], Nvol[trode], squeeze=False, sharey=True,
×
233
                               figsize=figsize)
234
        ylim = (0, 1.01)
×
235
        datax = times
×
236
        for pInd in range(Npart[trode]):
×
237
            for vInd in range(Nvol[trode]):
×
238
                sol_str = str_base.format(pInd=pInd, vInd=vInd)
×
239
                # Remove axis ticks
240
                ax[pInd,vInd].xaxis.set_major_locator(plt.NullLocator())
×
241
                datay = utils.get_dict_key(data, sol_str, squeeze=False)[:,-1]
×
242
                line, = ax[pInd,vInd].plot(times, datay)
×
243
        if save_flag:
×
244
            fig.savefig("mpet_surf.pdf", bbox_inches="tight")
×
245
        return fig, ax
×
246

247
    # Plot SoC profile
248
    if plot_type[:-2] in ["soc"]:
×
249
        trode = plot_type[-1]
×
250
        ffvec = utils.get_dict_key(data, pfx + 'ffrac_{trode}'.format(trode=trode))
×
251
        if data_only:
×
252
            return times*td, ffvec
×
253
        fig, ax = plt.subplots(figsize=figsize)
×
254
        ax.plot(times*td, ffvec)
×
255
        xmin = np.min(ffvec)
×
256
        xmax = np.max(ffvec)
×
257
        ax.set_ylim((0, 1.05))
×
258
        ax.set_xlabel("Time [s]")
×
259
        ax.set_ylabel("Filling Fraciton [dimless]")
×
260
        if save_flag:
×
261
            fig.savefig("mpet_soc.pdf", bbox_inches="tight")
×
262
        return fig, ax
×
263

264
    # Check to make sure mass is conserved in elyte
265
    if plot_type == "elytecons":
×
266
        fig, ax = plt.subplots(figsize=figsize)
×
267
        eps = 1e-2
×
268
        ymin = 1-eps
×
269
        ymax = 1+eps
×
270
#        ax.set_ylim((ymin, ymax))
271
        ax.set_ylabel('Avg. Concentration of electrolyte [nondim]')
×
272
        sep = pfx + 'c_lyte_s'
×
273
        anode = pfx + 'c_lyte_a'
×
274
        cath = pfx + 'c_lyte_c'
×
275
        ax.set_xlabel('Time [s]')
×
276
        cvec = utils.get_dict_key(data, cath)
×
277
        if Nvol["s"]:
×
278
            cvec_s = utils.get_dict_key(data, sep)
×
279
            cvec = np.hstack((cvec_s, cvec))
×
280
        if "a" in trodes:
×
281
            cvec_a = utils.get_dict_key(data, anode)
×
282
            cvec = np.hstack((cvec_a, cvec))
×
283
        cavg = np.sum(porosvec*dxvec*cvec, axis=1)/np.sum(porosvec*dxvec)
×
284
        if data_only:
×
285
            plt.close(fig)
×
286
            return times*td, cavg
×
287
        np.set_printoptions(precision=8)
×
288
        ax.plot(times*td, cavg)
×
289
        if save_flag:
×
290
            fig.savefig("mpet_elytecons.pdf", bbox_inches="tight")
×
291
        return fig, ax
×
292

293
    # Plot current profile
294
    if plot_type == "curr":
×
295
        theoretical_1C_current = config[config['limtrode'], "cap"] / 3600.  # A/m^2
×
296
        current = (utils.get_dict_key(data, pfx + 'current')
×
297
                   * theoretical_1C_current / config['1C_current_density'] * config['curr_ref'])
298
        ffvec = utils.get_dict_key(data, pfx + 'ffrac_c')
×
299
        if data_only:
×
300
            return times*td, current
×
301
        fig, ax = plt.subplots(figsize=figsize)
×
302
        ax.plot(times*td, current)
×
303
        xmin = np.min(ffvec)
×
304
        xmax = np.max(ffvec)
×
305
        ax.set_xlabel("Time [s]")
×
306
        ax.set_ylabel("Current [C-rate]")
×
307
        if save_flag:
×
308
            fig.savefig("mpet_current.png", bbox_inches="tight")
×
309
        return fig, ax
×
310

311
    elif plot_type == "power":
×
312
        current = utils.get_dict_key(data, pfx + 'current') * (3600/td) * (cap/3600)  # in A/m^2
×
313
        voltage = (Vstd - (k*Tref/e)*utils.get_dict_key(data, pfx + 'phi_applied'))  # in V
×
314
        power = np.multiply(current, voltage)
×
315
        if data_only:
×
316
            return times*td, power
×
317
        fig, ax = plt.subplots(figsize=figsize)
×
318
        ax.plot(times*td, power)
×
319
        ax.set_xlabel("Time [s]")
×
320
        ax.set_ylabel(r"Power [W/m$^2$]")
×
321
        if save_flag:
×
322
            fig.savefig("mpet_power.png", bbox_inches="tight")
×
323
        return fig, ax
×
324

325
    # Plot electrolyte concentration or potential
326
    elif plot_type in ["elytec", "elytep", "elytecf", "elytepf",
×
327
                       "elytei", "elyteif", "elytedivi", "elytedivif"]:
328
        fplot = (True if plot_type[-1] == "f" else False)
×
329
        t0ind = (0 if not fplot else -1)
×
330
        datax = cellsvec
×
331
        c_sep, p_sep = pfx + 'c_lyte_s', pfx + 'phi_lyte_s'
×
332
        c_anode, p_anode = pfx + 'c_lyte_a', pfx + 'phi_lyte_a'
×
333
        c_cath, p_cath = pfx + 'c_lyte_c', pfx + 'phi_lyte_c'
×
334
        datay_c = utils.get_dict_key(data, c_cath, squeeze=False)
×
335
        datay_p = utils.get_dict_key(data, p_cath, squeeze=False)
×
336
        L_c = config['L']["c"] * config['L_ref'] * Lfac
×
337
        Ltot = L_c
×
338
        if config["Nvol"]["s"]:
×
339
            datay_s_c = utils.get_dict_key(data, c_sep, squeeze=False)
×
340
            datay_s_p = utils.get_dict_key(data, p_sep, squeeze=False)
×
341
            datay_c = np.hstack((datay_s_c, datay_c))
×
342
            datay_p = np.hstack((datay_s_p, datay_p))
×
343
            L_s = config['L']["s"] * config['L_ref'] * Lfac
×
344
            Ltot += L_s
×
345
        else:
346
            L_s = 0
×
347
        if "a" in trodes:
×
348
            datay_a_c = utils.get_dict_key(data, c_anode, squeeze=False)
×
349
            datay_a_p = utils.get_dict_key(data, p_anode, squeeze=False)
×
350
            datay_c = np.hstack((datay_a_c, datay_c))
×
351
            datay_p = np.hstack((datay_a_p, datay_p))
×
352
            L_a = config['L']["a"] * config['L_ref'] * Lfac
×
353
            Ltot += L_a
×
354
        else:
355
            L_a = 0
×
356
        xmin = 0
×
357
        xmax = Ltot
×
358
        if plot_type in ["elytec", "elytecf"]:
×
359
            ylbl = 'Concentration of electrolyte [M]'
×
360
            datay = datay_c * c_ref / 1000.
×
361
        elif plot_type in ["elytep", "elytepf"]:
×
362
            ylbl = 'Potential of electrolyte [V]'
×
363
            datay = datay_p*(k*Tref/e) - Vstd
×
364
        elif plot_type in ["elytei", "elyteif", "elytedivi", "elytedivif"]:
×
365
            cGP_L = utils.get_dict_key(data, "c_lyteGP_L")
×
366
            pGP_L = utils.get_dict_key(data, "phi_lyteGP_L")
×
367
            cmat = np.hstack((cGP_L.reshape((-1,1)), datay_c, datay_c[:,-1].reshape((-1,1))))
×
368
            pmat = np.hstack((pGP_L.reshape((-1,1)), datay_p, datay_p[:,-1].reshape((-1,1))))
×
369
            disc = geom.get_elyte_disc(
×
370
                Nvol, config["L"], config["poros"], config["BruggExp"])
371
            i_edges = np.zeros((numtimes, len(facesvec)))
×
372
            for tInd in range(numtimes):
×
373
                i_edges[tInd, :] = mod_cell.get_lyte_internal_fluxes(
×
374
                    cmat[tInd, :], pmat[tInd, :], disc, config)[1]
375
            if plot_type in ["elytei", "elyteif"]:
×
376
                ylbl = r'Current density of electrolyte [A/m$^2$]'
×
377
                datax = facesvec
×
378
                datay = i_edges * (F*constants.c_ref*config["D_ref"]/config["L_ref"])
×
379
            elif plot_type in ["elytedivi", "elytedivif"]:
×
380
                ylbl = r'Divergence of electrolyte current density [A/m$^3$]'
×
381
                datax = cellsvec
×
382
                datay = np.diff(i_edges, axis=1) / disc["dxvec"]
×
383
                datay *= (F*constants.c_ref*config["D_ref"]/config["L_ref"]**2)
×
384
        if fplot:
×
385
            datay = datay[t0ind]
×
386
        if data_only:
×
387
            return datax, datay, L_a, L_s
×
388
        dataMin, dataMax = np.min(datay), np.max(datay)
×
389
        dataRange = dataMax - dataMin
×
390
        ymin = dataMin - 0.05*dataRange
×
391
        ymax = dataMax + 0.05*dataRange
×
392
        fig, ax = plt.subplots(figsize=figsize)
×
393
        ax.set_xlabel('Battery Position [{unit}]'.format(unit=Lunit))
×
394
        ax.set_ylabel(ylbl)
×
395
        ttl = ax.text(
×
396
            0.5, 1.05, ttl_fmt.format(perc=0),
397
            transform=ax.transAxes, verticalalignment="center",
398
            horizontalalignment="center")
399
        ax.set_ylim((ymin, ymax))
×
400
        ax.set_xlim((xmin, xmax))
×
401
        # returns tuble of line objects, thus comma
402
        if fplot:
×
403
            line1, = ax.plot(datax, datay, '-')
×
404
        else:
405
            line1, = ax.plot(datax, datay[t0ind,:], '-')
×
406
        ax.axvline(x=L_a, linestyle='--', color='g')
×
407
        ax.axvline(x=(L_a+L_s), linestyle='--', color='g')
×
408
        if fplot:
×
409
            print("time =", times[t0ind]*td, "s")
×
410
            if save_flag:
×
411
                fig.savefig("mpet_{pt}.png".format(pt=plot_type),
×
412
                            bbox_inches="tight")
413
            return fig, ax
×
414

415
        def init():
×
416
            line1.set_ydata(np.ma.array(datax, mask=True))
×
417
            ttl.set_text('')
×
418
            return line1, ttl
×
419

420
        def animate(tind):
×
421
            line1.set_ydata(datay[tind])
×
422
            t_current = times[tind]
×
423
            tfrac = (t_current - tmin)/(tmax - tmin) * 100
×
424
            ttl.set_text(ttl_fmt.format(perc=tfrac))
×
425
            return line1, ttl
×
426

427
    # Plot solid particle-average concentrations
428
    elif plot_type[:-2] in ["cbarLine", "dcbardtLine"]:
×
429
        trode = plot_type[-1]
×
430
        fig, ax = plt.subplots(Npart[trode], Nvol[trode], squeeze=False, sharey=True,
×
431
                               figsize=figsize)
432
        partStr = "partTrode{trode}vol{{vInd}}part{{pInd}}".format(trode=trode) + sStr
×
433
        type2c = False
×
434
        if config[trode, "type"] in constants.one_var_types:
×
435
            if plot_type[:-2] in ["cbarLine"]:
×
436
                str_base = pfx + partStr + "cbar"
×
437
            elif plot_type[:-2] in ["dcbardtLine"]:
×
438
                str_base = pfx + partStr + "dcbardt"
×
439
        elif config[trode, "type"] in constants.two_var_types:
×
440
            type2c = True
×
441
            if plot_type[:-2] in ["cbarLine"]:
×
442
                str1_base = pfx + partStr + "c1bar"
×
443
                str2_base = pfx + partStr + "c2bar"
×
444
            elif plot_type[:-2] in ["dcbardtLine"]:
×
445
                str1_base = pfx + partStr + "dc1bardt"
×
446
                str2_base = pfx + partStr + "dc2bardt"
×
447
        ylim = (0, 1.01)
×
448
        datax = times*td
×
449
        if data_only:
×
450
            plt.close(fig)
×
451
            if type2c:
×
452
                sol1_str = str1_base.format(pInd=pOut, vInd=vOut)
×
453
                sol2_str = str2_base.format(pInd=pOut, vInd=vOut)
×
454
                datay1 = utils.get_dict_key(data, sol1_str)
×
455
                datay2 = utils.get_dict_key(data, sol2_str)
×
456
                datay = (datay1, datay2)
×
457
            else:
458
                sol_str = str_base.format(pInd=pOut, vInd=vOut)
×
459
                datay = utils.get_dict_key(data, sol_str)
×
460
            return datax, datay
×
461
        xLblNCutoff = 4
×
462
        xLbl = "Time [s]"
×
463
        yLbl = "Particle Average Filling Fraction"
×
464
        for pInd in range(Npart[trode]):
×
465
            for vInd in range(Nvol[trode]):
×
466
                if type2c:
×
467
                    sol1_str = str1_base.format(pInd=pInd, vInd=vInd)
×
468
                    sol2_str = str2_base.format(pInd=pInd, vInd=vInd)
×
469
                    if Nvol[trode] > xLblNCutoff:
×
470
                        # Remove axis ticks
471
                        ax[pInd,vInd].xaxis.set_major_locator(plt.NullLocator())
×
472
                    else:
473
                        ax[pInd,vInd].set_xlabel(xLbl)
×
474
                        ax[pInd,vInd].set_ylabel(yLbl)
×
475
                    datay1 = utils.get_dict_key(data, sol1_str)
×
476
                    datay2 = utils.get_dict_key(data, sol2_str)
×
477
                    line1, = ax[pInd,vInd].plot(times, datay1)
×
478
                    line2, = ax[pInd,vInd].plot(times, datay2)
×
479
                else:
480
                    sol_str = str_base.format(pInd=pInd, vInd=vInd)
×
481
                    if Nvol[trode] > xLblNCutoff:
×
482
                        # Remove axis ticks
483
                        ax[pInd,vInd].xaxis.set_major_locator(plt.NullLocator())
×
484
                    else:
485
                        ax[pInd,vInd].set_xlabel(xLbl)
×
486
                        ax[pInd,vInd].set_ylabel(yLbl)
×
487
                    datay = utils.get_dict_key(data, sol_str)
×
488
                    line, = ax[pInd,vInd].plot(times, datay)
×
489
        return fig, ax
×
490

491
    # Plot all solid concentrations or potentials
492
    elif plot_type[:-2] in ["csld"]:
×
493
        if dataReporter == "hdf5Fast":
×
494
            # hdf5Fast does not print internal particle concentrations
495
            raise Exception("hdf5Fast dataReporter does not print internal particle "
×
496
                            "concentrations, rerun simulation with another data reporter")
497

498
        timettl = False  # Plot the current simulation time as title
×
499
        # Plot title in seconds
500
        ttlscl, ttlunit = 1, "s"
×
501
        t0ind = 0
×
502
        trode = plot_type[-1]
×
503
        if plot_type[0] == "c":
×
504
            plt_cavg = True
×
505
        else:
506
            plt_cavg = False
×
507
        plt_axlabels = True
×
508
        if config[trode, "type"] in constants.one_var_types:
×
509
            type2c = False
×
510
        elif config[trode, "type"] in constants.two_var_types:
×
511
            type2c = True
×
512
        Nv, Np = Nvol[trode], Npart[trode]
×
513
        partStr = "partTrode{trode}vol{vInd}part{pInd}" + sStr
×
514
        fig, ax = plt.subplots(Np, Nv, squeeze=False, sharey=True, figsize=figsize)
×
515
        if not type2c:
×
516
            cstr_base = pfx + partStr + "c"
×
517
            cbarstr_base = pfx + partStr + "cbar"
×
518
            cstr = np.empty((Np, Nv), dtype=object)
×
519
            cbarstr = np.empty((Np, Nv), dtype=object)
×
520
            lines = np.empty((Np, Nv), dtype=object)
×
521
        elif type2c:
×
522
            c1str_base = pfx + partStr + "c1"
×
523
            c2str_base = pfx + partStr + "c2"
×
524
            c1barstr_base = pfx + partStr + "c1bar"
×
525
            c2barstr_base = pfx + partStr + "c2bar"
×
526
            c1str = np.empty((Np, Nv), dtype=object)
×
527
            c2str = np.empty((Np, Nv), dtype=object)
×
528
            c1barstr = np.empty((Np, Nv), dtype=object)
×
529
            c2barstr = np.empty((Np, Nv), dtype=object)
×
530
            lines1 = np.empty((Np, Nv), dtype=object)
×
531
            lines2 = np.empty((Np, Nv), dtype=object)
×
532
            lines3 = np.empty((Np, Nv), dtype=object)
×
533
        lens = np.zeros((Np, Nv))
×
534
        if data_only:
×
535
            print("tInd_{}".format(tOut), "time =", times[tOut]*td, "s")
×
536
            lenval = psd_len[trode][vOut, pOut]
×
537
            if type2c:
×
538
                c1str = c1str_base.format(trode=trode, pInd=pOut, vInd=vOut)
×
539
                c2str = c2str_base.format(trode=trode, pInd=pOut, vInd=vOut)
×
540
                c1barstr = c1barstr_base.format(trode=trode, pInd=pOut, vInd=vOut)
×
541
                c2barstr = c2barstr_base.format(trode=trode, pInd=pOut, vInd=vOut)
×
542
                datay1 = utils.get_dict_key(data, c1str[pOut,vOut])
×
543
                datay2 = utils.get_dict_key(data, c2str[pOut,vOut])
×
544
                datay = (datay1, datay2)
×
545
                numy = len(datay1)
×
546
            else:
547
                cstr = cstr_base.format(trode=trode, pInd=pOut, vInd=vOut)
×
548
                cbarstr = cbarstr_base.format(trode=trode, pInd=pOut, vInd=vOut)
×
549
                datay = utils.get_dict_key(data, cstr)[tOut]
×
550
                numy = len(datay)
×
551
            datax = np.linspace(0, lenval * Lfac, numy)
×
552
            plt.close(fig)
×
553
            return datax, datay
×
554
        ylim = (0, 1.01)
×
555
        for pInd in range(Np):
×
556
            for vInd in range(Nv):
×
557
                lens[pInd,vInd] = psd_len[trode][vInd,pInd]
×
558
                if type2c:
×
559
                    c1str[pInd,vInd] = c1str_base.format(trode=trode, pInd=pInd, vInd=vInd)
×
560
                    c2str[pInd,vInd] = c2str_base.format(trode=trode, pInd=pInd, vInd=vInd)
×
561
                    c1barstr[pInd,vInd] = c1barstr_base.format(trode=trode, pInd=pInd, vInd=vInd)
×
562
                    c2barstr[pInd,vInd] = c2barstr_base.format(trode=trode, pInd=pInd, vInd=vInd)
×
563
                    datay1 = utils.get_dict_key(data, c1str[pInd,vInd])[t0ind]
×
564
                    datay2 = utils.get_dict_key(data, c2str[pInd,vInd])[t0ind]
×
565
                    datay3 = 0.5*(datay1 + datay2)
×
566
                    lbl1, lbl2 = r"$\widetilde{c}_1$", r"$\widetilde{c}_2$"
×
567
                    lbl3 = r"$\overline{c}$"
×
568
                    numy = len(datay1) if isinstance(datay1, np.ndarray) else 1
×
569
                    datax = np.linspace(0, lens[pInd,vInd] * Lfac, numy)
×
570
                    line1, = ax[pInd,vInd].plot(datax, datay1, label=lbl1)
×
571
                    line2, = ax[pInd,vInd].plot(datax, datay2, label=lbl2)
×
572
                    if plt_cavg:
×
573
                        line3, = ax[pInd,vInd].plot(datax, datay3, '--', label=lbl3)
×
574
                        lines3[pInd,vInd] = line3
×
575
                    lines1[pInd,vInd] = line1
×
576
                    lines2[pInd,vInd] = line2
×
577
                else:
578
                    cstr[pInd,vInd] = cstr_base.format(trode=trode, pInd=pInd, vInd=vInd)
×
579
                    cbarstr[pInd,vInd] = cbarstr_base.format(trode=trode, pInd=pInd, vInd=vInd)
×
580
                    datay = utils.get_dict_key(data, cstr[pInd,vInd])[t0ind]
×
581
                    numy = len(datay)
×
582
                    datax = np.linspace(0, lens[pInd,vInd] * Lfac, numy)
×
583
                    line, = ax[pInd,vInd].plot(datax, datay)
×
584
                    lines[pInd,vInd] = line
×
585
                ax[pInd,vInd].set_ylim(ylim)
×
586
                ax[pInd,vInd].set_xlim((0, lens[pInd,vInd] * Lfac))
×
587
                if plt_axlabels:
×
588
                    if config[trode, "type"] in ["ACR", "ACr_diff", "ACR2"]:
×
589
                        ax[pInd, vInd].set_xlabel(r"$x$ [{Lunit}]".format(Lunit=Lunit))
×
590
                    else:
591
                        ax[pInd, vInd].set_xlabel(r"$r$ [{Lunit}]".format(Lunit=Lunit))
×
592
                    if plot_type[0] == "c":
×
593
                        ax[pInd, vInd].set_ylabel(r"$\widetilde{{c}}$")
×
594
                    elif plot_type[:2] == "mu":
×
595
                        ax[pInd, vInd].set_ylabel(r"$\mu/k_\mathrm{B}T$")
×
596
                if timettl:
×
597
                    ttl = ax[pInd, vInd].text(
×
598
                        0.5, 1.04, "t = {tval:3.3f} {ttlu}".format(
599
                            tval=times[t0ind]*td*ttlscl, ttlu=ttlunit),
600
                        verticalalignment="center", horizontalalignment="center",
601
                        transform=ax[pInd, vInd].transAxes)
602

603
        def init():
×
604
            toblit = []
×
605
            for pInd in range(Npart[trode]):
×
606
                for vInd in range(Nvol[trode]):
×
607
                    if type2c:
×
608
                        data_c1str = utils.get_dict_key(data, c1str[pInd,vInd])[t0ind]
×
609
                        # check if it is array, then return length. otherwise return 1
610
                        numy = len(data_c1str) if isinstance(data_c1str, np.ndarray) else 1
×
611
                        maskTmp = np.zeros(numy)
×
612
                        lines1[pInd,vInd].set_ydata(np.ma.array(maskTmp, mask=True))
×
613
                        lines2[pInd,vInd].set_ydata(np.ma.array(maskTmp, mask=True))
×
614
                        lines_local = np.vstack((lines1, lines2))
×
615
                        if plt_cavg:
×
616
                            lines3[pInd,vInd].set_ydata(np.ma.array(maskTmp, mask=True))
×
617
                            lines_local = np.vstack((lines_local, lines3))
×
618
                    else:
619
                        data_cstr = utils.get_dict_key(data, cstr[pInd,vInd])[t0ind]
×
620
                        numy = len(data_cstr) if isinstance(data_cstr, np.ndarray) else 1
×
621
                        maskTmp = np.zeros(numy)
×
622
                        lines[pInd,vInd].set_ydata(np.ma.array(maskTmp, mask=True))
×
623
                        lines_local = lines.copy()
×
624
                    toblit.extend(lines_local.reshape(-1))
×
625
                    if timettl:
×
626
                        ttl.set_text("")
×
627
                        toblit.extend([ttl])
×
628
            return tuple(toblit)
×
629

630
        def animate(tind):
×
631
            toblit = []
×
632
            for pInd in range(Npart[trode]):
×
633
                for vInd in range(Nvol[trode]):
×
634
                    if type2c:
×
635
                        datay1 = utils.get_dict_key(data, c1str[pInd,vInd])[tind]
×
636
                        datay2 = utils.get_dict_key(data, c2str[pInd,vInd])[tind]
×
637
                        datay3 = 0.5*(datay1 + datay2)
×
638
                        lines1[pInd,vInd].set_ydata(datay1)
×
639
                        lines2[pInd,vInd].set_ydata(datay2)
×
640
                        lines_local = np.vstack((lines1, lines2))
×
641
                        if plt_cavg:
×
642
                            lines3[pInd,vInd].set_ydata(datay3)
×
643
                            lines_local = np.vstack((lines_local, lines3))
×
644
                    else:
645
                        datay = utils.get_dict_key(data, cstr[pInd,vInd])[tind]
×
646
                        lines[pInd,vInd].set_ydata(datay)
×
647
                        lines_local = lines.copy()
×
648
                    toblit.extend(lines_local.reshape(-1))
×
649
                    if timettl:
×
650
                        ttl.set_text("t = {tval:3.3f} {ttlu}".format(
×
651
                            tval=times[tind]*td*ttlscl, ttlu=ttlunit))
652
                        toblit.extend([ttl])
×
653
            return tuple(toblit)
×
654

655
    # Plot average solid concentrations
656
    elif plot_type in ["cbar_c", "cbar_a", "cbar_full"]:
×
657
        if plot_type[-4:] == "full":
×
658
            trvec = ["a", "c"]
×
659
        elif plot_type[-1] == "a":
×
660
            trvec = ["a"]
×
661
        else:
662
            trvec = ["c"]
×
663
        dataCbar = {}
×
664
        for trode in trodes:
×
665
            dataCbar[trode] = np.zeros((numtimes, Nvol[trode], Npart[trode]))
×
666
            for tInd in range(numtimes):
×
667
                for vInd in range(Nvol[trode]):
×
668
                    for pInd in range(Npart[trode]):
×
669
                        dataStr = (
×
670
                            pfx
671
                            + "partTrode{t}vol{vInd}part{pInd}".format(
672
                                t=trode, vInd=vInd, pInd=pInd)
673
                            + sStr + "cbar")
674
                        dataCbar[trode][tInd,vInd,pInd] = (
×
675
                            np.squeeze(utils.get_dict_key(data, dataStr))[tInd])
676
        if data_only:
×
677
            return dataCbar
×
678
        # Set up colors.
679
        # Uses either discrete or smooth colors
680
        # Define if you want smooth or discrete color changes in plot settings (-c)
681
        # Option: "discrete" or "smooth"
682
        # Discrete color changes:
683
        if color_changes == 'discrete':
×
684
            # Make a discrete colormap that goes from green to yellow
685
            # to red instantaneously
686
            cdict = {
×
687
                "red": [(0.0, 0.0, 0.0),
688
                        (to_yellow, 0.0, 1.0),
689
                        (1.0, 1.0, 1.0)],
690
                "green": [(0.0, 0.502, 0.502),
691
                          (to_yellow, 0.502, 1.0),
692
                          (to_red, 1.0, 0.0),
693
                          (1.0, 0.0, 0.0)],
694
                "blue": [(0.0, 0.0, 0.0),
695
                         (1.0, 0.0, 0.0)]
696
                }
697
            cmap = mpl.colors.LinearSegmentedColormap(
×
698
                "discrete", cdict)
699
        # Smooth colormap changes:
700
        if color_changes == 'smooth':
×
701
            # generated with colormap.org
702
            cmap_location = os.path.dirname(os.path.abspath(__file__)) + r'\colormaps_custom.npz'
×
703
            cmaps = np.load(cmap_location)
×
704
            cmap_data = cmaps[smooth_type]
×
705
            cmap = mpl.colors.ListedColormap(cmap_data/255.)
×
706

707
        size_frac_min = 0.10
×
708
        fig, axs = plt.subplots(1, len(trvec), squeeze=False, figsize=figsize)
×
709
        ttlx = 0.5 if len(trvec) < 2 else 1.1
×
710
        ttl = axs[0,0].text(
×
711
            ttlx, 1.05, ttl_fmt.format(perc=0),
712
            transform=axs[0,0].transAxes, verticalalignment="center",
713
            horizontalalignment="center")
714
        collection = np.empty(len(trvec), dtype=object)
×
715
        for indx, trode in enumerate(trvec):
×
716
            ax = axs[0,indx]
×
717
            # Get particle sizes (and max size) (length-based)
718
            lens = psd_len[trode]
×
719
            len_max = np.max(lens)
×
720
            len_min = np.min(lens)
×
721
            ax.patch.set_facecolor('white')
×
722
            # Don't stretch axes to fit figure -- keep 1:1 x:y ratio.
723
            ax.set_aspect('equal', 'box')
×
724
            # Don't show axis ticks
725
            ax.xaxis.set_major_locator(plt.NullLocator())
×
726
            ax.yaxis.set_major_locator(plt.NullLocator())
×
727
            ax.set_xlim(0, 1.)
×
728
            ax.set_ylim(0, float(Npart[trode])/Nvol[trode])
×
729
            # Label parts of the figure
730
#            ylft = ax.text(-0.07, 0.5, "Separator",
731
#                    transform=ax.transAxes, rotation=90,
732
#                    verticalalignment="center",
733
#                    horizontalalignment="center")
734
#            yrht = ax.text(1.09, 0.5, "Current Collector",
735
#                    transform=ax.transAxes, rotation=90,
736
#                    verticalalignment="center",
737
#                    horizontalalignment="center")
738
#            xbtm = ax.text(.50, -0.05, "Electrode Depth -->",
739
#                    transform=ax.transAxes, rotation=0,
740
#                    verticalalignment="center",
741
#                    horizontalalignment="center")
742
            # Geometric parameters for placing the rectangles on the axes
743
            spacing = 1.0 / Nvol[trode]
×
744
            size_fracs = 0.4*np.ones((Nvol[trode], Npart[trode]))
×
745
            if len_max != len_min:
×
746
                size_fracs = (lens - len_min)/(len_max - len_min)
×
747
            sizes = (size_fracs*(1-size_frac_min) + size_frac_min) / Nvol[trode]
×
748
            # Create rectangle "patches" to add to figure axes.
749
            rects = np.empty((Nvol[trode], Npart[trode]), dtype=object)
×
750
            color = 'green'  # value is irrelevant -- it will be animated
×
751
            for (vInd, pInd), c in np.ndenumerate(sizes):
×
752
                size = sizes[vInd,pInd]
×
753
                center = np.array([spacing*(vInd + 0.5), spacing*(pInd + 0.5)])
×
754
                bottom_left = center - size / 2
×
755
                rects[vInd,pInd] = plt.Rectangle(
×
756
                    bottom_left, size, size, color=color)
757
            # Create a group of rectange "patches" from the rects array
758
            collection[indx] = mcollect.PatchCollection(rects.reshape(-1))
×
759
            # Put them on the axes
760
            ax.add_collection(collection[indx])
×
761
        # Have a "background" image of rectanges representing the
762
        # initial state of the system.
763

764
        def init():
×
765
            for indx, trode in enumerate(trvec):
×
766
                cbar_mat = dataCbar[trode][0,:,:]
×
767
                colors = cmap(cbar_mat.reshape(-1))
×
768
                collection[indx].set_color(colors)
×
769
                ttl.set_text('')
×
770
            out = [collection[i] for i in range(len(collection))]
×
771
            out.append(ttl)
×
772
            out = tuple(out)
×
773
            return out
×
774

775
        def animate(tind):
×
776
            for indx, trode in enumerate(trvec):
×
777
                cbar_mat = dataCbar[trode][tind,:,:]
×
778
                colors = cmap(cbar_mat.reshape(-1))
×
779
                collection[indx].set_color(colors)
×
780
            t_current = times[tind]
×
781
            tfrac = (t_current - tmin)/(tmax - tmin) * 100
×
782
            ttl.set_text(ttl_fmt.format(perc=tfrac))
×
783
            out = [collection[i] for i in range(len(collection))]
×
784
            out.append(ttl)
×
785
            out = tuple(out)
×
786
            return out
×
787

788
    # Plot cathode potential
789
    elif plot_type[0:5] in ["bulkp"]:
×
790
        trode = plot_type[-1]
×
791
        fplot = (True if plot_type[-3] == "f" else False)
×
792
        t0ind = (0 if not fplot else -1)
×
793
        fig, ax = plt.subplots(figsize=figsize)
×
794
        ax.set_xlabel('Position in electrode [{unit}]'.format(unit=Lunit))
×
795
        ax.set_ylabel('Potential of cathode [nondim]')
×
796
        ttl = ax.text(0.5, 1.05, ttl_fmt.format(perc=0),
×
797
                      transform=ax.transAxes, verticalalignment="center",
798
                      horizontalalignment="center")
799
        bulkp = pfx + 'phi_bulk_{trode}'.format(trode=trode)
×
800
        datay = utils.get_dict_key(data, bulkp)
×
801
        ymin = np.min(datay) - 0.2
×
802
        ymax = np.max(datay) + 0.2
×
803
        if trode == "a":
×
804
            datax = cellsvec[:Nvol["a"]]
×
805
        elif trode == "c":
×
806
            datax = cellsvec[-Nvol["c"]:]
×
807
        if data_only:
×
808
            plt.close(fig)
×
809
            return datax, datay
×
810
        # returns tuble of line objects, thus comma
811
        line1, = ax.plot(datax, datay[t0ind])
×
812

813
        def init():
×
814
            line1.set_ydata(np.ma.array(datax, mask=True))
×
815
            ttl.set_text('')
×
816
            return line1, ttl
×
817

818
        def animate(tind):
×
819
            line1.set_ydata(datay[tind])
×
820
            t_current = times[tind]
×
821
            tfrac = (t_current - tmin)/(tmax - tmin) * 100
×
822
            ttl.set_text(ttl_fmt.format(perc=tfrac))
×
823
            return line1, ttl
×
824

825
    # plot cycling plots
826
    elif plot_type[0:5] == "cycle":
×
827
        current = utils.get_dict_key(data, pfx + 'current') / td  # gives us C-rates in /s
×
828
        # the capacity we calculate is the apparent capacity from experimental measurement,
829
        # not the real capacity of the electrode
830
        charge_discharge = utils.get_dict_key(data, pfx + "CCCVCPcycle_charge_discharge")
×
831
        ind_start_disch, ind_end_disch, ind_start_ch, ind_end_ch = \
×
832
            utils.get_negative_sign_change_arrays(charge_discharge)
833
        # get segments that indicate 1s for the charge/discharge segments, one for each
834
        # charge/discharge in the y axis
835
        cycle_numbers = np.arange(1, tot_cycle + 1)  # get cycle numbers on x axis
×
836
        # first figure out the number of cycles
837
        # find mass of limiting electrode
838
        # get the currents (are multiplied by 0 if it is not the segment we want)
839
        currents = cap * current  # A/m^2
×
840
        voltage = (Vstd - (k*Tref/e)*utils.get_dict_key(data, pfx + 'phi_applied'))  # in V
×
841
        # Q(t) array for the ith cycle for discharge_cap_func[i]
842
        discharge_cap_func = np.zeros((tot_cycle, 400))
×
843
        charge_cap_func = np.zeros((tot_cycle, 400))
×
844
        # V(t) array for the ith cycle for discharge_volt[i]
845
        discharge_volt = np.zeros((tot_cycle, 400))
×
846
        charge_volt = np.zeros((tot_cycle, 400))
×
847
        # total discharge capacity
848
        discharge_capacities = np.zeros(tot_cycle)
×
849
        charge_capacities = np.zeros(tot_cycle)
×
850
        discharge_total_cap = np.zeros((tot_cycle, len(times)))
×
851
        charge_total_cap = np.zeros((tot_cycle, len(times)))
×
852
        # only save discharge_cap_func and discharge_volt up to those values
853
        for j in range(tot_cycle):
×
854
            print("hi", ind_start_disch[j], ind_end_disch[j])
×
855
            discharge_cap_temp = integrate.cumtrapz(
×
856
                currents[ind_start_disch[j]:ind_end_disch[j]],
857
                times[ind_start_disch[j]:ind_end_disch[j]]*td, initial=0)/3600
858
            # get the total one padded with zeros so we can sum
859
            discharge_total_cap[j,:] = np.append(np.zeros(ind_start_disch[j]),
×
860
                                                 np.append(discharge_cap_temp, np.zeros(
861
                                                     len(currents)-ind_end_disch[j])))
862
            # integrate Q = int(I)dt, units in A hr/m^2
863
            # Ahr. pad w zero because the first number is always 0
864
            dis_volt_temp = voltage[ind_start_disch[j]:ind_end_disch[j]]
×
865
            # only fill the interpolated values
866
            discharge_volt[j,:] = np.linspace(dis_volt_temp[0], dis_volt_temp[-1], 400)
×
867
            f = interp1d(dis_volt_temp, discharge_cap_temp, fill_value='extrapolate')
×
868
            discharge_cap_func[j,:] = f(np.linspace(dis_volt_temp[0], dis_volt_temp[-1], 400))
×
869
            discharge_capacities[j] = discharge_cap_func[j,-1] * 1000  # mAh/m^2
×
870

871
        for j in range(tot_cycle):
×
872
            charge_cap_temp = integrate.cumtrapz(
×
873
                currents[ind_start_ch[j]:ind_end_ch[j]],
874
                times[ind_start_ch[j]:ind_end_ch[j]]*td, initial=0)/3600
875
            # get the total one padded with zeros so we can sum
876
            charge_total_cap[j,:] = np.append(np.zeros(ind_start_ch[j]),
×
877
                                              np.append(charge_cap_temp, np.zeros(
878
                                                  len(currents)-ind_end_ch[j])))
879
            # integrate Q = int(I)dt, units in A hr/m^2
880
            # Ahr. pad w zero because the first number is always 0
881
            ch_volt_temp = voltage[ind_start_ch[j]:ind_end_ch[j]]
×
882
            # only fill the interpolated values
883
            charge_volt[j,:] = np.linspace(ch_volt_temp[0], ch_volt_temp[-1], 400)
×
884
            f = interp1d(ch_volt_temp, charge_cap_temp, fill_value='extrapolate')
×
885
            charge_cap_func[j,:] = f(np.linspace(ch_volt_temp[0], ch_volt_temp[-1], 400))
×
886
            charge_capacities[j] = charge_cap_func[j,-1] * 1000  # mAh/m^2
×
887

888
        # units will be in Ahr/m^2*m^2 = Ah
889
        # discharge_voltages and Q store each of the V, Q data. cycle i is stored in row i for
890
        # both of these arrays
891
        gravimetric_caps_disch = -discharge_capacities/(config["P_L"][limtrode] * (
×
892
            1-config["poros"][limtrode]) * (config["L"][limtrode]*config['L_ref']))  # mAh/m^3
893
        gravimetric_caps_ch = charge_capacities/(config["P_L"][limtrode] * (
×
894
            1-config["poros"][limtrode]) * (config["L"][limtrode]*config['L_ref']))  # mAh/m^3
895
        # discharge_capacities = np.trapz(discharge_currents, times*td) *1000/3600
896
        # #mAh/m^2 since int over time
897
        # get the total capacites that we output in the data file with padded zeros
898
        discharge_total_capacities = np.sum(discharge_total_cap, axis=0)
×
899
        charge_total_capacities = np.sum(charge_total_cap, axis=0)
×
900

901
        # for QV or dQdV plots:
902
        # plot all cycles if less than six cycles, otherwise use equal spacing and plot six
903
        plot_indexes = 0
×
904
        if tot_cycle > 7:
×
905
            plot_indexes = (np.arange(0, 7)*(tot_cycle-1)/6).astype(int)
×
906
        else:
907
            plot_indexes = np.arange(0, tot_cycle)
×
908

909
        if plot_type == "cycle_capacity":  # plots discharge capacity
×
910
            if len(gravimetric_caps_disch) != len(cycle_numbers):
×
911
                # if we weren't able to complete the simulation, we only plot up to the
912
                # cycle we were able to calculate
913
                cycle_numbers = cycle_numbers[:len(gravimetric_caps_disch)]
×
914
            if data_only:
×
915
                return cycle_numbers, gravimetric_caps_ch, gravimetric_caps_disch
×
916
            fig, ax = plt.subplots(figsize=figsize)
×
917
            ax.plot(cycle_numbers, np.round(gravimetric_caps_ch, decimals=2), 'o', label='Charge')
×
918
            ax.plot(
×
919
                cycle_numbers,
920
                np.round(
921
                    gravimetric_caps_disch,
922
                    decimals=2),
923
                'o',
924
                label='Discharge')
925
            ax.legend()
×
926
            ax.set_xlabel("Cycle Number")
×
927
            ax.set_ylabel(r"Capacity [mAh/$m^3$]")
×
928
            ax.xaxis.set_major_locator(mpl.ticker.MaxNLocator(integer=True))
×
929
            if save_flag:
×
930
                fig.savefig("mpet_cycle_capacity.png", bbox_inches="tight")
×
931
            return fig, ax
×
932
        elif plot_type == "cycle_efficiency":
×
933
            # do we need to change this q because molweight changed? should be okay because Nm
934
            # still same efficiency = discharge_cap/charge_cap
935
            efficiencies = np.abs(np.divide(discharge_capacities, charge_capacities))
×
936
            if len(efficiencies) != len(cycle_numbers):
×
937
                # if we weren't able to complete the simulation, we only plot up to the
938
                # cycle we were able to calculate
939
                cycle_numbers = cycle_numbers[:len(efficiencies)]
×
940
            if data_only:
×
941
                return cycle_numbers, efficiencies
×
942
            fig, ax = plt.subplots(figsize=figsize)
×
943
            ax.plot(cycle_numbers, efficiencies, 'o')
×
944
            ax.set_xlabel("Cycle Number")
×
945
            ax.set_ylabel("Cycle Efficiency")
×
946
            ax.set_ylim(0, 1.1)
×
947
            ax.xaxis.set_major_locator(mpl.ticker.MaxNLocator(integer=True))
×
948
            if save_flag:
×
949
                fig.savefig("mpet_cycle_efficiency.png", bbox_inches="tight")
×
950
            return fig, ax
×
951
        elif plot_type == "cycle_cap_frac":
×
952
            discharge_cap_fracs = discharge_capacities/discharge_capacities[0]
×
953
            if len(discharge_cap_fracs) != len(cycle_numbers):
×
954
                # if we weren't able to complete the simulation, we only plot up to the
955
                # cycle we were able to calculate
956
                cycle_numbers = cycle_numbers[:len(discharge_cap_fracs)]
×
957
            if data_only:
×
958
                return cycle_numbers, discharge_cap_fracs
×
959
            # normalize by the first discharge capacity
960
            fig, ax = plt.subplots(figsize=figsize)
×
961
            ax.plot(cycle_numbers, np.round(discharge_cap_fracs, decimals=2), 'o')
×
962
            ax.set_xlabel("Cycle Number")
×
963
            ax.set_ylabel("State of Health")
×
964
            ax.set_ylim(0, 1.1)
×
965
            ax.xaxis.set_major_locator(mpl.ticker.MaxNLocator(integer=True))
×
966
            if save_flag:
×
967
                fig.savefig("mpet_cycle_cap_frac.png", bbox_inches="tight")
×
968
            return fig, ax
×
969
        elif plot_type == "cycle_Q_V":
×
970

971
            if data_only:
×
972
                return discharge_volt, discharge_cap_func
×
973

974
            fig, ax = plt.subplots(figsize=figsize)
×
975
            for i in plot_indexes:
×
976
                ax.plot(discharge_cap_func[i,:], discharge_volt[i,:])
×
977
            ax.legend(plot_indexes+1)
×
978
            ax.set_xlabel(r'Capacity (A hr/m$^2$)')
×
979
            ax.set_ylabel("Voltage (V)")
×
980
            ax.xaxis.set_major_locator(mpl.ticker.MaxNLocator(integer=True))
×
981
            if save_flag:
×
982
                fig.savefig("mpet_Q_V.png", bbox_inches="tight")
×
983
            return fig, ax
×
984

985
        elif plot_type == "cycle_dQ_dV":
×
986
            # nondimensionalize dQ and dV by initial discharge cap
987
            max_cap = discharge_capacities[0]/1000  # in Ahr/m^2
×
988
            # calculates dQdV along each curve
989
            dQ_dV = np.divide(np.diff(discharge_cap_func/max_cap, axis=1),
×
990
                              np.diff(discharge_volt, axis=1))
991
            volt = (discharge_volt[:,1:]+discharge_volt[:,:-1])/2
×
992

993
            if data_only:
×
994
                return volt, dQ_dV
×
995

996
            fig, ax = plt.subplots(figsize=figsize)
×
997
            for i in plot_indexes:
×
998
                ax.plot(discharge_volt[i,:], dQ_dV[i,:])
×
999
            ax.legend(plot_indexes+1)
×
1000
            ax.set_xlabel("Voltage (V)")
×
1001
            ax.set_ylabel('d%Q/dV (%/V))')
×
1002
            ax.xaxis.set_major_locator(mpl.ticker.MaxNLocator(integer=True))
×
1003
            if save_flag:
×
1004
                fig.savefig("mpet_dQ_dV.png", bbox_inches="tight")
×
1005
            return fig, ax
×
1006

1007
        elif plot_type == "cycle_data":
×
1008
            discharge_energies = discharge_total_capacities*voltage
×
1009
            charge_energies = charge_total_capacities*voltage
×
1010
            if data_only:
×
1011
                return discharge_total_capacities, charge_total_capacities, discharge_energies, \
×
1012
                    charge_energies
1013
            return
×
1014

1015
    else:
1016
        raise Exception("Unexpected plot type argument. See README.md.")
×
1017

1018
    ani = manim.FuncAnimation(
×
1019
        fig, animate, frames=numtimes, interval=50, blit=True, repeat=False, init_func=init)
1020
    if save_flag:
×
1021
        fig.tight_layout()
×
1022
        ani.save("mpet_{type}.mp4".format(type=plot_type), fps=25, bitrate=5500)
×
1023

1024
    return fig, ax, ani
×
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