Пример #1
0
def load_calibration_results(f, verbose=True):
    '''
    Given the name of a pickle file saved by save_calibration_results(),
    opens the dictionary stored in the pickle file and does its best to
    convert the information stored in its values into molecule objects.
    '''
    if verbose:
        print('\n\nfunction \'load_calibration_results\' at your service!\n')
    if type(f) is str:
        with open(f, 'rb') as f:  # load the calibrations!
            calibration_results = pickle.load(f)
    else:
        calibration_results = pickle.load(f)

    mdict = {}  # turn the calibration results back into Molecule objects
    for mol, result in calibration_results.items():
        try:
            m = Molecule(mol, verbose=verbose)
        except FileNotFoundError:  # this happens if any molecule names were changed
            try:
                m = Molecule(result['formula'], verbose=verbose)
                m.name = mol
            except (KeyError, FileNotFoundError):
                m = result
        else:
            for attr, value in result.items():
                setattr(m, attr, value)
        mdict[mol] = m
    if verbose:
        print('\nfunction \'load_calibration_results\' finished!\n\n')
    return mdict
Пример #2
0
def getSymmetryElements(mol):
    from chem.molecules import Atom
    testmol = mol.copy()
    testmol.recenter()
    testmol.reorient()
    if isinstance(mol, list): 
        #oops, xyz, better make a molecule object
        from Molecules import Molecule, getAtomListFromXYZ
        atomList = getAtomListFromXYZ(mol)
        testmol = Molecule(atomList)
        
    def testTransformation(trans, atoms):
        for atom in atoms:
            newAtom = atom.copy()
            newAtom.transform(trans)
            test = testmol.testAtomEquivalence(newAtom)
            if not test:
                #this is not a symmetry element
                return False
        return True

    symmElements = []
    allAtoms = testmol.getAtoms()
    for trans in TRANSFORMATION_ORDER:
        matrix = transformations[trans]
        isSymmOp = testTransformation(matrix, allAtoms)
        if isSymmOp:
            symmElements.append(trans)

    return symmElements
Пример #3
0
def carrier_gas_cal(
    dataset=None,  #if signal not given, reads average from dataset
    signal=None,  #steady-state signal from in-flux of carrier gas
    mol='He',  #calibration Molecule object or name of calibration molecule
    carrier=None,
    viscosity=None,
    mass='primary',  #mass at which to calibrate
    composition=1,  #mol fraction calibration molecule in carrier gas
    chip='SI-3iv1-1-C5',  #chip object or name of chip
    tspan=None,
):
    '''
    returns a calibration factor based the carrier gas concentration
    '''

    calibration = {'type': 'carrier gas'}

    if type(chip) is str:
        chip = Chip(chip)

    if type(mol) is str:
        mol = Molecule(mol)

    if carrier is None:
        carrier = mol
    elif type(carrier) is str:
        carrier = Molecule(carrier)

    if mass == 'primary':
        mass = mol.primary

    if type(composition) in (float, int):
        fraction = composition
    elif type(composition) is dict:
        fraction = composition[mol.name]

    n_dot = chip.capillary_flow(gas=carrier)

    n_dot_i = fraction * n_dot

    F_cal = signal / n_dot_i

    if signal is None:
        if tspan is None:
            tspan = dataset['txpan']
        x, y = get_signal(dataset, mass=mass, tspan=tspan)
        calibration['Q_QMS'] = np.trapz(y, x)
        signal = calibration['Q_QMS'] / (tspan[-1] - tspan[0])

        calibration['n_mol'] = n_dot_i * (tspan[-1] - tspan[0])

    calibration['mass'] = mass
    calibration['n_dot_i'] = n_dot_i
    calibration['signal'] = signal
    calibration['F_cal'] = F_cal

    return calibration
