Exemple #1
0
def bkg_only_fit(output, steppar=False, error=False):
    """
    Fit bkg region alone to XRB model
    """
    out = g309.load_data_and_models(["bkg"], snr_model=None)
    set_energy_range(out['bkg'])
    xs.AllData.ignore("bad")

    # Reset XRB to "typical" values
    # As usual, fit is pretty sensitive to initial values
    # (initial kT values that are too small drive fit to a bad local minimum)
    xrb = xs.AllModels(1, 'xrb')
    xrb.setPars({xrb.apec.kT.index : "0.2, , 0, 0, 0.5, 1"},  # Unabsorped apec (local bubble)
                {xrb.tbnew_gas.nH.index : "1.5, , 0.01, 0.1, 5, 10"},  # Galactic absorption
                {xrb.apec_6.kT.index : "0.7, , 0, 0, 2, 4"},  # Absorbed apec (galactic halo)
                {xrb.apec.norm.index : 1e-3},
                {xrb.apec_6.norm.index : 1e-3} )
    xrb.apec.kT.frozen = False
    xrb.tbnew_gas.nH.frozen = False
    xrb.apec_6.kT.frozen = False
    xrb.apec.norm.frozen = False
    xrb.apec_6.norm.frozen = False

    xs.Fit.perform()
    if xs.Plot.device == "/xs":
        xs.Plot("ldata delchi")

    if steppar:
        xs.Fit.steppar("xrb:{:d} 0.1 0.5 20".format(xs_utils.par_num(xrb, xrb.apec.kT)))
        xs.Fit.steppar("xrb:{:d} 0.4 0.8 20".format(xs_utils.par_num(xrb, xrb.apec_6.kT)))
        if xs.Plot.device == "/xs":
            xs.Plot("ldata delchi")

    if error:

        xs.Xset.openLog(output + "_error.log")

        print "Error run started:", datetime.now()
        xs.Fit.error("xrb:{:d}".format(xs_utils.par_num(xrb, xrb.apec.kT))
                  + " xrb:{:d}".format(xs_utils.par_num(xrb, xrb.apec.norm))
                  + " xrb:{:d}".format(xs_utils.par_num(xrb, xrb.tbnew_gas.nH))
                  + " xrb:{:d}".format(xs_utils.par_num(xrb, xrb.apec_6.kT))
                  + " xrb:{:d}".format(xs_utils.par_num(xrb, xrb.apec_6.norm)))

        print "Error run complete:", datetime.now()

        xs.Xset.closeLog()

    # Dump useful things here...
    products(output)
    print_model(xrb, output + "_xrb.txt")
Exemple #2
0
def bkg_only_fit(output, steppar=False, error=False):
    """
    Fit bkg region alone to XRB model
    """
    out = g309.load_data_and_models(["bkg"], snr_model=None)
    set_energy_range(out['bkg'])
    xs.AllData.ignore("bad")

    # Reset XRB to "typical" values
    # As usual, fit is pretty sensitive to initial values
    # (initial kT values that are too small drive fit to a bad local minimum)
    xrb = xs.AllModels(1, 'xrb')
    xrb.setPars({xrb.apec.kT.index : "0.2, , 0, 0, 0.5, 1"},  # Unabsorped apec (local bubble)
                {xrb.tbnew_gas.nH.index : "1.5, , 0.01, 0.1, 5, 10"},  # Galactic absorption
                {xrb.apec_6.kT.index : "0.7, , 0, 0, 2, 4"},  # Absorbed apec (galactic halo)
                {xrb.apec.norm.index : 1e-3},
                {xrb.apec_6.norm.index : 1e-3} )
    xrb.apec.kT.frozen = False
    xrb.tbnew_gas.nH.frozen = False
    xrb.apec_6.kT.frozen = False
    xrb.apec.norm.frozen = False
    xrb.apec_6.norm.frozen = False

    xs.Fit.perform()
    if xs.Plot.device == "/xs":
        xs.Plot("ldata delchi")

    if steppar:
        xs.Fit.steppar("xrb:{:d} 0.1 0.5 20".format(xs_utils.par_num(xrb, xrb.apec.kT)))
        xs.Fit.steppar("xrb:{:d} 0.4 0.8 20".format(xs_utils.par_num(xrb, xrb.apec_6.kT)))
        if xs.Plot.device == "/xs":
            xs.Plot("ldata delchi")

    if error:

        xs.Xset.openLog(output + "_error.log")

        print "Error run started:", datetime.now()
        xs.Fit.error("xrb:{:d}".format(xs_utils.par_num(xrb, xrb.apec.kT))
                  + " xrb:{:d}".format(xs_utils.par_num(xrb, xrb.apec.norm))
                  + " xrb:{:d}".format(xs_utils.par_num(xrb, xrb.tbnew_gas.nH))
                  + " xrb:{:d}".format(xs_utils.par_num(xrb, xrb.apec_6.kT))
                  + " xrb:{:d}".format(xs_utils.par_num(xrb, xrb.apec_6.norm)))

        print "Error run complete:", datetime.now()

        xs.Xset.closeLog()

    # Dump useful things here...
    products(output)
    print_model(xrb, output + "_xrb.txt")
