Ejemplo n.º 1
0
def activity_steps(
    data,
    mols,
    cycles,
    cycle_str='selector',
    mode='average',
    t_int=15,
    t_tail=30,
    t_pre=15,
    find_max=False,
    t_max_buffer=5,
    V_max_buffer=5,
    find_min=False,
    t_min_buffer=5,
    V_min_buffer=5,
    background=None,
    t_bg=None,
    unit='pmol/s',
    ax='new',
    tspan_plot=None,
    verbose=True,
):
    '''
    Powerful function for determining activity and faradaic efficiency for
    a set of potential steps.
    Requires calibrated molecule objects (mols) and cycle numbers, which by
    default refer to data['selector']

    if mode='average', it integrates over the last t_int of each cycle. If
    mode='integral', it integrates from t_pre before the start until t_tail
    after the end of each cycle.

    If find_max=True, rather than using the full timespan of the cycle, it
    finds the timespan at which the potential is within V_max_buffer mV of its
    maximum value, and cuts of t_max_buffer, and then uses this timespan as above.
    Correspondingly for find_min, V_min_buffer, and t_min_buffer.

    A timespan for which to get the background signals at each of the masses
    can be given as t_bg. Alternately, background can be set to 'linear' in
    which case it draws a line connecting the signals just past the endpoints
    of the timespan for each cycle.

    If ax is not None, it highlights the area under the signals and EC currents
    that are integrated/averaged.

    The function returns a dictionary including:
        'Qs': the integrated charges or averaged currents for each cycle
        'ns': dictionary containing, for each molecule, the integrated amounts
            or average flux for each cycle
        'Vs': the average potential for each cycle
        'ax': the axes on which the function plotted.

    '''
    if verbose:
        print('\n\nfunction \'evaluate_datapoints\' at your service!\n')
    # ----- parse inputs -------- #
    try:
        iter(mols)
    except TypeError:
        mols = [mols]
    mdict = dict([(m.name, m) for m in mols])

    if mode in ['average', 'averaging', 'mean']:
        mode = 'average'
    elif mode in ['integral', 'integrate', 'integrating']:
        mode = 'integral'

    if t_bg is not None:
        bgs = {}
        for mol, m in mdict.items():
            x_bg, y_bg = m.get_flux(data,
                                    tspan=t_bg,
                                    removebackground=False,
                                    unit=unit)
            bgs[mol] = np.mean(y_bg)
    # should perhaps give additional options for bg, but honostly t_bg is pretty good
    else:
        bgs = dict([(mol, 0) for mol in mdict.keys()])

    if ax == 'new':
        ax = plot_experiment(data,
                             mols,
                             removebackground=False,
                             tspan=tspan_plot,
                             emphasis=None,
                             unit=unit)
    else:
        try:
            iter(ax)
        except TypeError:
            ax = [ax]

    Qs, Vs = np.array([]), np.array([])
    ns = dict([(mol, np.array([])) for mol in mdict.keys()])
    for cycle in cycles:
        c = select_cycles(data, [cycle], cycle_str=cycle_str, verbose=verbose)

        if find_max:
            t_v, v = get_potential(c)
            v_max = max(v)
            mask = v_max - V_max_buffer * 1e-3 < v
            t_max = t_v[mask]
            t_start = t_max[0] + t_max_buffer
            t_end = t_max[-1] - t_max_buffer
        elif find_min:
            t_v, v = get_potential(c)
            v_min = min(v)
            mask = v < v_min + V_min_buffer * 1e-3
            t_min = t_v[mask]
            t_start = t_min[0] + t_min_buffer
            t_end = t_min[-1] - t_min_buffer
        else:
            t_start = c['time/s'][0]
            t_end = c['time/s'][-1]

        if mode == 'average':
            try:
                iter(t_int)
            except TypeError:
                tspan = [t_end - t_int, t_end]
            else:
                tspan = [t_start + t_int[0], t_start + t_int[-1]]
        elif mode == 'integral':
            c = select_cycles(data, [cycle - 1, cycle, cycle + 1],
                              cycle_str=cycle_str,
                              verbose=verbose)
            tspan = [t_start - t_pre, t_end + t_tail]

        t_v, v = get_potential(c, tspan=tspan, verbose=verbose)
        V = np.mean(v)
        Vs = np.append(Vs, V)

        t, I = get_current(c, tspan=tspan, verbose=verbose, unit='A')
        if mode == 'average':
            Q = np.mean(I)
        elif mode == 'integral':
            Q = np.trapz(I, t)
        Qs = np.append(Qs, Q)

        for mol, m in mdict.items():
            x, y0 = m.get_flux(c, tspan=tspan, unit=unit, verbose=verbose)
            bg = bgs[mol]
            y = y0 - bg
            if mode == 'average':
                yy = np.mean(y)
            elif mode == 'integral':
                yy = np.trapz(y, x)
            ns[mol] = np.append(ns[mol], yy)
            if ax is not None:
                try:
                    iter(bg)
                except TypeError:
                    bg = bg * np.ones(y0.shape)
                color = m.get_color()
                ax[0].fill_between(x,
                                   y0,
                                   bg,
                                   where=y0 > bg,
                                   color=color,
                                   alpha=0.5)
        if ax is not None:
            ax[1].plot(t_v, v, 'k-', linewidth=3)
            J = I * 1e3 / data['A_el']
            bg_J = np.zeros(J.shape)
            ax[2].fill_between(t, J, bg_J, color='0.5', alpha=0.5)

    if verbose:
        print('\nfunction \'evaluate_datapoints\' finished!\n\n')

    return {'Qs': Qs, 'ns': ns, 'Vs': Vs, 'ax': ax}