Пример #4
0
def delta_response(L=100e-6, q0=1e15/Chem.NA, 
                   mol='H2', D=None, kH=None, n_el=None, 
                   A=0.196e-4, p_m=1e5, Temp=298.15,
                   verbose=True, tspan='auto', N_t=1000):
    '''
    Returns the output when a stagnant_operator operates on a delta function.
    There's probably a much smarter way to do it, but for now I'll just do
    triangle pulse of width tau/250
    '''
    if D is None or kH is None:
        if type(mol) is str:
            mol = Molecule(mol)
        if D is None:
            D = mol.D
        if kH is None:
            kH = mol.kH
    if verbose:
        print('calculating a delta function response.')
    h = kH*Chem.R*Temp*q0/(p_m*A)  #mass transfer coefficeint
    tau = L/h + L**2/(2*D)
    if tspan=='auto':
        tspan = [0, 4*tau]
    
    t = np.linspace(tspan[0], tspan[1], N_t)
    j = np.append(np.array([1]), np.zeros(N_t-1))
    tj = [t,j]
    print(type(tj))
    return stagnant_pulse(tj=tj, normalize=True, tspan=tspan,
                   L=L, A=A, q0=q0, p_m=p_m,
                   D=D, kH=kH, n_el=n_el, Temp=Temp, 
                   verbose=True, plot_type=None)
Пример #5
0
def chip_calibration(data,
                     mol='O2',
                     F_cal=None,
                     primary=None,
                     tspan=None,
                     tspan_bg=None,
                     gas='air',
                     composition=None,
                     chip='SI-3iv1'):
    '''
    Returns obect of class EC_MS.Chip, given data for a given gas (typically air) for which
    one component (typically O2 at M32) has a trusted calibration. The chip object
    has a capillary length (l_cap) that is set so that the capillary flux matches
    the measured signal for the calibrated gas.
    '''

    if type(mol) is str:
        m = Molecule(mol)
    else:
        m = mol
        mol = mol.name
    if F_cal is not None:
        m.F_cal = F_cal
    if primary is not None:
        m.primary = primary

    if gas == 'air' and composition is None:
        composition = air_composition[mol]

    x, y = m.get_flux(data, tspan=tspan, unit='mol/s')
    if tspan_bg is not None:
        x_bg, y_bg = m.get_flux(data, tspan=tspan_bg, unit='mol/s')
        y0 = np.mean(y_bg)
    else:
        y0 = 0
    n_dot = np.mean(y) - y0

    if type(chip) is str:
        chip = Chip(chip)
    n_dot_0 = chip.capillary_flow(gas=gas) / Chem.NA * composition

    l_eff = chip.l_cap * n_dot_0 / n_dot
    chip.l_cap = l_eff
    chip.parameters['l_cap'] = l_eff
    return chip