Exemple #3
0
def annulus_fit(output, error=False, error_rerun=False,
                free_center_elements=None, free_all_elements=None,
                four_ann=False, **kwargs):
    """
    Fit radial annuli simultaneously

    Arguments:
        output = output products file stem
        error = run error commands?
        error_rerun = run error commands 2nd time?
        four_ann = fit only 4 instead of 5 annuli?
        free_all_elements = (case-sensitive) elements to free in all annuli.
            Element names much match XSPEC parameter names.
            Default: Si, S free
        free_center_elements = (case-sensitive) additional elements to free in
            central circle (0-100 arcsec)
            Element names much match XSPEC parameter names.
        kwargs - passed to g309_models.load_data_and_models
            (suffix, mosmerge, marfrmf)
    Output: n/a
        loads of stuff dumped to output*
        XSPEC session left in fitted state
    """

    if free_center_elements is None:
        free_center_elements = []
    if free_all_elements is None:
        free_all_elements = ['Si', 'S']

    regs = ["ann_000_100", "ann_100_200", "ann_200_300", "ann_300_400", "ann_400_500"]
    if four_ann:
        regs = ["ann_000_100", "ann_100_200", "ann_200_300", "ann_300_400"]

    out = g309.load_data_and_models(regs, snr_model='vnei', **kwargs)
    for reg in regs:
        set_energy_range(out[reg])
    xs.AllData.ignore("bad")

    # Link nH across annuli
    # Each region has n spectra (n exposures)
    # [0] gets 1st of n ExtractedSpectra objects
    # .models['...'] gets corresponding 1st of n XSPEC models
    rings = [out[reg][0].models['snr'] for reg in regs]
    for ring in rings[1:]:  # Exclude center
        ring.tbnew_gas.nH.link = xs_utils.link_name(rings[0], rings[0].tbnew_gas.nH)

    # Start fit process

    xs.Fit.renorm()
    xs.Fit.perform()

    for ring in rings:
        ring.tbnew_gas.nH.frozen = False
        ring.vnei.kT.frozen = False
        ring.vnei.Tau.frozen = False
    xs.Fit.perform()

    for ring in rings:
        for elem in free_all_elements:
            comp = ring.vnei.__getattribute__(elem)
            comp.frozen = False

    for elem in free_center_elements:
        comp = rings[0].vnei.__getattribute__(elem)
        comp.frozen = False

    xs.Fit.perform()

    if xs.Plot.device == "/xs":
        xs.Plot("ldata delchi")

    # Error runs

    if error:

        xs.Xset.openLog(output + "_error.log")

        print "First error run:", datetime.now()
        for reg, ring in zip(regs, rings):
            print "Running", reg, "errors:", datetime.now()
            xs.Fit.error(error_str_all_free(ring))
            print reg, "errors complete:", datetime.now()  # Will not appear in error log

        if error_rerun:
            print "Second error run:", datetime.now()
            for reg, ring in zip(regs, rings):
                print "Running", reg, "errors:", datetime.now()
                xs.Fit.error(error_str_all_free(ring))
                print reg, "errors complete:", datetime.now()  # Will not appear in error log

        xs.Xset.closeLog()
        print "Error runs complete:", datetime.now()

    # Output products
    products(output)

    for ring in rings:
        model_log = output + "_{}.txt".format(ring.name)
        print_model(ring, model_log)
