def coreFunc(a):
        """runs the below for an angle, a
        Allows multiprocessing across a range of values for a
        """
        i, a = a
        model = Model(x0,
                      pot=pot,
                      spacing=spacing,
                      rng=rng,
                      step_size=step_size,
                      n_steps=n_steps,
                      rand_steps=rand_steps)

        c = acorr.Autocorrelations_1d(model)
        c.runModel(n_samples=n_samples,
                   n_burn_in=n_burn_in,
                   mixing_angle=a,
                   verbose=True,
                   verb_pos=i)
        store.store(c.model.samples, file_name, '_samples')
        store.store(c.model.traj, file_name, '_trajs')
        acs = c.getAcorr(separations,
                         opFn,
                         norm=False,
                         prll_map=prll_map,
                         max_sep=50)  # non norm for uWerr

        # get parameters generated
        traj = np.cumsum(c.trajs)
        p = c.model.p_acc
        xx = c.op_mean
        acorr_seps = c.acorr_ficticous_time
        acorr_counts = c.acorr_counts
        store.store(p, file_name, '_probs')
        store.store(acs, file_name, '_acs')

        ans = errors.uWerr(c.op_samples, acorr=acs)  # get errors
        _, _, _, itau, itau_diff, _, acns = ans  # extract data
        w = errors.getW(itau, itau_diff, n=n_samples)  # get window length
        acns_err = errors.acorrnErr(acns, w, n_samples)  # get error estimates

        if rand_steps:  # correct for N being different for each correlation
            acns_err *= np.sqrt(n_samples) / acorr_counts

        return xx, acns, acns_err, p, w, traj, acorr_seps