Пример #6
0
    def capillary_flow(self,
                       gas='He',
                       w_cap=None,
                       h_cap=None,
                       l_cap=None,
                       T=None,
                       p=None):
        '''
        adapted from Membrane_chip.py,
        equations from Daniel Trimarco's PhD Thesis

        Returns the flux in molecules/s of a carrier gas through the
        chip capillary.
        As the flow starts out viscous, at low analyte production rates,
        an analyte flux is simply the analyte's mol fraction in the chip times
        this flux.

        We assume that flow is governed by the bulk properties (viscosity)
        of the carrier gas and the molecular properties (diameter, mass)
        of the analyte.
        '''

        if type(gas) is str:
            gas = Molecule(gas)
        ''' #This does not work! I do not know why.
        print(self.w_cap)
        for var in 'w_cap', 'h_cap', 'l_cap', 'T', 'p':
            if locals()[var] is None:
                locals()[var] = getattr(self, var)
        '''

        #This is actually the most compact way I can figure out to do this.
        w = next(x for x in [w_cap, self.w_cap] if x is not None)
        h = next(x for x in [h_cap, self.h_cap] if x is not None)
        l = next(x for x in [l_cap, self.l_cap] if x is not None)
        T = next(x for x in [T, self.T] if x is not None)
        p = next(x for x in [p, self.p] if x is not None)

        s = gas.molecule_diameter  #molecular diameter in m
        m = gas.molecule_mass  #molecular mass in kg
        eta = gas.dynamic_viscosity  #viscosity in Pa*s

        d = ((w * h) / np.pi)**0.5 * 2  # hydraulic diameter
        #d=4.4e-6  #used in Henriksen2009
        a = d / 2  #hydraulic radius
        p_1 = p  #pressure at start of capillary (chip pressure)
        lam = d  #mean free path of transition from visc. to mol. flow

        p_t = Chem.kB * T / (2**0.5 * np.pi * s**2 * lam)  #transition pressure

        p_2 = 0  #pressure at end of capillary (vacuum)

        p_m = (p_1 +
               p_t) / 2  #average pressure in the visc. + trans. flow region

        v_m = (8 * Chem.kB * T / (np.pi * m))**0.5  #mean molecular velocity

        nu = (m /
              (Chem.kB * T))**0.5  #a resiprocal velocity used for short-hand
        #dumb, but inherited straight from Henrikson2009 through all of
        #Daniel's work up to his thesis, where it was finally dropped, which
        #unfortunatly makes that term of the equation a bit silly-looking.

        N_dot = (1 / (Chem.kB * T) * 1 / l *
                 ((a**4 * np.pi /
                   (8 * eta) * p_m + a**3 * 2 * np.pi / 3 * v_m *
                   (1 + 2 * a * nu * p_m / eta) /
                   (1 + 2.48 * a * nu * p_m / eta)) *
                  (p_1 - p_t) + a**3 * 2 * np.pi / 3 * v_m * (p_t - p_2)))
        return N_dot