Exemple #4
0
def single_fit(output, region='src', with_bkg=True, free_elements=None,
               error=False, error_rerun=False,
               tau_scan=False, tau_freeze=None, nH_freeze=None,
               snr_model='vnei', **kwargs):
    """Fit any region to an arbitrary remnant model, possibly fitting XRB with
    background region as well.

    Arguments
        output: file stem string
    Keyword arguments
        region: region spectrum to be fitted
        with_bkg: fit to X-ray background region (bkg) simultaneously
        snr_model: snr model expression (and parameter setup) to use
        free_elements: (default) is [Si,S]
            if [], use solar abundances
        error: perform single error run
        tau_scan: steppar over plausible Tau values to ensure convergence to "correct" best fit
        tau_freeze: freeze ionization timescale to provided value
        nH_freeze: freeze SNR absorption to provided value
        kwargs - passed to g309_models.load_data_and_models
            (suffix, mosmerge, marfrmf)
    """
    if free_elements is None:
        free_elements = ['Si', 'S']

    # Set up spectra and models in XSPEC
    if with_bkg:
        out = g309.load_data_and_models([region, 'bkg'], snr_model=snr_model, **kwargs)
        set_energy_range(out['bkg'])
    else:
        out = g309.load_data_and_models([region], **kwargs)

    set_energy_range(out[region])
    xs.AllData.ignore("bad")

    xrb = xs.AllModels(1, 'xrb')
    snr = xs.AllModels(1, 'snr_' + region)

    # Reset XRB to "typical" values, do NOT vary yet
    if with_bkg:
        xrb.setPars({xrb.apec.kT.index : "0.1, , 0, 0, 0.5, 1"},  # Unabsorped apec (local bubble)
                    {xrb.tbnew_gas.nH.index : "1, , 0.1, 0.5, 5, 10"},  # Extragalactic absorption
                    {xrb.tbnew_gas_5.nH.index : "1, , 0.1, 0.5, 5, 10"},  # Ridge absorption
                    {xrb.apec_6.kT.index : "0.5, , 0, 0, 2, 4"},  # Galactic ridge (+ minimal halo maybe)
                    {xrb.apec.norm.index : 1e-3},
                    {xrb.apec_6.norm.index : 1e-3} )
        xs_utils.freeze_model(xrb)
        # Try floating norms to help initial fit
        xrb.apec.norm.frozen = False
        xrb.apec_6.norm.frozen = False

        def thaw_bkg():
            for c in [xrb.apec.kT, xrb.tbnew_gas.nH, xrb.tbnew_gas_5.nH,
                      xrb.apec_6.kT]:
                c.frozen = False

    xs.Fit.renorm()

    # Let SNR model vary (NOTE: this assumes default to be vnei...)
    if snr_model.startswith('vnei'):

        xs_utils.freeze_model(snr)

        # Configure initial SNR parameters
        if nH_freeze:
            snr.tbnew_gas.nH = nH_freeze
        else:
            snr.tbnew_gas.nH.frozen=False

        snr.vnei.kT.frozen=False
        snr.vnei.norm.frozen=False

        if tau_freeze:
            snr.vnei.Tau = tau_freeze
        else:
            snr.vnei.Tau.frozen = False

        for elem in free_elements:
            comp = snr.vnei.__getattribute__(elem)
            comp.frozen = False

        if snr_model == 'vnei+nei':
            snr.nei.norm = 0
        elif snr_model == 'vnei+powerlaw':
            snr.powerlaw.PhoIndex = 2
            snr.powerlaw.norm = 0  # zero
        elif snr_model == 'vnei+srcutlog':
            # srcutlog, w/ one free parameter, behaves better than powerlaw
            snr.srcutlog.__getattribute__('break').frozen = False

        # Run initial fit
        if with_bkg:
            # Fit has enormous trouble converging
            if tau_freeze:
                thaw_bkg()
                xs.Fit.perform()
            else:
                snr.vnei.Tau = 2e10
                snr.vnei.Tau.frozen = True
                xs.Fit.perform()

                thaw_bkg()
                xs.Fit.perform()
                snr.vnei.Tau.frozen = False
                xs.Fit.perform()

        else:
            xs.Fit.perform()

        # Post-processing on initial fit

        if tau_scan:
            xs.Fit.steppar("log {:s}:{:d} 1e9 5e13 15".format(snr.name,
                                xs_utils.par_num(snr, snr.vnei.Tau)))

        if snr_model == 'vnei+nei':
            snr.nei.kT.frozen = False
            snr.nei.Tau.frozen = False
            snr.nei.norm.frozen = False
            xs.Fit.perform()
        elif snr_model == 'vnei+powerlaw':

            snr.powerlaw.PhoIndex = 2
            snr.powerlaw.norm = 0  # zero

            snr.powerlaw.norm.frozen = False
            xs.Fit.perform()

            snr.powerlaw.PhoIndex.frozen = False
            xs.Fit.perform()

            # Because powerlaw norm generally runs to zero, traverse moderately
            # strong power law cases
            xs.Fit.steppar("log {:s}:{:d} 1e-5 1e-2 30".format(snr.name,
                                xs_utils.par_num(snr, snr.powerlaw.norm)))

        elif snr_model == 'vnei+srcutlog':

            # Check reasonably high break values: 15 -- 17
            xs.Fit.steppar("{:s}:{:d} 15 17 20".format(snr.name,
                    xs_utils.par_num(snr, snr.srcutlog.__getattribute__('break'))
                    ))

    elif snr_model == 'vpshock':

        xs_utils.freeze_model(snr)

        snr.tbnew_gas.nH.frozen=False
        snr.vpshock.kT.frozen=False
        snr.vpshock.norm.frozen=False
        if tau_freeze:
            raise Exception("ERROR: vpshock not configured for fixed Tau")

        for elem in free_elements:
            comp = snr.vpshock.__getattribute__(elem)
            comp.frozen = False

        # vpshock fits are very ill behaved, must coerce into best fit
        snr.vpshock.Tau_l = 1e8
        snr.vpshock.Tau_u = 5e10
        xs.Fit.perform()

        if with_bkg:
            thaw_bkg()
            xs.Fit.perform()

        snr.vpshock.Tau_l.frozen = False
        snr.vpshock.Tau_u.frozen = False
        xs.Fit.perform()

        if tau_scan:
            # Since Tau_u is constrained to be greater than Tau_l (?) this
            # should ensure that both Tau_u and Tau_l traverse a range of
            # values.  But I haven't checked it yet...
            xs.Fit.steppar("log {:s}:{:d} 1e9 5e13 15".format(snr.name,
                                xs_utils.par_num(snr, snr.vpshock.Tau_u)))

    else:
        raise Exception("Invalid SNR model - please add branch")


    # Compute standard 90% errors
    if error:

        xs.Xset.openLog(output + "_error.log")

        if with_bkg:
            xs.Fit.error(error_str_all_free(xrb))
        xs.Fit.error(error_str_all_free(snr))

        if error_rerun:
            if with_bkg:
                xs.Fit.error(error_str_all_free(xrb))
            xs.Fit.error(error_str_all_free(snr))
        xs.Xset.closeLog()

    # Dump standard outputs
    products(output)
    print_model(snr, output + "_{:s}.txt".format(snr.name))
    if with_bkg:
        print_model(xrb, output + "_xrb.txt")
