def fit_draws(draws, parname, nbins=50, params=None, plot=True, verbose=True): """Fit a gaussian to the histogram of the given parameter. Before using this routine you should use get_parameter_info() to extract the parameter info for use by get_draws(). This is because using this routine will invalidate the internal data structures that get_draws() uses when its params argument is None. If params is not None then it should be the return value of get_parameter_info(). If plot is True then a plot of the histogram and fit will be made. If verbose is True then a quick comparison of the fit results will be displayed. """ if parname not in draws["parnames"]: raise RuntimeError, "Unknown parameter '%s'" % parname # Exclude any point with an iteration number of 0 # idx = draws["iteration"] > 0 parvals = draws[parname][idx] (hy, hx) = np.histogram(parvals, bins=nbins, new=True) xlo = hx[:-1] xhi = hx[1:] id = parname ui.load_arrays(id, 0.5 * (xlo + xhi), hy) # We can guess the amplitude and position fairly reliably; # for the FWHM we just use the inter-quartile range of the # X axis. # ui.set_source(id, ui.gauss1d.gparam) gparam.pos = xlo[xlo.size // 2] gparam.ampl = hy[xlo.size // 2] gparam.fwhm = xlo[xlo.size * 3 // 4] - xlo[xlo.size // 4] # Get the best-fit value if available if params != None: p0 = dict(zip(params["parnames"], params["parvals"]))[parname] logger = logging.getLogger("sherpa") olvl = logger.level logger.setLevel(40) ostat = ui.get_stat_name() ui.set_stat("leastsq") ui.fit(id) ui.set_stat(ostat) logger.setLevel(olvl) if plot: # We manually create the plot since we want to use a histogram for the # data and the Sherpa plots use curves. # ##dplot = ui.get_data_plot(id) mplot = ui.get_model_plot(id) chips.lock() try: chips.open_undo_buffer() chips.erase() chips.add_histogram(xlo, xhi, hy) ##chips.add_histogram(xlo, xhi, mplot.y, ["line.color", "red", "line.style", "dot"]) chips.add_curve(mplot.x, mplot.y, ["line.color", "red", "symbol.style", "none"]) if params != None: chips.add_vline(p0, ["line.color", "green", "line.style", "longdash"]) chips.set_plot_xlabel(parname) except: chips.discard_undo_buffer() chips.unlock() raise chips.close_undo_buffer() chips.unlock() sigma = gparam.fwhm.val / (2.0 * np.sqrt(2 * np.log(2))) if verbose: print "" print "Fit to histogram of draws for parameter %s gives" % parname print " mean = %g" % gparam.pos.val print " sigma = %g" % sigma print "" if params != None: idx = params["parnames"] == parname print " best fit = %g" % p0 print " covar sigma = %g" % params["parmaxes"][idx][0] print "" return (gparam.pos.val, sigma, gparam.ampl.val)
def mht(parnames, mu, sigma, niter=1000, id=None, file=None, verbose=True, normalize=True, draw=draw_t, accept=accept_tcash, **kwargs): """Metropolis-Hastings. The default distribution is the t distribution, and the statistic is assumed to be the Cash statistic. The kwargs are passed through to the draw and accept routines. The draw routine is used to create a new proposal. The accept routine is used to determine whether to accept the proposal. If verbose is True then the iteration results are printed to STDOUT after each iteration. If file is not None then the iteration results are printed to the given file wach iteration. If normalize is True then the displayed results (whether to STDOUT or file) are relative to the best-fit values rather than absolute ones (so the values for the xpos parameter are written out as xpos-xpos_0 where xpos_0 is the value from the input mu argument). This also holds for the statistic value (so the results are statistic-statistic_0). The reason for normalize is to try and avoid lose of information without having to display numbers to 15 decimal places. """ # Should we just change to cash here instead of throwing an error? # if ui.get_stat_name() != "cash": raise RuntimeError, "Statistic must be cash, not %s" % ui.get_stat_name() if id == None: idval = ui.get_default_id() else: idval = id # Output storage # nelem = niter + 1 npars = mu.size if npars != len(parnames): raise RuntimeError, "mu.size = %d len(parnames) = %d!" % (npars, len(parnames)) params = np.zeros((nelem,npars)) stats = np.zeros(nelem) alphas = np.zeros(nelem) # Using a bool is technically nicer, but stick with an int8 here for easier processing # of the output. # ##acceptflag = np.zeros(nelem, dtype=np.bool) acceptflag = np.zeros(nelem, dtype=np.int8) params[0] = mu.copy() current = mu.copy() alphas[0] = 0 _set_par_vals(parnames, current) stats[0] = ui.calc_stat(id=idval) if normalize: outstr = "# iteration accept d_statistic %s" % " d_".join(parnames) else: outstr = "# iteration accept statistic %s" % " ".join(parnames) if verbose: print outstr if file != None: fout = open(file, "w") fout.write(outstr) fout.write("\n") def draw_to_string(idx): "Return the given draw as a string for display/output" if normalize: outstr = "%-6d %1d %g %s" % (idx, acceptflag[idx], stats[idx]-stats[0], " ".join(["%g" % (v-v0) for (v,v0) in zip(params[idx],params[0])])) else: outstr = "%-6d %1d %g %s" % (idx, acceptflag[idx], stats[idx], alphas[idx], " ".join(["%g" % v for v in params[idx]])) return outstr # Iterations # - no burn in at present # - the 0th element of the params array is the input value # - we loop until all parameters are within the allowable # range; should there be some check to ensure we are not # rejecting a huge number of proposals, which would indicate # that the limits need increasing or very low s/n data? # for i in range(1,nelem,1): current = params[i-1] # Create a proposal and set the parameter values. If any lie # outside the allowed range then catch this (ParameterError) # and create a new proposal. # while True: try: proposal = draw(mu, current, sigma, **kwargs) _set_par_vals(parnames, proposal) break except ParameterErr: pass # Do we accept this proposal? # stat_temp = ui.calc_stat(id=idval) alphas[i] = np.exp( -0.5*stat_temp + dmvt(current, mu, sigma, 4) + 0.5*stats[i-1] - dmvt(proposal, mu, sigma, 4) ) if accept(current, stats[i-1], proposal, stat_temp, mu, sigma, **kwargs): params[i] = proposal.copy() stats[i] = stat_temp acceptflag[i] = 1 else: params[i] = params[i-1] stats[i] = stats[i-1] acceptflag[i] = 0 outstr = draw_to_string(i) if verbose: print outstr if file != None: fout.write(outstr) fout.write("\n") if file != None: fout.close() print "Created: %s" % file # Return a dictionary containing the draws # out = { "parnames": parnames, "statistic": stats, "accept": acceptflag, "alphas": alphas, "iteration": np.arange(0,nelem,1) } for (idx,name) in zip(range(npars),parnames): if out.has_key(name): raise RuntimeError, "Unexpected name clash: parameter '%s'" % name out[name] = params[:,idx] return out
def fit_draws(draws, parname, nbins=50, params=None, plot=True, verbose=True): """Fit a gaussian to the histogram of the given parameter. Before using this routine you should use get_parameter_info() to extract the parameter info for use by get_draws(). This is because using this routine will invalidate the internal data structures that get_draws() uses when its params argument is None. If params is not None then it should be the return value of get_parameter_info(). If plot is True then a plot of the histogram and fit will be made. If verbose is True then a quick comparison of the fit results will be displayed. """ if parname not in draws["parnames"]: raise RuntimeError, "Unknown parameter '%s'" % parname # Exclude any point with an iteration number of 0 # idx = draws["iteration"] > 0 parvals = draws[parname][idx] (hy, hx) = np.histogram(parvals, bins=nbins, new=True) xlo = hx[:-1] xhi = hx[1:] id = parname ui.load_arrays(id, 0.5 * (xlo + xhi), hy) # We can guess the amplitude and position fairly reliably; # for the FWHM we just use the inter-quartile range of the # X axis. # ui.set_source(id, ui.gauss1d.gparam) gparam.pos = xlo[xlo.size // 2] gparam.ampl = hy[xlo.size // 2] gparam.fwhm = xlo[xlo.size * 3 // 4] - xlo[xlo.size // 4] # Get the best-fit value if available if params != None: p0 = dict(zip(params["parnames"], params["parvals"]))[parname] logger = logging.getLogger("sherpa") olvl = logger.level logger.setLevel(40) ostat = ui.get_stat_name() ui.set_stat("leastsq") ui.fit(id) ui.set_stat(ostat) logger.setLevel(olvl) if plot: # We manually create the plot since we want to use a histogram for the # data and the Sherpa plots use curves. # ##dplot = ui.get_data_plot(id) mplot = ui.get_model_plot(id) chips.lock() try: chips.open_undo_buffer() chips.erase() chips.add_histogram(xlo, xhi, hy) ##chips.add_histogram(xlo, xhi, mplot.y, ["line.color", "red", "line.style", "dot"]) chips.add_curve(mplot.x, mplot.y, ["line.color", "red", "symbol.style", "none"]) if params != None: chips.add_vline( p0, ["line.color", "green", "line.style", "longdash"]) chips.set_plot_xlabel(parname) except: chips.discard_undo_buffer() chips.unlock() raise chips.close_undo_buffer() chips.unlock() sigma = gparam.fwhm.val / (2.0 * np.sqrt(2 * np.log(2))) if verbose: print "" print "Fit to histogram of draws for parameter %s gives" % parname print " mean = %g" % gparam.pos.val print " sigma = %g" % sigma print "" if params != None: idx = params["parnames"] == parname print " best fit = %g" % p0 print " covar sigma = %g" % params["parmaxes"][idx][0] print "" return (gparam.pos.val, sigma, gparam.ampl.val)