Пример #7
0
def flow_operator(
        mode='steady',  #in steady mode it's not really an operator.
        system='chip',  #
        A_el=0.196e-4,
        A=0.196e-4,
        q0=1.5e15 / Chem.NA,
        Temp=None,  #universal pars
        L=100e-6,
        w=5e-3,
        w2=5e-3,
        F=1e-9,  #geometry pars   #w and w2 changed from 0.5e-3 to 5e-3 on 17K28.
        c0=None,
        j0=None,
        j_el=-1,  #inlet flow pars
        p_m=1e5,  #chip pars
        phi=0.5,
        dp=20e-9,
        Lp=100e-6,  #DEMS pars
        mol='H2',
        D=None,
        kH=None,
        n_el=None,
        M=None,  #mol pars
        p_gas=0,
        normalize=False,
        unit='pmol/s',
        flux_direction='out',
        N=100,  #solver pars
        verbose=True):
    '''
    Follows the recipe in Scott's MSc, page 61, for calculating collection
    efficiency in a flow system by solving a differential equation. This
    can be used to compare different types of EC-MS
    '''
    if verbose:
        print('\n\nfunction \'flow_operator\' at your service!\n')
    if type(mol) is str:
        mol = Molecule(mol)
    if Temp is not None:
        mol.set_temperature(Temp)
    else:
        Temp = 298.15  #standard temperature in K
    if D is None:
        D = mol.D  #diffusion constant in electrolyte / [m^2/s]
    if kH is None:
        kH = mol.kH  #dimensionless henry's law constant
    if M is None:
        M = Chem.Mass(mol.name) * 1e-3  # molar mass / [kg/mol]
    #print(c0)
    if n_el is None and c0 is None and not normalize:
        n_el = mol.n_el
    if c0 is None:
        if j0 is None:
            j0 = j_el * A_el / (n_el * Chem.Far)
        c0 = j0 / F
        #concentration is production rate over flow rate: [mol/s] / [m^3/s)] = [mol/m^3] )

    if system == 'chip':
        h = kH * Chem.R * Temp * q0 / (p_m * A)  #Scott's MSc, page 50
    elif system == 'DEMS':
        h = kH * phi * dp / (3 * Lp) * np.sqrt(
            8 / np.pi * Chem.R * Temp / M)  #Scott's MSc, page 49

    v0 = F / (L * w2)
    alpha = h * L / D
    beta = v0 * L**2 / (D * w)  #There is a mistake in Scott's MSc page60!
    # There I got the non-dimensionalization wrong, and wrote beta = v0*w**2/(D*L)
    # in fact, beta = v0*L**2/(D*w)

    Xspan = np.linspace(0, 1, 1000)
    X, CC = solve_flow(alpha=alpha,
                       beta=beta,
                       Xspan=Xspan,
                       N=N,
                       verbose=verbose)

    x = X * w
    cc = CC * c0
    j = cc[:, 0] * h  # mol/m^3 * m/s = mol/(m^2*s)
    Z = np.linspace(0, 1, N)
    z = Z * L

    eta_m = 1 - np.trapz(CC[-1, :], Z)  #Scott's MSc, page 61
    eta_m_check = w2 * np.trapz(j, x) / (
        c0 * F)  # m*mol/(m^2*s)*m / ((mol/m^3)*m^3/s) = 1

    qm = c0 * F * eta_m

    if verbose:
        print('portion not escaped = ' + str(eta_m))
        print('portion collected = ' + str(eta_m_check) + '\n\n')
    if system == 'chip':
        eta_v = 1
    elif system == 'DEMS':
        p_w = Chem.p_vap(mol='H2O', T=Temp, unit='Pa')
        M_w = Chem.Mass('H2O') * 1e-3
        j_w = A * p_w / (Chem.R * Temp) * phi * dp / (3 * Lp) * np.sqrt(
            8 / np.pi * Chem.R * Temp / M_w)
        eta_v = q0 / (qm + j_w)  #fixed 17H10

    eta = eta_m * eta_v

    if verbose:
        print('q0 = ' + str(q0) + ' mol/s, h = ' + str(h) + ' m/s, alpha = ' +
              str(alpha) + ', j0 = ' + str(j0) + ' mol/s, max(c)/c0 = ' +
              str(np.max(np.max(cc)) / c0) + ',  kH = ' + str(kH) +
              ', eta = ' + str(eta) + ', mol = ' + str(mol.name) +
              ', system = ' + str(system) + '' + ', beta = ' + str(beta) + '' +
              ', v0 = ' + str(v0))

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

    results = {
        'x': x,
        'z': z,
        'j': j,
        'cc': cc,
        'eta_m': eta_m,
        'eta_v': eta_v,
        'eta': eta,
        'dimensions': 'xz'
    }
    return results