def main(x0, pot, file_name, n_samples, n_burn_in,
        mixing_angles, step_sizes, separations, opFn ,n_steps = 1,
        tintTh=None, paccTh = None, opTh = None, plot_res=1000,
        save = False):
    """A wrapper function
    
    Required Inputs
        x0          :: np.array :: initial position input to the HMC algorithm
        pot         :: potential class :: defined in hmc.potentials
        file_name   :: string :: the final plot will be saved with a similar name if save=True
        n_samples   :: int :: number of HMC samples
        n_burn_in   :: int :: number of burn in samples
        mixing_angles :: iterable :: mixing angles for the HMC algorithm
        angle_labels  :: list :: list of labels for the angles provided
        opFn        :: func :: function for autocorellations - takes one input: samples
        op_name     :: str :: name of opFn for plotting
        separations :: iterable :: lengths of autocorellations
        max_sep     :: float :: define the max separation
    
    Optional Inputs
        rand_steps :: bool :: probability of with prob
        step_size :: float :: MDMC step size
        n_steps :: int :: number of MDMC steps
        spacing :: float :: lattice spacing
        acFunc :: func :: function for evaluating autocorrelations
        save :: bool :: True saves the plot, False prints to the screen
    """
    
    # required declarations. If no lines are provided this still allows iteration for 0 times
    lines = {}  # contains the label as the key and a tuple as (x,y) data in the entry
    acs = {}
    
    rng = np.random.RandomState()
    
    print 'Running Model: {}'.format(file_name)
    
    def coreFunc(a):
        """runs the below for an angle, a
        Allows multiprocessing across a range of values for a
        """
        i,d,a = a
        model = Model(x0, pot=pot, rng=rng, step_size = d,
          n_steps = n_steps, rand_steps=False)
        
        c = acorr.Autocorrelations_1d(model)
        c.runModel(n_samples=n_samples, n_burn_in=n_burn_in, mixing_angle = a, verbose=False, verb_pos=i)
        acs = c.getAcorr(separations, opFn, norm = False)
        
        # get parameters generated
        pacc = c.model.p_acc
        pacc_err = np.std(c.model.sampler.accept.accept_rates)
        ans = errors.uWerr(c.op_samples, acorr=acs)         # get errors
        op_av, op_diff, _, itau, itau_diff, _, acns = ans             # extract data
        w = errors.getW(itau, itau_diff, n=n_samples)       # get window length
        if not np.isnan(w):
            acns_err = errors.acorrnErr(acns, w, n_samples)     # get error estimates
            acs = acns
        else:
            acns_err = np.full(acs.shape, np.nan)
            itau = itau_diff = np.nan
        
        return op_av, op_diff, acs, acns_err, itau, itau_diff, pacc, pacc_err, w
    
    # use multiprocessings
    elms = step_sizes.size*mixing_angles.size
    combinations = np.zeros((elms,3))
    combinations[:,1:] = np.array(np.meshgrid(step_sizes, mixing_angles)).T.reshape(-1,2)
    combinations[:,0] = np.arange(elms)
    
    ans = prll_map(coreFunc, combinations, verbose=True)
    
    print 'Finished Running Model: {}'.format(file_name)
    
    # results have now been obtained. This operation is a dimension shuffle
    # Currently the first iterable dimension contains one copy of each item
    # Want to split into separate arrays each of length n
    op_av, op_diff, acs, acns_err, itau, itau_diff, pacc, pacc_err, w = zip(*ans)
    
    op_av       = np.array(op_av)
    op_diff     = np.array(op_diff)
    acs         = np.array(acs)
    acns_err    = np.array(acns_err)
    itau        = np.array(itau)
    itau_diff   = np.array(itau_diff)
    pacc        = np.array(pacc)
    pacc_err    = np.array(pacc_err).ravel()
    w           = np.array(w)
    
    # Bundle all data ready for Plot() and store data as .pkl or .json for future use
    all_plot = {'itau':(step_sizes, itau, itau_diff), 
                'pacc':(step_sizes, pacc, pacc_err),
                'acorr':(step_sizes, acs, acns_err),
                'op':(step_sizes, op_av, op_diff)
                }
    
    max_step = step_sizes[-1]
    min_step = step_sizes[0]
    step_sizes_th = np.linspace(min_step, max_step, plot_res)
    if paccTh is not None:
        accs = np.asarray(map(paccTh,step_sizes_th))
        all_plot['pacc_th'] = (step_sizes_th, accs)
        
        if tintTh is not None:
            # itau = np.asarray([tintTh(dtau,acc) for dtau, acc in zip(step_sizes_th, pacc)])
            # itau_hi = np.asarray([tintTh(dtau,acc) for dtau, acc in zip(step_sizes_th, pacc+pacc_err)])
            # itau_lo = np.asarray([tintTh(dtau,acc) for dtau, acc in zip(step_sizes_th, pacc-pacc_err)])
            # all_plot['tint_th'] = (step_sizes_th, itau, itau_hi, itau_lo)
            itau = np.asarray([tintTh(dtau,acc) for dtau, acc in zip(step_sizes_th, accs)])
            all_plot['tint_th'] = (step_sizes_th, itau)
    # do the same for the X^2 operators
    if opTh is not None:
        all_plot['op_th'] = (step_sizes_th, np.asarray(map(opTh,step_sizes_th)))

    store.store(all_plot, file_name, '_allPlot')
    
    plot(save = saveOrDisplay(save, file_name), **all_plot)