Exemple #5
0
def annulus_fit(output, error=False, error_rerun=False,
                free_center_elements=None, free_all_elements=None,
                four_ann=False, **kwargs):
    """
    Fit radial annuli simultaneously

    Arguments:
        output = output products file stem
        error = run error commands?
        error_rerun = run error commands 2nd time?
        four_ann = fit only 4 instead of 5 annuli?
        free_all_elements = (case-sensitive) elements to free in all annuli.
            Element names much match XSPEC parameter names.
            Default: Si, S free
        free_center_elements = (case-sensitive) additional elements to free in
            central circle (0-100 arcsec)
            Element names much match XSPEC parameter names.
        kwargs - passed to g309_models.load_data_and_models
            (suffix, mosmerge, marfrmf)
    Output: n/a
        loads of stuff dumped to output*
        XSPEC session left in fitted state
    """

    if free_center_elements is None:
        free_center_elements = []
    if free_all_elements is None:
        free_all_elements = ['Si', 'S']

    regs = ["ann_000_100", "ann_100_200", "ann_200_300", "ann_300_400", "ann_400_500"]
    if four_ann:
        regs = ["ann_000_100", "ann_100_200", "ann_200_300", "ann_300_400"]

    out = g309.load_data_and_models(regs, snr_model='vnei', **kwargs)
    for reg in regs:
        set_energy_range(out[reg])
    xs.AllData.ignore("bad")

    # Link nH across annuli
    # Each region has n spectra (n exposures)
    # [0] gets 1st of n ExtractedSpectra objects
    # .models['...'] gets corresponding 1st of n XSPEC models
    rings = [out[reg][0].models['snr'] for reg in regs]
    for ring in rings[1:]:  # Exclude center
        ring.tbnew_gas.nH.link = xs_utils.link_name(rings[0], rings[0].tbnew_gas.nH)

    # Start fit process

    xs.Fit.renorm()
    xs.Fit.perform()

    for ring in rings:
        ring.tbnew_gas.nH.frozen = False
        ring.vnei.kT.frozen = False
        ring.vnei.Tau.frozen = False
    xs.Fit.perform()

    for ring in rings:
        for elem in free_all_elements:
            comp = ring.vnei.__getattribute__(elem)
            comp.frozen = False

    for elem in free_center_elements:
        comp = rings[0].vnei.__getattribute__(elem)
        comp.frozen = False

    xs.Fit.perform()

    if xs.Plot.device == "/xs":
        xs.Plot("ldata delchi")

    # Error runs

    if error:

        xs.Xset.openLog(output + "_error.log")

        print "First error run:", datetime.now()
        for reg, ring in zip(regs, rings):
            print "Running", reg, "errors:", datetime.now()
            xs.Fit.error(error_str_all_free(ring))
            print reg, "errors complete:", datetime.now()  # Will not appear in error log

        if error_rerun:
            print "Second error run:", datetime.now()
            for reg, ring in zip(regs, rings):
                print "Running", reg, "errors:", datetime.now()
                xs.Fit.error(error_str_all_free(ring))
                print reg, "errors complete:", datetime.now()  # Will not appear in error log

        xs.Xset.closeLog()
        print "Error runs complete:", datetime.now()

    # Output products
    products(output)

    for ring in rings:
        model_log = output + "_{}.txt".format(ring.name)
        print_model(ring, model_log)