Пример #8
0
def stagnant_operator(tj=None,
                      tpulse=10,
                      tspan=None,
                      j_el=-1,
                      L=100e-6,
                      A=0.196e-4,
                      q0=1.5e15 / Chem.NA,
                      p_m=1e5,
                      mol='H2',
                      p_gas=0,
                      normalize=False,
                      D=None,
                      kH=None,
                      n_el=None,
                      Temp=None,
                      unit='pmol/s',
                      flux_direction='out',
                      verbose=True,
                      ax=None,
                      plot_type=None,
                      startstate='zero',
                      N=30,
                      colormap='plasma',
                      aspect='auto'):
    '''
    Models a pulse of current towards a specified product in our EC-MS setup.
    Theory in chapter 2 of Scott's masters thesis.
    all arguments are in pure SI units. The electrode output can either be given
    as a steady-state square pulse of electrical current (tpulse, j_el, n_el),
    or as a measured current (tj[1]) as a function of time (tj[0])

    #tj[1] should have units A/m^2. 1 mA/cm^2 is 10 A/m^2

    #17B02: p_gas is the partial pressure of the analyte in the carrier gas.
    # this enables, e.g., CO depletion modelling.
    '''
    if verbose:
        print('\n\nfunction \'stagnant_operator\' at your service!\n')
    if type(mol) is str:
        mol = Molecule(mol)
    if Temp is not None:
        mol.set_temperature(Temp)
    else:
        Temp = 298.15  #standard temperature in K
    if D is None:
        D = mol.D
    if kH is None:
        kH = mol.kH
    if n_el is None and not normalize:
        n_el = mol.n_el
    if tspan is None:
        if tj is None:
            tspan = [-0.1 * tpulse, 1.2 * tpulse]
        else:
            tspan = [tj[0][0], tj[0][-1]]

    h = kH * Chem.R * Temp * q0 / (p_m * A)  #mass transfer coefficeint

    alpha = L * h / D  #system parameter

    #non-dimensional scales:
    t0 = L**2 / D
    if tj is None:
        if normalize:
            j0 = 1
        else:
            j0 = j_el / (n_el * Chem.Far)
    else:
        t = tj[0]
        if normalize:
            j0 = 1
            j = tj[1] / np.max(np.abs(tj[1]))
        else:
            j = tj[1] / (n_el * Chem.Far)  # A/m^2 --> mol/(m^2*s)
            j0 = max(np.abs(j))
    c0 = j0 * L / D
    tau = L**2 / (2 * D) + L / h
    #from the approximate analytical solution, Scott's thesis appendix D

    Tpulse = tpulse / t0
    Tspan = np.linspace(tspan[0], tspan[1],
                        1000) / t0  #why do I give so many time points?

    if tj is None:

        def J_fun(T):
            if T < 0:
                return 0
            if T < Tpulse:
                return 1
            return 0
    else:
        T_in = t / t0
        J_in = j / max(np.abs(j))

        #print('max(J_in) = ' + str(max(J_in)))
        def J_fun(T):
            if T < T_in[0]:  #assume no current outside of the input tj data
                return 0
            if T < T_in[-1]:
                return np.interp(T, T_in, J_in)
            return 0

    c_gas = p_gas / (Chem.R * Temp)
    cg = c_gas / kH  #concentration analyte in equilibrium with carrier gas, 17B02
    Cg = cg / c0  #non-dimensionalized concentration analyte at equilibrium with carrier gas, 17B02

    #pars = ([alpha, J_fun, Cg],) #odeint needs the tuple. 17A12: Why ?!
    [T, CC] = solve_stagnant(alpha=alpha,
                             J_fun=J_fun,
                             Cg=Cg,
                             Tspan=Tspan,
                             startstate=startstate,
                             flux=False,
                             N=N)
    cc = CC * c0
    t = T * t0
    j = h * (cc[:, 0] - cg)  #mass transport at the membrane
    #j1 = D * (cc[:,1] - cc[:,0])
    #fick's first law at the membrane gives the same j :)
    if verbose:
        print('q0 = ' + str(q0) + ' mol/s, h = ' + str(h) + ' m/s, alpha = ' +
              str(alpha) + ', j0 = ' + str(j0) + ' mol/(m^2*s), max(j)/j0 = ' +
              str(max(j) / j0) + ',  t0 = ' + str(t0) + ' s, c0 = ' + str(c0) +
              ' mol/m^3' + ', tau (analytical) = ' + str(tau) + ' s' +
              ', cg = ' + str(cg) + ' mM')

    # get ready to plot:
    N = np.shape(cc)[1]
    z = np.arange(N) / (N - 1) * L
    #this will only be used for heatmap, so it's okay if dx isn't quite right.
    if 'cm^2' not in unit:
        j = j * A
    if unit[0] == 'u':
        j = j * 1e6
    elif unit[0] == 'n':
        j = j * 1e9
    elif unit[0] == 'p':
        j = j * 1e12
    if flux_direction == 'in':
        j = -j

    if normalize:
        s_int = np.trapz(j, t)
        if verbose:
            print('normalizing from area = ' + str(s_int))
        j = j / s_int

        #plotting was moved on 17G30 some legacy code here:
    if plot_type is not None and ax is not None:
        print(
            'We recommend you plot seperately, using the function \'plot_operation\'.'
        )
        axes = plot_operation(cc=cc,
                              t=t,
                              z=z,
                              j=j,
                              ax=ax,
                              plot_type=plot_type,
                              colormap=colormap,
                              aspect=aspect,
                              verbose=verbose)
        if verbose:
            print('\nfunction \'stagnant_operator\' finished!\n\n')
        return t, j, axes

    results = {'t': t, 'z': z, 'j': j, 'cc': cc, 'dimensions': 'tz'}

    return results