def main(x0,
         pot,
         file_name,
         n_samples,
         n_burn_in,
         mixing_angles,
         angle_labels,
         opFn,
         op_name,
         separations,
         rand_steps=False,
         step_size=.5,
         n_steps=1,
         spacing=1.,
         acFunc=None,
         save=False):
    """A wrapper function
    
    Required Inputs
        x0          :: np.array :: initial position input to the HMC algorithm
        pot         :: potential class :: defined in hmc.potentials
        file_name   :: string :: the final plot will be saved with a similar name if save=True
        n_samples   :: int :: number of HMC samples
        n_burn_in   :: int :: number of burn in samples
        mixing_angles :: iterable :: mixing angles for the HMC algorithm
        angle_labels  :: list :: list of labels for the angles provided
        opFn        :: func :: function for autocorellations - takes one input: samples
        op_name     :: str :: name of opFn for plotting
        separations :: iterable :: lengths of autocorellations
        max_sep     :: float :: define the max separation
    
    Optional Inputs
        rand_steps :: bool :: True for exponential distribution of MDMC steps
        step_size :: float :: MDMC step size
        n_steps :: int :: number of MDMC steps
        spacing :: float :: lattice spacing
        acFunc :: func :: function for evaluating autocorrelations
        save :: bool :: True saves the plot, False prints to the screen
    """
    al = len(mixing_angles)  # the number of mixing angles
    send_prll = prll_map if al == 1 else None

    # required declarations. If no lines are provided this still allows iteration for 0 times
    lines = {
    }  # contains the label as the key and a tuple as (x,y) data in the entry
    acs = {}

    if not isinstance(separations, np.ndarray):
        separations = np.asarray(separations)
    rng = np.random.RandomState()

    subtitle = r"Potential: {}; Lattice: ${}$; $a={:.1f}; \delta\tau={:.2f}; n={}; m={:.1f}$".format(
        pot.name, x0.shape, spacing, step_size, n_steps, pot.m)

    print 'Running Model: {}'.format(file_name)

    def coreFunc(a):
        """runs the below for an angle, a
        Allows multiprocessing across a range of values for a
        """
        i, a = a
        model = Model(x0,
                      pot=pot,
                      spacing=spacing,
                      rng=rng,
                      step_size=step_size,
                      n_steps=n_steps,
                      rand_steps=rand_steps)

        c = acorr.Autocorrelations_1d(model)
        c.runModel(n_samples=n_samples,
                   n_burn_in=n_burn_in,
                   mixing_angle=a,
                   verbose=True,
                   verb_pos=i)
        store.store(c.model.samples, file_name, '_samples')
        store.store(c.model.traj, file_name, '_trajs')
        acs = c.getAcorr(separations,
                         opFn,
                         norm=False,
                         prll_map=prll_map,
                         max_sep=50)  # non norm for uWerr

        # get parameters generated
        traj = np.cumsum(c.trajs)
        p = c.model.p_acc
        xx = c.op_mean
        acorr_seps = c.acorr_ficticous_time
        acorr_counts = c.acorr_counts
        store.store(p, file_name, '_probs')
        store.store(acs, file_name, '_acs')

        ans = errors.uWerr(c.op_samples, acorr=acs)  # get errors
        _, _, _, itau, itau_diff, _, acns = ans  # extract data
        w = errors.getW(itau, itau_diff, n=n_samples)  # get window length
        acns_err = errors.acorrnErr(acns, w, n_samples)  # get error estimates

        if rand_steps:  # correct for N being different for each correlation
            acns_err *= np.sqrt(n_samples) / acorr_counts

        return xx, acns, acns_err, p, w, traj, acorr_seps

    # use multiprocessing

    if al == 1:  # don't use multiprocessing for just 1 mixing angle
        a = mixing_angles[0]
        ans = [coreFunc((i, a)) for i, a in enumerate(mixing_angles)]
    else:  # use multiprocessing for a number of mixing angles
        ans = prll_map(coreFunc, zip(range(al), mixing_angles), verbose=False)
    #
    print 'Finished Running Model: {}'.format(file_name)
    # results have now been obtained. This operation is a dimension shuffle
    # Currently the first iterable dimension contains one copy of each item
    # Want to split into separate arrays each of length n
    xx, acns, acns_err, ps, ws, ts, acxs = zip(*ans)
    print 'trajs:'
    print ts
    print '\n' * al  # hack to avoid overlapping with the progress bar from multiprocessing
    out = lambda p, x, a: '> measured at angle:{:3.1f}: <x(0)x(0)> = {}; <P_acc> = {:4.2f}'.format(
        a, x, p)
    for p, x, a in zip(ps, xx, mixing_angles):
        print out(p, x, a)  # print output as defined above

    # Decide a good total length for the plot
    w = np.max(ws)  # same length for all theory and measured data
    print 'Window is:{}'.format(w)
    if np.isnan(w):
        alen = int(len(separations) / 2)
    else:
        alen = 2 * w

    # Create Dictionary for Plotting Measured Data
    aclabel = r'Measured: $C_{\mathscr{X}^2}(s; '             \
        + r'\langle\rho_t\rangle_t'+r'={:4.2f}; '.format(p)
    yelpwx = zip(acns, acns_err, angle_labels, ps, ws,
                 acxs)  # this is an iterable of all a/c plot values

    # create the dictionary item to pass to plot()
    acns = {
        aclabel + r'\vartheta = {})$'.format(l): (x[:alen], y[:alen], e[:alen])
        for y, e, l, p, w_i, x in yelpwx
    }

    if acFunc is not None:  # Create Dictionary for Plotting Theory
        fx_f = np.max(np.asarray(
            [a[:alen]
             for a in acxs]))  # last trajectory separation length to plot
        fx_res = step_size * 0.1  # points per x-value
        fx_points = fx_f / fx_res + 1  # number of points to use
        fx = np.linspace(0, fx_f, fx_points,
                         True)  # create the x-axis for the theory
        windowed_ps = ps[:alen]  # windowed acceptance probabilities
        # calculcate theory across all tau, varying p_acc and normalise
        normFn = lambda pt: np.array(
            [acFunc(t=xi, pa=pt[0], theta=pt[1])
             for xi in fx]) / acFunc(t=0, pa=pt[0], theta=pt[1])
        fs = map(normFn, zip(
            ps, mixing_angles))  # map the a/c function to acceptance & angles
        th_label = r'Theory: $C_{\mathscr{X}^2}(s\sim \text{Exp}(\frac{1}{r}); \langle\rho_t\rangle_t = '
        pfl = zip(
            ps, fs,
            angle_labels)  # this is an iterable of all theory plot values
        pfl = pfl[:alen]  # cut to the same window length as x-axis

        # create the dictionary item to pass to plot()
        lines = {
            th_label + r'{:4.2f}; \theta = {})$'.format(p, l): (fx, f)
            for p, f, l in pfl if f is not None
        }
    else:
        pass  # lines = {} has been declared at the top so will allow the iteration in plot()

    # Bundle all data ready for Plot() and store data as .pkl or .json for future use
    all_plot = {
        'acns': acns,
        'lines': lines,
        'subtitle': subtitle,
        'op_name': op_name
    }
    store.store(all_plot, file_name, '_allPlot')

    plot(acns, lines, subtitle, op_name, save=saveOrDisplay(save, file_name))