Ejemplo n.º 2
0
def get_datapoints(dataset,
                   cycles,
                   mols=['H2', 'C2H4', 'CH4'],
                   tspan=[0, 100],
                   t_steady=[50, 60],
                   Vcycle=0,
                   transient='CH4',
                   colors=None,
                   cycle_str=None,
                   plotcycles=False,
                   plottransient=False,
                   data_type='CA',
                   verbose=True):
    '''
    Ways to control this function:
    (1) put in a dictionary for mols with plotting colors and sub-dictionaries
    for products that should be split into transient ('dyn') and steady-state ('ss')
    for example, mols={'C2H4':'g', 'CH4':{'ss':'r','dyn':[0.8, 0, 0]}
    All transient integrations will use same t_steady
    (2)
    '''
    if verbose:
        print('\n\nfunction \'get_datapoints\' at your service!\n')
    #interpret inputs.
    if type(mols) is dict:
        colors = mols.copy()
    if colors is None:
        colors = mols
        if type(mols) is dict:
            mols = list(mols.keys())
        if type(transient) is str:
            transient = [transient]
    else:
        mols = list(colors.keys())
        transient = [
            mol for (mol, value) in colors.items() if type(value) is dict
        ]
    if type(t_steady) is dict:
        transient = list(t_steady.keys())
    else:
        ts = t_steady
        t_steady = {}
    if type(colors) is dict:
        for mol in transient:
            if type(colors[mol]) is dict:
                colors[mol] = colors[mol]['ss']
                #just for plotting cycles with appropriate colors
    if Vcycle in ['previous', 'last', 'rest']:
        Vcycle = -1
    elif Vcycle in ['present', 'current', 'same', 'work']:
        Vcycle = 0
    elif Vcycle in ['next']:
        Vcycle = 1
    V_str = dataset['V_str']

    #prepare space for results:
    V = []
    integrals = {}
    for mol in mols:
        if mol in transient:
            integrals[mol] = {'ss': [], 'dyn': []}
            if mol not in t_steady.keys():
                t_steady[mol] = ts
        else:
            integrals[mol] = []

    #get results:
    for cycle in cycles:
        off_data = select_cycles(dataset,
                                 cycles=cycle + Vcycle,
                                 t_zero='start',
                                 data_type=data_type,
                                 cycle_str=cycle_str,
                                 verbose=verbose)
        #off_data is data from the cycle that the independent variable is obtained form
        on_data = select_cycles(dataset,
                                cycles=[cycle, cycle + 1],
                                t_zero='start',
                                data_type=data_type,
                                cycle_str=cycle_str,
                                verbose=verbose)
        #on_data is data from the cycle, and following cycle for the tail, that the dependent variable is obtained from

        t_off = off_data['time/s']
        V_off = off_data[V_str]
        V += [np.trapz(V_off, t_off) / (t_off[-1] - t_off[0])]

        if plotcycles:
            title = str(cycle) + ', U = ' + str(V[-1])
            plot_experiment(on_data, mols=colors, title=title, verbose=verbose)

        for mol in integrals.keys():
            title = mol + ', cycle=' + str(cycle) + ', U=' + str(V[-1])
            if verbose:
                print('working on: ' + str(mol))
            x, y = get_flux(on_data,
                            tspan=tspan,
                            mol=mol,
                            removebackground=True,
                            unit='nmol/s',
                            verbose=verbose)

            if type(integrals[mol]) is dict:
                ts = t_steady[mol]
                if plottransient:
                    ax = 'new'
                else:
                    ax = None
                ss, dyn = integrate_transient(x,
                                              y,
                                              tspan=tspan,
                                              t_steady=ts,
                                              ax=ax,
                                              title=title,
                                              verbose=verbose)
                integrals[mol]['ss'] += [ss]
                integrals[mol]['dyn'] += [dyn]
            else:
                integrals[mol] += [np.trapz(y, x)]
    integrals['V'] = V
    if verbose:
        print('\nfunction \'get_datapoints\' finished!\n\n')
    return integrals