Пример #9
0
        else: #if you insist
            return str(date)

    else:
        return str(date)
    date_string = '{0:2d}{1:1s}{2:2d}'.format(year%100, chr(ord('A') + month - 1), day)
    date_string = date_string.replace(' ', '0')

    return date_string



if __name__ == '__main__':
    from Molecules import Molecule
    #make an 'H2' molecule from the data file
    a = Molecule('H2')
    a.birthday = date_scott() #today is it's birthday!
    a.mood = 'Happy'    #make it happy
    #write everything about the happy H2 molecule to a file
    f = open('data/test.txt', 'w')
    attributes_to_file(f, a)
    f.close()
    #make a CO2 molecule from the data file...
    b = Molecule('CO2')
    print(b.name)
    #... and confuse the shit out it!
    f = open('data/test.txt', 'r')
    file_to_attributes(f, b) #its attributes are reset with the H2 data
    f.close()
    print(b.name) #now it thinks it's H2.
    print(b.__str__) #but deep down it's not
Пример #10
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
Пример #11
0
def point_calibration(
    data,
    mol,
    mass='primary',
    cal_type='internal',
    tspan=None,
    n_el=None,
    tail=0,
    tspan_bg=None,
    chip=None,
    composition=None,
    carrier=None,
):
    '''
    Returns a molecule object calibrated based in one of the following ways.
        internally: cal_type='internal', need n_el
        externally: cal_type='external', need chip, carrier, composition
    The signal is taken as an average over the tspan, or at a linear
    extrapolated point if len(tspan)==1. Same for current for internal cal.
    For external calibration,
    '''

    m = Molecule(mol)
    if mass == 'primary':
        mass = m.primary
    if composition is None:
        if carrier == mol or carrier is None:
            composition = 1
        elif carrier == 'air':
            composition = air_composition[mol]

    #get average signal
    if tspan is None:
        tspan = data['tspan']
    if tspan_bg is not None:
        x_bg, y_bg = get_signal(data, mass, tspan=tspan_bg)
        y0 = np.mean(y_bg)
    else:
        y0 = 0
    if type(tspan) in [int, float]:
        x, y = get_signal(data, mass, [tspan - 10, tspan + 10])
        S = np.interp(tspan, x, y - y0)
    else:
        x, y = get_signal(data, mass, tspan=[tspan[0], tspan[1] + tail])
        #S = np.trapz(y-y0, x) #/ (x[-1] - x[1])
        S = np.mean(y) - y0  #more accurate for few, evenly spaced datapoints

    if cal_type == 'internal':
        if type(tspan) in [int, float]:
            t, i = get_current(data, tspan=[tspan - 10, tspan + 10], unit='A')
            I = np.interp(tspan, t, i)
        else:
            t, i = get_current(data, tspan=tspan, unit='A')
            I = np.mean(i)  #np.trapz(i, t) #/ (t[-1] - t[1])
        n = I / (n_el * Chem.Far)

    elif cal_type == 'external':
        if chip is None:
            chip = 'SI-3iv1'
        if type(chip) is str:
            chip = Chip(chip)
        if carrier == None:
            carrier = mol
        n = chip.capillary_flow(gas=carrier) / Chem.NA * composition
        if type(tspan) not in [int, float]:
            pass
            #n = n * (tspan[-1]- tspan[0])

    else:
        print('not sure what you mean, dude, when you say cal_type = \'' +
              cal_type + '\'')

    F_cal = S / n
    m.F_cal = F_cal

    print('point_calibration() results: S = ' + str(S) + ' , n = ' + str(n) +
          ', F_cal = ' + str(F_cal))

    return m