def main(x0,
         pot,
         file_name,
         n_rng,
         n_samples=1000,
         n_burn_in=25,
         step_size=0.2,
         save=False):
    """A wrapper function
    
    Required Inputs
        x0              :: np.array :: initial position input to the HMC algorithm
        pot             :: pot. cls :: defined in hmc.potentials
        file_name       :: string   :: the final plot will be saved with a similar name if save=True
        n_rng           :: int arr  :: array of number of leapfrog step sizes
    
    Optional Inputs
        n_samples       :: int      :: number of HMC samples
        n_burn_in       :: int      :: number of burn in samples
        save :: bool    :: bool     :: True saves the plot, False prints to the screen
        step_size       :: int      :: Leap Frog step size
    """
    lines = {
    }  # contains the label as the key and a tuple as (x,y) data in the entry
    scats = {}

    print 'Running Model: {}'.format(file_name)
    # f = np.vectorize(lambda t: accHMC1dFree(t, step_size, 0, np.arange(1, x0.size+1)))
    f = np.vectorize(lambda t: HMC1dfVm0lf0(t, step_size, x0.size))
    x_fine = np.linspace(0, n_rng[-1] * step_size, 101, True)
    theory1 = f(x_fine)

    label = r'$\text{erfc}\left( \frac{\delta\tau^2}{2}' \
        + r' \sqrt{ 2 p^{2}_{0,1} N \sigma^{(2)}_{m^2} } \right)$'
    lines[label] = (x_fine, theory1)

    def coreFunc(n_steps):
        """function for multiprocessing support
        
        Required Inputs
            n_steps :: int :: the number of LF steps
        """

        model = Model(x0.copy(),
                      pot,
                      step_size=step_size,
                      n_steps=n_steps,
                      accept_kwargs={'get_delta_hs': True})
        model.sampler.accept.store_acceptance = True

        prob = 1.
        delta_hs = 1.
        av_dh = -1
        accept_rates = []
        delta_hs = []
        samples = []
        while av_dh < 0:
            model.run(n_samples=n_samples, n_burn_in=n_burn_in)
            accept_rates += model.sampler.accept.accept_rates[n_burn_in:]
            delta_hs += model.sampler.accept.delta_hs[n_burn_in:]
            samples.append(model.samples.copy())
            av_dh = np.mean(delta_hs)
            if av_dh < 0: tqdm.write('running again -ve av_dh')

        accept_rates = np.asarray(accept_rates)
        samples = np.concatenate(tuple(samples), axis=0)
        prob = accept_rates.mean()
        meas_av_exp_dh = np.asscalar((1. / np.exp(delta_hs)).mean())

        # ans = errors.uWerr(accept_rates)                    # get errors
        # f_aav, f_diff, _, itau, itau_diff, _, acns = ans    # extract data
        mean = accept_rates.mean()
        err = np.std(accept_rates) / np.sqrt(n_samples)
        th_err = np.std(accept_rates) / np.sqrt(n_samples)
        theory = acceptance(dtau=step_size, delta_h=av_dh)

        return mean, theory, err, th_err

    # use multi-core support to speed up
    ans = prll_map(coreFunc, n_rng, verbose=True)
    print 'Finished Running Model: {}'.format(file_name)

    prob, theory, errs, th_err = zip(*ans)

    x = np.asarray(n_rng) * step_size
    # theories[r'$p_{HMC}$'] = (x2, theory2)
    scats[r'$\text{erfc}(\sqrt{\langle \delta H_t \rangle}/2)$'] = (x, theory,
                                                                    th_err)
    scats[r'Measured'] = (x, prob, errs)

    # one long subtitle - long as can't mix LaTeX and .format()
    subtitle = '\centering Potential: {}, Lattice: {}'.format(pot.name, x0.shape) \
        + r', $\delta\tau = ' + '{:4.2f}$'.format(step_size)

    all_plot = {'lines': lines, 'scats': scats, 'subtitle': subtitle}
    store.store(all_plot, file_name, '_allPlot')
    plot(
        lines=lines,
        scats=scats,
        subtitle=subtitle,
        save=saveOrDisplay(save, file_name),
    )
    pass