Ejemplo n.º 3
0
def calibration_curve(data,
                      mol,
                      mass='primary',
                      n_el=-2,
                      cycles=None,
                      cycle_str='selector',
                      mode='average',
                      t_int=15,
                      t_tail=30,
                      t_pre=15,
                      find_max=False,
                      t_max_buffer=5,
                      V_max_buffer=5,
                      find_min=False,
                      t_min_buffer=5,
                      V_min_buffer=5,
                      background=None,
                      t_bg=None,
                      tspan_plot=None,
                      remove_EC_bg=False,
                      color=None,
                      force_through_zero=False,
                      ax='new',
                      J_color='0.5',
                      unit=None,
                      out='Molecule',
                      verbose=True):
    '''
    Powerful function for integrating a molecule when the assumption of
    100% faradaic efficiency can be made.

    Requires a dataset, and cycle numbers, which by default refer to data['selector']

    if mode='average', it integrates over the last t_int of each cycle. If
    mode='integral', it integrates from t_pre before the start until t_tail
    after the end of each cycle.

    If find_max=True, rather than using the full timespan of the cycle, it
    finds the timespan at which the potential is within V_max_buffer mV of its
    maximum value, and cuts of t_max_buffer, and then uses this timespan as above.
    Correspondingly for find_min, V_min_buffer, and t_min_buffer.

    A timespan for which to get the background signals at each of the masses
    can be given as t_bg. Alternately, background can be set to 'linear' in
    which case it draws a line connecting the signals just past the endpoints
    of the timespan for each cycle.

    If ax is not None, it highlights the area under the signals and EC currents
    that are integrated/averaged, and also makes the calibration curve.

    The can return any or multiple of the following:
        'Qs': the integrated charges or averaged currents for each cycle
        'ns': the corresponding amount or flux for each cycle
        'Ys': the integrated or averaged signal for each cycle
        'Vs': the average potential for each cycle
        'F_cal': calibration factor in C/mol
        'Molecule': Molecule object with the calibration factor
        'ax': the axes on which the function plotted.
    out specifies what the function returns.
    By default, it returns the molecule

    '''

    # ----- parse inputs -------- #
    m = Molecule(mol)
    if mass == 'primary':
        mass = m.primary
    else:
        m.primary = mass

    if mode in ['average', 'averaging', 'mean']:
        mode = 'average'
    elif mode in ['integral', 'integrate', 'integrating']:
        mode = 'integral'

    use_bg_fun = False
    if t_bg is not None:
        x_bg, y_bg = get_signal(data, mass=mass, tspan=t_bg, unit='A')
        bg = np.mean(y_bg)
    elif callable(background):
        use_bg_fun = True
    elif background is not None and type(background) is not str:
        bg = background
    else:
        bg = 0

    if unit is None:
        if mode == 'average':
            unit = 'p'  # pmol/s and pA
        elif mode == 'integral':
            unit = 'n'  # nmol and nC
    elif unit[0] in ['p', 'n', 'u']:
        unit = unit[0]  # I'm only going to look at the prefix
    else:
        print('WARNING! unit=' + str(unit) +
              ' not recognized. calibration_curve() using raw SI.')
        unit = ''

    # ---------- shit, lots of plotting options... ---------#
    ax1, ax2a, ax2b, ax2c = None, None, None, None
    fig1, fig2 = None, None
    if ax == 'new':
        ax1 = 'new'
        ax2 = 'new'
    else:
        try:
            iter(ax)
        except TypeError:
            ax2c = ax
        else:
            try:
                ax1, ax2 = ax
            except (TypeError, IndexError):
                print('WARNING: calibration_curve couldn\'t use the give axes')
    if ax1 == 'new':
        ax1 = plot_experiment(data,
                              masses=[mass],
                              tspan=tspan_plot,
                              emphasis=None,
                              removebackground=False,
                              unit='A')
        fig1 = ax1[0].get_figure()
    else:
        try:
            ax1a = ax1[0]
        except TypeError:
            ax1a = ax1
        plot_signal(data,
                    masses=[mass],
                    tspan=tspan_plot,
                    removebackground=False,
                    unit='A',
                    ax=ax1a)
    if ax2 == 'new':
        fig2, [ax2a, ax2c] = plt.subplots(ncols=2)
        ax2b = ax2a.twinx()
        fig2.set_figwidth(fig1.get_figheight() * 3)
    else:
        try:
            iter(ax2)
        except TypeError:
            ax2c = ax2
        else:
            try:
                ax2a, ax2b, ax2c = ax2
            except (TypeError, IndexError):
                print('WARNING: calibration_curve couldn\'t use the give ax2')

    # ----- cycle through and calculate integrals/averages -------- #
    Ys, ns, Vs, Is, Qs = [], [], [], [], []

    for cycle in cycles:
        c = select_cycles(data, [cycle], cycle_str=cycle_str, verbose=verbose)

        if find_max:
            t_v, v = get_potential(c)
            v_max = max(v)
            mask = v_max - V_max_buffer * 1e-3 < v
            t_max = t_v[mask]
            t_start = t_max[0] + t_max_buffer
            t_end = t_max[-1] - t_max_buffer
            print('v_max = ' + str(v_max))  # debugging
        elif find_min:
            t_v, v = get_potential(c)
            v_min = min(v)
            mask = v < v_min + V_min_buffer * 1e-3
            t_min = t_v[mask]
            t_start = t_min[0] + t_min_buffer
            t_end = t_min[-1] - t_min_buffer
        else:
            t_start = c['time/s'][0]
            t_end = c['time/s'][-1]

        print('[t_start, t_end] = ' + str([t_start, t_end]) +
              '\n\n')  # debugging

        if mode == 'average':
            tspan = [t_end - t_int, t_end]
        elif mode == 'integral':
            c = select_cycles(data, [cycle - 1, cycle, cycle + 1],
                              cycle_str=cycle_str,
                              verbose=verbose)
            tspan = [t_start - t_pre, t_end + t_tail]

        t, I = get_current(c, tspan=tspan, verbose=verbose)
        t_v, v = get_potential(c, tspan=tspan, verbose=verbose)
        x, y = get_signal(c, mass=mass, tspan=tspan, verbose=verbose, unit='A')
        if use_bg_fun:  # has to work on x.
            bg = background(x)
        elif type(background) is str and background in ['linear', 'endpoints']:
            if t_bg is None:
                t_bg = 5
            tspan_before = [t_start - t_pre - t_bg, t_start - t_pre]
            tspan_after = [t_end + t_tail, t_end + t_tail + t_bg]
            x_before, y_before = get_signal(data,
                                            mass=mass,
                                            tspan=tspan_before)
            x_after, y_after = get_signal(data, mass=mass, tspan=tspan_after)
            x0, y0 = np.mean(x_before), np.mean(y_before)
            x1, y1 = np.mean(x_after), np.mean(y_after)
            bg = y0 + (x - x0) * (y1 - y0) / (x1 - x0)

        V = np.mean(v)

        if mode == 'average':
            I_av = np.mean(I)
            n = I_av / (n_el * Chem.Far)
            Y = np.mean(y - bg)
            Is += [I_av]

        elif mode == 'integral':
            Q = np.trapz(I, t)
            n = Q / (n_el * Chem.Far)
            Y = np.trapz(y - bg, x)
            Qs += [Q]

        if ax1 is not None:
            if color is None:
                color = m.get_color()
            try:
                iter(bg)
            except TypeError:
                y_bg = bg * np.ones(y.shape)
            else:
                y_bg = bg
            ax1[0].fill_between(
                x,
                y,
                y_bg,  #where=y>y_bg,
                color=color,
                alpha=0.5)
            J = I * 1e3 / data['A_el']
            J_bg = np.zeros(J.shape)
            ax1[2].fill_between(t, J, J_bg, color=J_color, alpha=0.5)

        ns += [n]
        Ys += [Y]
        Vs += [V]

    # ----- evaluate the calibration factor -------- #
    ns, Ys, Vs = np.array(ns), np.array(Ys), np.array(Vs)
    Is, Qs = np.array(Is), np.array(Qs)

    if remove_EC_bg:
        ns = ns - min(ns)

    if force_through_zero:
        F_cal = sum(Ys) / sum(
            ns)  # I'd actually be surprised if any fitting beat this
    else:
        pfit = np.polyfit(ns, Ys, deg=1)
        F_cal = pfit[0]

    m.F_cal = F_cal

    # ----- plot the results -------- #
    if color is None:
        color = m.get_color()
    ax2 = []
    if unit == 'p':
        ns_plot, Ys_plot = ns * 1e12, Ys * 1e12
    elif unit == 'n':
        ns_plot, Ys_plot = ns * 1e9, Ys * 1e9
    elif unit == 'u':
        ns_plot, Ys_plot = ns * 1e6, Ys * 1e6
    else:
        ns_plot, Ys_plot = ns, Ys

    if ax2a is not None:  # plot the internal H2 calibration
        V_str, J_str = sync_metadata(data, verbose=False)
        if n_el < 0:
            ax2a.invert_xaxis()
        ax2a.plot(Vs, ns_plot, '.-', color=J_color, markersize=10)
        ax2b.plot(Vs, Ys_plot, 's', color=color)
        ax2a.set_xlabel(V_str)
        if mode == 'average':
            ax2a.set_ylabel('<I>/(' + str(n_el) + '$\mathcal{F}$) / [' + unit +
                            'mol s$^{-1}$]')
            ax2b.set_ylabel('<' + mass + ' signal> / ' + unit + 'A')
        else:
            ax2a.set_ylabel('$\Delta$Q/(' + str(n_el) + '$\mathcal{F}$) / ' +
                            unit + 'mol')
            ax2b.set_ylabel(mass + 'signal / ' + unit + 'C')
        colorax(ax2b, color)
        colorax(ax2a, J_color)
        #align_zero(ax2a, ax2b)
        ax2 += [ax2a, ax2b]
    if ax2c is not None:
        ax2c.plot(ns_plot, Ys_plot, '.', color=color, markersize=10)
        # plot the best-fit line
        if force_through_zero:
            ns_pred_plot = np.sort(np.append(0, ns_plot))
            Y_pred_plot = F_cal * ns_pred_plot
        else:
            ns_pred_plot = np.sort(ns_plot)
            Y_pred_plot = F_cal * ns_pred_plot + pfit[1]
        #print('ns_pred_plot = ' + str(ns_pred_plot)) # debugging
        #print('Y_pred_plot = ' + str(Y_pred_plot)) # debugging
        ax2c.plot(ns_pred_plot, Y_pred_plot, '--', color=color)
        if mode == 'average':
            ax2c.set_xlabel('<I>/(' + str(n_el) + '$\mathcal{F}$) / [' + unit +
                            'mol s$^{-1}$]')
            ax2c.set_ylabel('<' + mass + ' signal> / ' + unit + 'A')
        else:
            ax2c.set_xlabel('$\Delta$Q/(' + str(n_el) + '$\mathcal{F}$) / ' +
                            unit + 'mol')
            ax2c.set_ylabel(mass + ' signal / ' + unit + 'C')
        ax2 += [ax2c]

    # ------- parse 'out' and return -------- #
    if fig1 is None and ax1 is not None:
        fig1 = ax1[0].get_figure()
    if fig2 is None and ax2 is not None:
        fig2 = ax2[0].get_figure()
    possible_outs = {
        'ax': [ax1, ax2],
        'fig': [fig1, fig2],
        'Molecule': m,
        'Is': Is,
        'Qs': Qs,
        'F_cal': F_cal,
        'Vs': Vs,
        'ns': ns,
        'Ys': Ys
    }
    if type(out) is str:
        outs = possible_outs[out]
    else:
        outs = [possible_outs[o] for o in out]
    if verbose:
        print('\nfunction \'calibration_curve\' finished!\n\n')
    return outs