Пример #12
0
def ML_strip_cal(CV_and_MS,
                 cycles=[1, 2],
                 t_int=200,
                 cycle_str='cycle number',
                 mol='CO2',
                 mass='primary',
                 n_el=None,
                 Vspan=[0.5, 1.0],
                 redox=1,
                 ax='two',
                 title='default',
                 verbose=True,
                 plot_instantaneous=False):
    '''
    Determines F_cal = Q_QMS / n_electrode by integrating a QMS signal over
    tspan, assuming the starting value is background; and
    integrating over vspan the difference between two CV cycles and converting
    that to a molar amount.
    Returns a partially populated calibration dictionary.
    The calibration factor is calibration['F_cal']
    '''
    if verbose:
        print('\n\ncalibration function \'ML_strip_cal\' at your service!\n')

    if ax == 'two':
        fig1 = plt.figure()
        ax1 = fig1.add_subplot(211)
        ax2 = fig1.add_subplot(212)
    elif ax is list:
        ax1 = ax[0]
        ax2 = ax[1]
    elif ax is None:
        ax1 = None
        ax2 = None
    else:
        ax1 = ax
        ax2 = None

    if type(mol) is str:
        mol = Molecule(mol, writenew=False)
    name = mol.name
    if n_el is None:
        n_el = mol.n_el
    if mass == 'primary':
        mass = mol.primary
    if np.size(t_int) == 1:
        #t_int = CV_and_MS['tspan_2'][0] + np.array([0, t_int]) #assumes I've cut stuff. Not necessarily true.
        #better solution below (17C22)
        pass
    if title == 'default':
        title = name + '_' + mass

#    print(type(CV_and_MS)) #was having a problem due to multiple outputs.
    cycles_data, ax1 = plot_CV_cycles(CV_and_MS,
                                      cycles,
                                      ax=ax1,
                                      title=title,
                                      cycle_str=cycle_str)
    ax1.xaxis.tick_top()
    ax1.xaxis.set_label_position('top')
    #    print(type(cycles_data))
    Q_diff, diff = CV_difference(cycles_data, Vspan=Vspan, redox=redox, ax=ax1)
    #if ax1 is not None:
    #    ax1.set_title(title)

    n_mol = Q_diff / (Chem.Far * n_el)
    t = diff[0]
    J_diff = diff[2]
    A_el = CV_and_MS['A_el']

    if np.size(t_int) == 1:  #17C22
        t_int = t[0] + np.array([0, t_int])
        #now it starts at the same time as EC data in the vrange starts

    #Q_diff seemed to be having a problem, but turned out to just be because
    #I forgot to remove_delay().
    # Now everything checks out, as can be seen here:
    '''
    Q_diff1 = A_el * np.trapz(J_diff, t) * 1e-3   #factor 1e-3 converts mA to A
    print('Q_diff = ' + str(Q_diff) +'\nQ_diff1 = ' + str(Q_diff1) +
          '\nratio = ' + str(Q_diff1/Q_diff))
    '''
    x = CV_and_MS[mass + '-x']
    y = CV_and_MS[mass + '-y']
    I_keep = [I for (I, x_I) in enumerate(x) if t_int[0] < x_I < t_int[1]]
    x = x[I_keep]
    y = y[I_keep]

    background = min(y)  #very simple background subtraction
    Q_QMS = np.trapz(y - background, x)
    F_cal = Q_QMS / n_mol

    y_el = J_diff * A_el / (Chem.Far * n_el) * F_cal * 1e-3
    #factor 1e-3 converts mA to A

    if ax2 is not None:
        ax2.plot(x, y * 1e9, 'k-')
        #ax2.plot(x, [background*1e9]*len(x), 'k-')
        ax2.fill_between(
            x,
            background * 1e9,
            y * 1e9,  #where=y>background,
            facecolor='g',
            interpolate=True)
        if plot_instantaneous:
            ax2.plot(t, y_el * 1e9, 'r--')  #Without mass transport
        ax2.set_xlabel('time / s')
        ax2.set_ylabel('signal / nA')