def main(x0,
         pot,
         file_name,
         n_samples,
         n_burn_in,
         mixing_angle,
         angle_labels,
         opFn,
         op_name,
         rand_steps=False,
         step_size=.5,
         n_steps=1,
         spacing=1.,
         itauFunc=None,
         separations=range(5000),
         acTheory=None,
         max_sep=None,
         save=False):
    """Takes a function: opFn. Runs HMC-MCMC. Runs opFn on HMC samples.
        Calculates Autocorrelation + Errors on opFn.
    
    Required Inputs
        x0          :: np.array :: initial position input to the HMC algorithm
        pot         :: potential class :: defined in hmc.potentials
        file_name   :: string :: final plot will be saved with a similar name if save=True
        n_samples   :: int :: number of HMC samples
        n_burn_in   :: int :: number of burn in samples
        mixing_angle :: iterable :: mixing angles for the HMC algorithm
        angle_labels :: list :: list of angle label text for legend plotting
        opFn        :: func :: a function to run over samples
        op_name     :: str :: label for the operator for plotting
    
    Optional Inputs
        rand_steps :: bool :: probability of with prob
        step_size :: float :: MDMC step size
        n_steps :: int :: number of MDMC steps
        spacing ::float :: lattice spacing
        save :: bool :: True saves the plot, False prints to the screen
        acTheory :: func :: acTheory(t, pa, theta) takes in acceptance prob., time, angle 
        separations :: range / nparray :: the number of separations for A/C
        max_sep :: float :: the maximum separation tom consider for autocorrelations
    """
    rng = np.random.RandomState()
    multi_angle = len(mixing_angle) > 1  # see if multiprocessing is needed

    print 'Running Model: {}'.format(file_name)

    # output to print to screen
    out = lambda p,x,a:  '> measured at angle:{:3.1f}:'.format(a) \
        + ' <x^2>_L = {}; <P_acc>_HMC = {:4.2f}'.format(x, p)

    if not multi_angle:
        mixing_angle = mixing_angle[0]
        model = Model(
            x0,
            pot=pot,
            spacing=spacing,  # set up model
            rng=rng,
            step_size=step_size,
            n_steps=n_steps,
            rand_steps=rand_steps)
        c = acorr.Autocorrelations_1d(model)  # set up autocorrs
        c.runModel(
            n_samples=n_samples,
            n_burn_in=n_burn_in,  # run MCMC
            mixing_angle=mixing_angle,
            verbose=True)
        acs = c.getAcorr(separations, opFn, norm=False, prll_map=prll_map)
        cfn = c.op_samples

        # get parameters generated
        traj = c.model.traj  # get trajectory lengths for each LF step
        p = c.model.p_acc  # get acceptance rates at each M-H step
        xx = np.average(
            c.op_samples)  # get average of the function run over the samples

        if itauFunc:
            t = itauFunc(tau=(n_steps * step_size),
                         m=1,
                         pa=p,
                         theta=mixing_angle)
        else:
            t = None
        if acTheory is not None:
            ac_th = np.asarray(
                [acTheory(t, p, mixing_angle) for t in c.acorr_ficticous_time])
        else:
            ac_th = None

        ans = errors.uWerr(cfn, acorr=acs)
        x, gta, w = preparePlot(cfn,
                                ans,
                                n=n_samples,
                                itau_theory=t,
                                mcore=False,
                                acn_theory=ac_th)
        window_fns, int_ac_fns, acorr_fns = [[item] for item in gta]
        ws = [w]  # makes compatible with multiproc
        x_lst = [x]  # again same as last two lines
        print out(p, xx, mixing_angle)

    else:  # use multicore support

        def coreFunc(a):
            """runs the below for an angle, a"""
            i, a = a
            model = Model(
                x0,
                pot=pot,
                spacing=spacing,  # set up model
                rng=rng,
                step_size=step_size,
                n_steps=n_steps,
                rand_steps=rand_steps)
            c = acorr.Autocorrelations_1d(model)  # set up autocorrs
            c.runModel(
                n_samples=n_samples,
                n_burn_in=n_burn_in,  # run MCMC
                mixing_angle=a,
                verbose=True,
                verb_pos=i)
            acs = c.getAcorr(separations,
                             opFn,
                             norm=False,
                             prll_map=None,
                             ac=acs,
                             max_itau=max_itau)
            cfn = c.op_samples
            # get parameters generated
            p = c.model.p_acc  # get acceptance rates at each M-H step
            xx = np.average(
                cfn)  # get average of the function run over the samples

            ans = errors.uWerr(cfn, acorr=acs)
            if itauFunc:
                t = itauFunc(tau=n_steps * step_size, m=1, pa=p, theta=a)
            else:
                t = None
            if acTheory is not None:
                th_x = np.linspace(0, c.acorr_ficticous_time, 10000)
                ac_th = np.asarray([th_x, [acTheory(t, p, a) for t in th_x]])
            else:
                ac_th = None

            x, gta, w = preparePlot(cfn,
                                    n=n_samples,
                                    itau_theory=t,
                                    mcore=True,
                                    acn_theory=ac_th)
            return xx, traj, p, x, gta, w

        #
        # use multiprocessing
        l = len(mixing_angle)  # number of mixing angles
        ans = prll_map(coreFunc, zip(range(l), mixing_angle), verbose=False)

        # unpack from multiprocessing
        xx, traj, ps, x_lst, gtas, ws = zip(*ans)

        print '\n' * l  # hack to avoid text overlapping in terminal
        for p, x, a in zip(
                ps, xx, mixing_angle):  # print intermediate results to screen
            print out(p, x, a)
        window_fns, int_ac_fns, acorr_fns = zip(
            *gtas)  # separate out to respective lists

    lines = {
        0: window_fns,
        1: int_ac_fns,
        2: acorr_fns
    }  # create a dictionary for plotting
    #
    print 'Finished Running Model: {}'.format(file_name)

    subtitle = r"Potential: {}; Lattice Shape: ".format(pot.name) \
        + r"${}$; $a={:.1f}; \delta\tau={:.1f}; n={}$".format(
            x0.shape, spacing, step_size, n_steps)

    # all_plot contains all necessary keyword arguments
    all_plot = {
        'lines_d': lines,
        'x_lst': x_lst,
        'ws': ws,
        'subtitle': subtitle,
        'mcore': multi_angle,
        'angle_labels': angle_labels,
        'op_name': op_name
    }

    store.store(all_plot, file_name, '_allPlot')

    plot(lines_d=lines,
         x_lst=x_lst,
         ws=ws,
         subtitle=subtitle,
         mcore=multi_angle,
         angle_labels=angle_labels,
         op_name=op_name,
         save=saveOrDisplay(save, file_name))
    pass