Exemple #6
0
def single_fit(output, region='src', with_bkg=True, free_elements=None,
               error=False, error_rerun=False,
               tau_scan=False, tau_freeze=None, nH_freeze=None,
               snr_model='vnei', **kwargs):
    """Fit any region to an arbitrary remnant model, possibly fitting XRB with
    background region as well.

    Arguments
        output: file stem string
    Keyword arguments
        region: region spectrum to be fitted
        with_bkg: fit to X-ray background region (bkg) simultaneously
        snr_model: snr model expression (and parameter setup) to use
        free_elements: (default) is [Si,S]
            if [], use solar abundances
        error: perform single error run
        tau_scan: steppar over plausible Tau values to ensure convergence to "correct" best fit
        tau_freeze: freeze ionization timescale to provided value
        nH_freeze: freeze SNR absorption to provided value
        kwargs - passed to g309_models.load_data_and_models
            (suffix, mosmerge, marfrmf)
    """
    if free_elements is None:
        free_elements = ['Si', 'S']

    # Set up spectra and models in XSPEC
    if with_bkg:
        out = g309.load_data_and_models([region, 'bkg'], snr_model=snr_model, **kwargs)
        set_energy_range(out['bkg'])
    else:
        out = g309.load_data_and_models([region], snr_model=snr_model, **kwargs)

    set_energy_range(out[region])
    xs.AllData.ignore("bad")

    if snr_model == 'gauss':
        for extr in out[region]:
            extr.spec.ignore("**-5.0, 8.0-**")

    xrb = xs.AllModels(1, 'xrb')
    snr = xs.AllModels(1, 'snr_' + region)

    # Reset XRB to "typical" values, do NOT vary yet
    if with_bkg:
        xrb.setPars({xrb.apec.kT.index : "0.1, , 0, 0, 0.5, 1"},  # Unabsorped apec (local bubble)
                    {xrb.tbnew_gas.nH.index : "1, , 0.1, 0.5, 5, 10"},  # Extragalactic absorption
                    {xrb.tbnew_gas_5.nH.index : "1, , 0.1, 0.5, 5, 10"},  # Ridge absorption
                    {xrb.apec_6.kT.index : "0.5, , 0, 0, 2, 4"},  # Galactic ridge (+ minimal halo maybe)
                    {xrb.apec.norm.index : 1e-3},
                    {xrb.apec_6.norm.index : 1e-3} )
        xs_utils.freeze_model(xrb)
        # Try floating norms to help initial fit
        xrb.apec.norm.frozen = False
        xrb.apec_6.norm.frozen = False

        def thaw_bkg():
            for c in [xrb.apec.kT, xrb.tbnew_gas.nH, xrb.tbnew_gas_5.nH,
                      xrb.apec_6.kT]:
                c.frozen = False

    xs.Fit.renorm()

    # Let SNR model vary (NOTE: this assumes default to be vnei...)
    if snr_model.startswith('vnei'):

        xs_utils.freeze_model(snr)

        # Configure initial SNR parameters
        if nH_freeze:
            snr.tbnew_gas.nH = nH_freeze
        else:
            snr.tbnew_gas.nH.frozen=False

        snr.vnei.kT.frozen=False
        snr.vnei.norm.frozen=False

        if tau_freeze:
            snr.vnei.Tau = tau_freeze
        else:
            snr.vnei.Tau.frozen = False

        for elem in free_elements:
            comp = snr.vnei.__getattribute__(elem)
            comp.frozen = False

        if snr_model == 'vnei+nei':
            snr.nei.norm = 0
        elif snr_model == 'vnei+powerlaw':
            snr.powerlaw.PhoIndex = 2
            snr.powerlaw.norm = 0  # zero
        elif snr_model == 'vnei+srcutlog':
            # srcutlog, w/ one free parameter, behaves better than powerlaw
            snr.srcutlog.__getattribute__('break').frozen = False

        # Run initial fit
        if with_bkg:
            # Fit has enormous trouble converging
            if tau_freeze:
                thaw_bkg()
                xs.Fit.perform()
            else:
                snr.vnei.Tau = 2e10
                snr.vnei.Tau.frozen = True
                xs.Fit.perform()

                thaw_bkg()
                xs.Fit.perform()
                snr.vnei.Tau.frozen = False
                xs.Fit.perform()

        else:
            xs.Fit.perform()

        # Post-processing on initial fit

        if tau_scan:
            xs.Fit.steppar("log {:s}:{:d} 1e9 5e13 15".format(snr.name,
                                xs_utils.par_num(snr, snr.vnei.Tau)))

        if snr_model == 'vnei+nei':
            snr.nei.kT.frozen = False
            snr.nei.Tau.frozen = False
            snr.nei.norm.frozen = False
            xs.Fit.perform()
        elif snr_model == 'vnei+powerlaw':

            snr.powerlaw.PhoIndex = 2
            snr.powerlaw.norm = 0  # zero

            snr.powerlaw.norm.frozen = False
            xs.Fit.perform()

            snr.powerlaw.PhoIndex.frozen = False
            xs.Fit.perform()

            # Because powerlaw norm generally runs to zero, traverse moderately
            # strong power law cases
            xs.Fit.steppar("log {:s}:{:d} 1e-5 1e-2 30".format(snr.name,
                                xs_utils.par_num(snr, snr.powerlaw.norm)))

        elif snr_model == 'vnei+srcutlog':

            # Check reasonably high break values: 15 -- 17
            xs.Fit.steppar("{:s}:{:d} 15 17 20".format(snr.name,
                    xs_utils.par_num(snr, snr.srcutlog.__getattribute__('break'))
                    ))

    elif snr_model == 'vpshock':

        xs_utils.freeze_model(snr)

        snr.tbnew_gas.nH.frozen=False
        snr.vpshock.kT.frozen=False
        snr.vpshock.norm.frozen=False
        if tau_freeze:
            raise Exception("ERROR: vpshock not configured for fixed Tau")

        for elem in free_elements:
            comp = snr.vpshock.__getattribute__(elem)
            comp.frozen = False

        # vpshock fits are very ill behaved, must coerce into best fit
        snr.vpshock.Tau_l = 1e8
        snr.vpshock.Tau_u = 5e10
        xs.Fit.perform()

        if with_bkg:
            thaw_bkg()
            xs.Fit.perform()

        snr.vpshock.Tau_l.frozen = False
        snr.vpshock.Tau_u.frozen = False
        xs.Fit.perform()

        if tau_scan:
            # Since Tau_u is constrained to be greater than Tau_l (?) this
            # should ensure that both Tau_u and Tau_l traverse a range of
            # values.  But I haven't checked it yet...
            xs.Fit.steppar("log {:s}:{:d} 1e9 5e13 15".format(snr.name,
                                xs_utils.par_num(snr, snr.vpshock.Tau_u)))

    elif snr_model == 'gauss':

        # Not supported!  Not at all constrained . . .
        assert not with_bkg

        xs_utils.freeze_model(snr)

        # Coupling between code is too tight, abstractions keep leaking.
        # Because models must be addressed by NUMBER in XSPEC
        # commands to tweak model parameters necessarily break
        # abstraction interface between "load models" and "fit models"
        # Possible solutions: (1) give up and let interfaces merge
        # (monolithic "g309_models_fits"), or . . . (2) stop whining
        if 'mosmerge' not in kwargs or kwargs['mosmerge']:
            instr_1 = xs.AllModels(1, 'instr_1')
            #instr_2 = xs.AllModels(2, 'instr_2')  # Let PN instr lines fit
            instr_3 = xs.AllModels(3, 'instr_3')
            for instr in [instr_1, instr_3]:
                # Must update parameter lower limits
                instr.constant.factor.values = "0, , 0, 0, , "
                instr.constant.factor.frozen = True
        else:  # No MOS merging
            instr_1 = xs.AllModels(1, 'instr_1')
            instr_2 = xs.AllModels(2, 'instr_2')
            #instr_3 = xs.AllModels(3, 'instr_3')  # Let PN instr lines fit
            instr_4 = xs.AllModels(4, 'instr_4')
            instr_5 = xs.AllModels(5, 'instr_5')
            for instr in [instr_1, instr_2, instr_4, instr_5]:
                # Must update parameter lower limits
                instr.constant.factor.values = "0, , 0, 0, , "
                instr.constant.factor.frozen = True

        snr.tbnew_gas.nH = 0
        snr.tbnew_gas.nH.frozen = True

        # LineE: lower/upper bounds set from Yamaguchi+ 2014
        # Sigma: prevent line from over-widening to fit as "constant" addition
        # norm: no bounds
        snr.setPars({snr.gaussian.LineE.index : "6.55, , 6.2, 6.3, 6.8, 6.9",
                     snr.gaussian.Sigma.index : "0.1, , 0, 0, 0.2, 0.5"} )
        snr.gaussian.LineE.frozen=False
        snr.gaussian.Sigma.frozen=False
        snr.gaussian.norm.frozen=False

        xs.Fit.perform()

    else:
        raise Exception("Invalid SNR model - please add branch")


    # Compute standard 90% errors
    if error:

        xs.Xset.openLog(output + "_error.log")

        if with_bkg:
            xs.Fit.error(error_str_all_free(xrb))
        xs.Fit.error(error_str_all_free(snr))

        if error_rerun:
            if with_bkg:
                xs.Fit.error(error_str_all_free(xrb))
            xs.Fit.error(error_str_all_free(snr))
        xs.Xset.closeLog()

    # Dump standard outputs
    products(output)
    print_model(snr, output + "_{:s}.txt".format(snr.name))
    if with_bkg:
        print_model(xrb, output + "_xrb.txt")