#        ax2.set_yscale('log')

    print(('QMS measured {0:5.2e} C of charge at M44 for {1:5.2e} mol ' +
           name + '.\n' +
           'Calibration factor for CO2 at M44 is {2:5.2e} C / mol.').format(
               Q_QMS, n_mol, F_cal))

    calibration = {'type': 'ML_strip'}
    calibration['raw_data'] = CV_and_MS['title']
    calibration['mass'] = mass
    calibration['n_mol'] = n_mol
    calibration['Q_el'] = Q_diff
    calibration['Q_QMS'] = Q_QMS
    calibration['F_cal'] = F_cal
    calibration['title'] = title

    if verbose:
        print('\ncalibration function \'ML_strip_cal\' finished!\n\n')

    if ax2 is None:
        ax = ax1
    else:
        ax = [ax1, ax2]
    return calibration, ax
Пример #13
0
def steady_state_cal(CA_and_MS,
                     t_int='half',
                     mol='CO2',
                     mass='primary',
                     n_el=None,
                     ax='new',
                     title='default',
                     verbose=True,
                     background='min'):
    if verbose:
        print(
            '\n\ncalibration function \'steady_state_cal\' at your service!\n')
    if type(mol) is str:
        mol = Molecule(mol, writenew=False)
    name = mol.name
    if n_el is None:
        n_el = mol.n_el
    if mass == 'primary':
        mass = mol.primary
    if t_int == 'half':
        t_int = (CA_and_MS['tspan_2'][1] + np.array(CA_and_MS['tspan_2'])) / 2
    elif t_int == 'all':
        t_int = np.array(CA_and_MS['tspan_2'])
    elif np.size(t_int) == 1:
        t_int = CA_and_MS['tspan_2'][1] + np.array([-t_int, 0])
        #by default integrate for time t_int up to end of interval
    if title == 'default':
        title = name + '_' + mass

    x = CA_and_MS[mass + '-x']
    y = CA_and_MS[mass + '-y']
    if background == 'min':
        background = min(y)
    elif background is None:
        background = 0
    I_keep = [I for (I, x_I) in enumerate(x) if t_int[0] < x_I < t_int[1]]
    x_r = x[I_keep]
    y_r = y[I_keep]
    Q_QMS = np.trapz(y_r - background, x_r)  #integrated signal in C

    V_str, J_str = sync_metadata(CA_and_MS)
    t = CA_and_MS['time/s']
    J = CA_and_MS[J_str]
    A_el = CA_and_MS['A_el']

    I_keep = [I for (I, t_I) in enumerate(t) if t_int[0] < t_I < t_int[1]]
    t_r = t[I_keep]
    J_r = J[I_keep]
    Q_el = A_el * np.trapz(J_r,
                           t_r) * 1e-3  # total electrode charge passed in C
    n_mol = Q_el / (Chem.Far * n_el)

    F_cal = Q_QMS / n_mol

    y_el = J * A_el / (Chem.Far * n_el) * F_cal * 1e-3
    # expected QMS signal without mass transport etc

    print(('QMS measured {0:5.2e} C of charge at M44 for {1:5.2e} mol ' +
           name + '.\n' +
           'Calibration factor for CO2 at M44 is {2:5.2e} C / mol.').format(
               Q_QMS, n_mol, F_cal))

    if ax == 'new':
        fig1 = plt.figure()
        ax = fig1.add_subplot(111)
    if ax is not None:
        ax.plot(x, y, 'k-')
        ax.plot(t, y_el + background, 'r--')
        ax.set_title(title)

    calibration = {'type': 'steady_state'}
    calibration['raw_data'] = CA_and_MS['title']
    calibration['mass'] = mass
    calibration['n_mol'] = n_mol
    calibration['Q_el'] = Q_el
    calibration['Q_QMS'] = Q_QMS
    calibration['F_cal'] = F_cal

    if verbose:
        print('\ncalibration function \'steady_state_cal\' finished!\n\n')

    return calibration