Exemple #6
0
def main(x0,
         pot,
         file_name,
         n_samples,
         n_burn_in,
         angle_fracs,
         opFn,
         op_name,
         rand_steps=False,
         step_size=.1,
         n_steps=1,
         spacing=1.,
         iTauTheory=None,
         pacc_theory=None,
         op_theory=None,
         save=False):
    """Takes a function: opFn. Runs HMC-MCMC. Runs opFn on GHMC samples.
        Calculates Integrated Autocorrelation + Errors across a number of angles
    
    Required Inputs
        x0          :: np.array :: initial position input to the HMC algorithm
        pot         :: potential class :: defined in hmc.potentials
        file_name   :: string :: final plot will be saved with a similar name if save=True
        n_samples   :: int :: number of HMC samples
        n_burn_in   :: int :: number of burn in samples
        angle_fracs :: iterable :: list of values: n, for mixing angles of nπ
        opFn        :: func :: a function o run over samples
        op_name     :: str :: label for the operator for plotting
    
    Optional Inputs
        rand_steps  :: bool :: probability of with prob
        step_size   :: float :: MDMC step size
        n_steps     :: int :: number of MDMC steps
        spacing     :: float :: lattice spacing
        iTauTheory  :: func :: a function for theoretical integrated autocorrelation
        pacc_theory :: float :: a value for theoretical acceptance probability
        op_theory   :: float :: a value for the operator at 0 separation
        save :: bool :: True saves the plot, False prints to the screen
    
    """
    if not isinstance(angle_fracs, np.ndarray):
        angle_fracs = np.asarray(angle_fracs)
    if not hasattr(n_samples, '__iter__'):
        n_samples = [n_samples] * angle_fracs.size
    rng = np.random.RandomState()
    angls = np.pi * angle_fracs

    print 'Running Model: {}'.format(file_name)
    explicit_prog = (angls.size <= 16)

    def coreFunc(a):
        """runs the below for an angle, a"""
        i, a, n_samples = a
        model = Model(
            x0,
            pot=pot,
            spacing=spacing,  # set up model
            rng=rng,
            step_size=step_size,
            n_steps=n_steps,
            rand_steps=rand_steps)
        c = acorr.Autocorrelations_1d(model)  # set up autocorrs
        c.runModel(
            n_samples=n_samples,
            n_burn_in=n_burn_in,  # run MCMC
            mixing_angle=a,
            verbose=explicit_prog,
            verb_pos=i)
        cfn = opFn(c.model.samples)  # run func on HMC samples

        # get parameters generated
        p = c.model.p_acc  # get acceptance rates at each M-H step

        # Calculating integrated autocorrelations
        ans = errors.uWerr(cfn)
        xx, f_diff, _, itau, itau_diff, itaus, _ = ans
        w = errors.getW(itau, itau_diff, n=cfn.shape[0])
        out = itau, itau_diff, f_diff, w
        return xx, p, itau, itau_diff, f_diff, w

    #
    ans = prll_map(coreFunc,
                   zip(range(angle_fracs.size), angls, n_samples),
                   verbose=1 - explicit_prog)

    # unpack from multiprocessing
    xx_lst, p_lst, itau_lst, itau_diffs_lst, f_diff_lst, w_lst = zip(*ans)
    print '\n' * angle_fracs.size * explicit_prog  # hack to avoid overlapping!

    print 'Finished Running Model: {}'.format(file_name)

    subtitle = r"Potential: {}; Lattice: ".format(pot.name) \
        + r"${}$; $a={:.1f}; \delta\tau={:.1f}; n={}$".format(
            x0.shape, spacing, step_size, n_steps)

    # Create dictionary item for the measured data
    lines = {  # format is [(y, Errorbar, label)] if no errorbars then None
        0: [(itau_lst, itau_diffs_lst, r'Measured')],
        1: [(xx_lst, f_diff_lst, r'Measured')],
        2: [(w_lst, None, r'Measured')],
        3: [(p_lst, None, r'Measured')]
    }

    # append theory lines to each dictionary list item

    # add theory for integrated autocorrelations if provided
    if iTauTheory is not None:
        vFn = lambda pt: iTauTheory(
            tau=n_steps * step_size, m=pot.m, pa=pt[0], theta=pt[1])
        f = map(vFn, zip(p_lst,
                         angls))  # map the a/c function to acceptance & angles
        lines[0].append((f, None, r'Theory'))

    if op_theory is not None:
        f = np.full(angls.shape, op_theory)
        lines[1].append((f, None, r'Theory'))

    # add theory for acceptance probabilities
    if pacc_theory is not None:
        f = np.full(angls.shape, pacc_theory)
        lines[3].append((f, None, 'Theory'))

    all_plot = {
        'lines': lines,
        'x': angle_fracs,
        'subtitle': subtitle,
        'op_name': op_name
    }
    if save: store.store(all_plot, file_name, '_allPlot')

    # enter as keyword arguments
    plot(save=saveOrDisplay(save, file_name), **all_plot)
    pass