def gaussianeb_fit_magseries( times, mags, errs, ebparams, param_bounds=None, scale_errs_redchisq_unity=True, sigclip=10.0, plotfit=False, magsarefluxes=False, verbose=True, curve_fit_kwargs=None, ): '''This fits a double inverted gaussian EB model to a magnitude time series. Parameters ---------- times,mags,errs : np.array The input mag/flux time-series to fit the EB model to. period : float The period to use for EB fit. ebparams : list of float This is a list containing the eclipsing binary parameters:: ebparams = [period (time), epoch (time), pdepth (mags), pduration (phase), psdepthratio, secondaryphase] `period` is the period in days. `epoch` is the time of primary minimum in JD. `pdepth` is the depth of the primary eclipse: - for magnitudes -> `pdepth` should be < 0 - for fluxes -> `pdepth` should be > 0 `pduration` is the length of the primary eclipse in phase. `psdepthratio` is the ratio of the secondary eclipse depth to that of the primary eclipse. `secondaryphase` is the phase at which the minimum of the secondary eclipse is located. This effectively parameterizes eccentricity. If `epoch` is None, this function will do an initial spline fit to find an approximate minimum of the phased light curve using the given period. The `pdepth` provided is checked against the value of `magsarefluxes`. if `magsarefluxes = True`, the `ebdepth` is forced to be > 0; if `magsarefluxes = False`, the `ebdepth` is forced to be < 0. param_bounds : dict or None This is a dict of the upper and lower bounds on each fit parameter. Should be of the form:: {'period': (lower_bound_period, upper_bound_period), 'epoch': (lower_bound_epoch, upper_bound_epoch), 'pdepth': (lower_bound_pdepth, upper_bound_pdepth), 'pduration': (lower_bound_pduration, upper_bound_pduration), 'psdepthratio': (lower_bound_psdepthratio, upper_bound_psdepthratio), 'secondaryphase': (lower_bound_secondaryphase, upper_bound_secondaryphase)} - To indicate that a parameter is fixed, use 'fixed' instead of a tuple providing its lower and upper bounds as tuple. - To indicate that a parameter has no bounds, don't include it in the param_bounds dict. If this is None, the default value of this kwarg will be:: {'period':(0.0,np.inf), # period is between 0 and inf 'epoch':(0.0, np.inf), # epoch is between 0 and inf 'pdepth':(-np.inf,np.inf), # pdepth is between -np.inf and np.inf 'pduration':(0.0,1.0), # pduration is between 0.0 and 1.0 'psdepthratio':(0.0,1.0), # psdepthratio is between 0.0 and 1.0 'secondaryphase':(0.0,1.0), # secondaryphase is between 0.0 and 1.0 scale_errs_redchisq_unity : bool If True, the standard errors on the fit parameters will be scaled to make the reduced chi-sq = 1.0. This sets the ``absolute_sigma`` kwarg for the ``scipy.optimize.curve_fit`` function to False. sigclip : float or int or sequence of two floats/ints or None If a single float or int, a symmetric sigma-clip will be performed using the number provided as the sigma-multiplier to cut out from the input time-series. If a list of two ints/floats is provided, the function will perform an 'asymmetric' sigma-clip. The first element in this list is the sigma value to use for fainter flux/mag values; the second element in this list is the sigma value to use for brighter flux/mag values. For example, `sigclip=[10., 3.]`, will sigclip out greater than 10-sigma dimmings and greater than 3-sigma brightenings. Here the meaning of "dimming" and "brightening" is set by *physics* (not the magnitude system), which is why the `magsarefluxes` kwarg must be correctly set. If `sigclip` is None, no sigma-clipping will be performed, and the time-series (with non-finite elems removed) will be passed through to the output. magsarefluxes : bool If True, will treat the input values of `mags` as fluxes for purposes of plotting the fit and sig-clipping. plotfit : str or False If this is a string, this function will make a plot for the fit to the mag/flux time-series and writes the plot to the path specified here. ignoreinitfail : bool If this is True, ignores the initial failure to find a set of optimized Fourier parameters using the global optimization function and proceeds to do a least-squares fit anyway. verbose : bool If True, will indicate progress and warn of any problems. curve_fit_kwargs : dict or None If not None, this should be a dict containing extra kwargs to pass to the scipy.optimize.curve_fit function. Returns ------- dict This function returns a dict containing the model fit parameters, the minimized chi-sq value and the reduced chi-sq value. The form of this dict is mostly standardized across all functions in this module:: { 'fittype':'gaussianeb', 'fitinfo':{ 'initialparams':the initial EB params provided, 'finalparams':the final model fit EB params, 'finalparamerrs':formal errors in the params, 'fitmags': the model fit mags, 'fitepoch': the epoch of minimum light for the fit, }, 'fitchisq': the minimized value of the fit's chi-sq, 'fitredchisq':the reduced chi-sq value, 'fitplotfile': the output fit plot if fitplot is not None, 'magseries':{ 'times':input times in phase order of the model, 'phase':the phases of the model mags, 'mags':input mags/fluxes in the phase order of the model, 'errs':errs in the phase order of the model, 'magsarefluxes':input value of magsarefluxes kwarg } } ''' stimes, smags, serrs = sigclip_magseries(times, mags, errs, sigclip=sigclip, magsarefluxes=magsarefluxes) # get rid of zero errs nzind = npnonzero(serrs) stimes, smags, serrs = stimes[nzind], smags[nzind], serrs[nzind] # check the ebparams ebperiod, ebepoch, ebdepth = ebparams[0:3] # check if we have a ebepoch to use if ebepoch is None: if verbose: LOGWARNING('no ebepoch given in ebparams, ' 'trying to figure it out automatically...') # do a spline fit to figure out the approximate min of the LC try: spfit = spline_fit_magseries(times, mags, errs, ebperiod, sigclip=sigclip, magsarefluxes=magsarefluxes, verbose=verbose) ebepoch = spfit['fitinfo']['fitepoch'] # if the spline-fit fails, try a savgol fit instead except Exception: sgfit = savgol_fit_magseries(times, mags, errs, ebperiod, sigclip=sigclip, magsarefluxes=magsarefluxes, verbose=verbose) ebepoch = sgfit['fitinfo']['fitepoch'] # if everything failed, then bail out and ask for the ebepoch finally: if ebepoch is None: LOGERROR("couldn't automatically figure out the eb epoch, " "can't continue. please provide it in ebparams.") # assemble the returndict returndict = { 'fittype': 'gaussianeb', 'fitinfo': { 'initialparams': ebparams, 'finalparams': None, 'finalparamerrs': None, 'fitmags': None, 'fitepoch': None, }, 'fitchisq': npnan, 'fitredchisq': npnan, 'fitplotfile': None, 'magseries': { 'phase': None, 'times': None, 'mags': None, 'errs': None, 'magsarefluxes': magsarefluxes, }, } return returndict else: if ebepoch.size > 1: if verbose: LOGWARNING('could not auto-find a single minimum ' 'for ebepoch, using the first one returned') ebparams[1] = ebepoch[0] else: if verbose: LOGWARNING( 'using automatically determined ebepoch = %.5f' % ebepoch) ebparams[1] = ebepoch.item() # next, check the ebdepth and fix it to the form required if magsarefluxes: if ebdepth < 0.0: ebparams[2] = -ebdepth[2] else: if ebdepth > 0.0: ebparams[2] = -ebdepth[2] # finally, do the fit try: # set up the fit parameter bounds if param_bounds is None: curvefit_bounds = (nparray([0.0, 0.0, -npinf, 0.0, 0.0, 0.0]), nparray([npinf, npinf, npinf, 1.0, 1.0, 1.0])) fitfunc_fixed = {} else: # figure out the bounds lower_bounds = [] upper_bounds = [] fitfunc_fixed = {} for ind, key in enumerate( ('period', 'epoch', 'pdepth', 'pduration', 'psdepthratio', 'secondaryphase')): # handle fixed parameters if (key in param_bounds and isinstance(param_bounds[key], str) and param_bounds[key] == 'fixed'): lower_bounds.append(ebparams[ind] - 1.0e-7) upper_bounds.append(ebparams[ind] + 1.0e-7) fitfunc_fixed[key] = ebparams[ind] # handle parameters with lower and upper bounds elif key in param_bounds and isinstance( param_bounds[key], (tuple, list)): lower_bounds.append(param_bounds[key][0]) upper_bounds.append(param_bounds[key][1]) # handle no parameter bounds else: lower_bounds.append(-npinf) upper_bounds.append(npinf) # generate the bounds sequence in the required format curvefit_bounds = (nparray(lower_bounds), nparray(upper_bounds)) # # set up the curve fit function # curvefit_func = partial(eclipses.invgauss_eclipses_curvefit_func, zerolevel=npmedian(smags), fixed_params=fitfunc_fixed) # # run the fit # if curve_fit_kwargs is not None: finalparams, covmatrix = curve_fit( curvefit_func, stimes, smags, p0=ebparams, sigma=serrs, bounds=curvefit_bounds, absolute_sigma=(not scale_errs_redchisq_unity), **curve_fit_kwargs) else: finalparams, covmatrix = curve_fit( curvefit_func, stimes, smags, p0=ebparams, sigma=serrs, bounds=curvefit_bounds, absolute_sigma=(not scale_errs_redchisq_unity), ) except Exception: LOGEXCEPTION("curve_fit returned an exception") finalparams, covmatrix = None, None # if the fit succeeded, then we can return the final parameters if finalparams is not None and covmatrix is not None: # calculate the chisq and reduced chisq fitmags, phase, ptimes, pmags, perrs = eclipses.invgauss_eclipses_func( finalparams, stimes, smags, serrs) fitchisq = npsum( ((fitmags - pmags) * (fitmags - pmags)) / (perrs * perrs)) fitredchisq = fitchisq / (len(pmags) - len(finalparams) - len(fitfunc_fixed)) stderrs = npsqrt(npdiag(covmatrix)) if verbose: LOGINFO('final fit done. chisq = %.5f, reduced chisq = %.5f' % (fitchisq, fitredchisq)) # get the fit epoch fperiod, fepoch = finalparams[:2] # assemble the returndict returndict = { 'fittype': 'gaussianeb', 'fitinfo': { 'initialparams': ebparams, 'finalparams': finalparams, 'finalparamerrs': stderrs, 'fitmags': fitmags, 'fitepoch': fepoch, }, 'fitchisq': fitchisq, 'fitredchisq': fitredchisq, 'fitplotfile': None, 'magseries': { 'phase': phase, 'times': ptimes, 'mags': pmags, 'errs': perrs, 'magsarefluxes': magsarefluxes, }, } # make the fit plot if required if plotfit and isinstance(plotfit, str): make_fit_plot(phase, pmags, perrs, fitmags, fperiod, ptimes.min(), fepoch, plotfit, magsarefluxes=magsarefluxes) returndict['fitplotfile'] = plotfit return returndict # if the leastsq fit failed, return nothing else: LOGERROR('eb-fit: least-squared fit to the light curve failed!') # assemble the returndict returndict = { 'fittype': 'gaussianeb', 'fitinfo': { 'initialparams': ebparams, 'finalparams': None, 'finalparamerrs': None, 'fitmags': None, 'fitepoch': None, }, 'fitchisq': npnan, 'fitredchisq': npnan, 'fitplotfile': None, 'magseries': { 'phase': None, 'times': None, 'mags': None, 'errs': None, 'magsarefluxes': magsarefluxes, }, } return returndict
def gaussianeb_fit_magseries(times, mags, errs, ebparams, sigclip=10.0, plotfit=False, magsarefluxes=False, verbose=True): '''This fits a double inverted gaussian EB model to a magnitude time series. ebparams = [period (time), epoch (time), pdepth (mags), pduration (phase), psdepthratio, secondaryphase] period is the period in days epoch is the time of minimum in JD pdepth is the depth of the primary eclipse - for magnitudes -> ebdepth should be < 0 - for fluxes -> ebdepth should be > 0 pduration is the length of the primary eclipse in phase psdepthratio is the ratio of the secondary eclipse depth to that of the primary eclipse. secondaryphase is the phase at which the minimum of the secondary eclipse is located. This effectively parameterizes eccentricity. if epoch is None, this function will do an initial spline fit to find an approximate minimum of the phased light curve using the given period. the pdepth provided is checked against the value of magsarefluxes. if magsarefluxes = True, the ebdepth is forced to be > 0; if magsarefluxes = False, the ebdepth is forced to be < 0. ''' stimes, smags, serrs = sigclip_magseries(times, mags, errs, sigclip=sigclip, magsarefluxes=magsarefluxes) # get rid of zero errs nzind = npnonzero(serrs) stimes, smags, serrs = stimes[nzind], smags[nzind], serrs[nzind] # check the ebparams ebperiod, ebepoch, ebdepth = ebparams[0:3] # check if we have a ebepoch to use if ebepoch is None: if verbose: LOGWARNING('no ebepoch given in ebparams, ' 'trying to figure it out automatically...') # do a spline fit to figure out the approximate min of the LC try: spfit = spline_fit_magseries(times, mags, errs, ebperiod, sigclip=sigclip, magsarefluxes=magsarefluxes, verbose=verbose) ebepoch = spfit['fitinfo']['fitepoch'] # if the spline-fit fails, try a savgol fit instead except: sgfit = savgol_fit_magseries(times, mags, errs, ebperiod, sigclip=sigclip, magsarefluxes=magsarefluxes, verbose=verbose) ebepoch = sgfit['fitinfo']['fitepoch'] # if everything failed, then bail out and ask for the ebepoch finally: if ebepoch is None: LOGERROR("couldn't automatically figure out the eb epoch, " "can't continue. please provide it in ebparams.") # assemble the returndict returndict = { 'fittype': 'gaussianeb', 'fitinfo': { 'initialparams': ebparams, 'finalparams': None, 'leastsqfit': None, 'fitmags': None, 'fitepoch': None, }, 'fitchisq': npnan, 'fitredchisq': npnan, 'fitplotfile': None, 'magseries': { 'phase': None, 'times': None, 'mags': None, 'errs': None, 'magsarefluxes': magsarefluxes, }, } return returndict else: if ebepoch.size > 1: if verbose: LOGWARNING('could not auto-find a single minimum ' 'for ebepoch, using the first one returned') ebparams[1] = ebepoch[0] else: if verbose: LOGWARNING( 'using automatically determined ebepoch = %.5f' % ebepoch) ebparams[1] = ebepoch # next, check the ebdepth and fix it to the form required if magsarefluxes: if ebdepth < 0.0: ebparams[2] = -ebdepth[2] else: if ebdepth > 0.0: ebparams[2] = -ebdepth[2] # finally, do the fit try: leastsqfit = spleastsq(eclipses.invgauss_eclipses_residual, ebparams, args=(stimes, smags, serrs), full_output=True) except Exception as e: leastsqfit = None # if the fit succeeded, then we can return the final parameters if leastsqfit and leastsqfit[-1] in (1, 2, 3, 4): finalparams = leastsqfit[0] covxmatrix = leastsqfit[1] # calculate the chisq and reduced chisq fitmags, phase, ptimes, pmags, perrs = eclipses.invgauss_eclipses_func( finalparams, stimes, smags, serrs) fitchisq = npsum( ((fitmags - pmags) * (fitmags - pmags)) / (perrs * perrs)) fitredchisq = fitchisq / (len(pmags) - len(finalparams) - 1) # get the residual variance and calculate the formal 1-sigma errs on the # final parameters residuals = leastsqfit[2]['fvec'] residualvariance = (npsum(residuals * residuals) / (pmags.size - finalparams.size)) if covxmatrix is not None: covmatrix = residualvariance * covxmatrix stderrs = npsqrt(npdiag(covmatrix)) else: LOGERROR('covxmatrix not available, fit probably failed!') stderrs = None if verbose: LOGINFO('final fit done. chisq = %.5f, reduced chisq = %.5f' % (fitchisq, fitredchisq)) # get the fit epoch fperiod, fepoch = finalparams[:2] # assemble the returndict returndict = { 'fittype': 'gaussianeb', 'fitinfo': { 'initialparams': ebparams, 'finalparams': finalparams, 'finalparamerrs': stderrs, 'leastsqfit': leastsqfit, 'fitmags': fitmags, 'fitepoch': fepoch, }, 'fitchisq': fitchisq, 'fitredchisq': fitredchisq, 'fitplotfile': None, 'magseries': { 'phase': phase, 'times': ptimes, 'mags': pmags, 'errs': perrs, 'magsarefluxes': magsarefluxes, }, } # make the fit plot if required if plotfit and isinstance(plotfit, str): _make_fit_plot(phase, pmags, perrs, fitmags, fperiod, ptimes.min(), fepoch, plotfit, magsarefluxes=magsarefluxes) returndict['fitplotfile'] = plotfit return returndict # if the leastsq fit failed, return nothing else: LOGERROR('eb-fit: least-squared fit to the light curve failed!') # assemble the returndict returndict = { 'fittype': 'gaussianeb', 'fitinfo': { 'initialparams': ebparams, 'finalparams': None, 'finalparamerrs': None, 'leastsqfit': leastsqfit, 'fitmags': None, 'fitepoch': None, }, 'fitchisq': npnan, 'fitredchisq': npnan, 'fitplotfile': None, 'magseries': { 'phase': None, 'times': None, 'mags': None, 'errs': None, 'magsarefluxes': magsarefluxes, }, } return returndict
def traptransit_fit_magseries(times, mags, errs, transitparams, sigclip=10.0, plotfit=False, magsarefluxes=False, verbose=True): '''This fits a trapezoid transit model to a magnitude time series. transitparams = [transitperiod (time), transitepoch (time), transitdepth (flux or mags), transitduration (phase), ingressduration (phase)] for magnitudes -> transitdepth should be < 0 for fluxes -> transitdepth should be > 0 if transitepoch is None, this function will do an initial spline fit to find an approximate minimum of the phased light curve using the given period. the transitdepth provided is checked against the value of magsarefluxes. if magsarefluxes = True, the transitdepth is forced to be > 0; if magsarefluxes = False, the transitdepth is forced to be < 0. ''' stimes, smags, serrs = sigclip_magseries(times, mags, errs, sigclip=sigclip, magsarefluxes=magsarefluxes) # get rid of zero errs nzind = npnonzero(serrs) stimes, smags, serrs = stimes[nzind], smags[nzind], serrs[nzind] # check the transitparams transitperiod, transitepoch, transitdepth = transitparams[0:3] # check if we have a transitepoch to use if transitepoch is None: if verbose: LOGWARNING('no transitepoch given in transitparams, ' 'trying to figure it out automatically...') # do a spline fit to figure out the approximate min of the LC try: spfit = spline_fit_magseries(times, mags, errs, transitperiod, sigclip=sigclip, magsarefluxes=magsarefluxes, verbose=verbose) transitepoch = spfit['fitinfo']['fitepoch'] # if the spline-fit fails, try a savgol fit instead except: sgfit = savgol_fit_magseries(times, mags, errs, transitperiod, sigclip=sigclip, magsarefluxes=magsarefluxes, verbose=verbose) transitepoch = sgfit['fitinfo']['fitepoch'] # if everything failed, then bail out and ask for the transitepoch finally: if transitepoch is None: LOGERROR( "couldn't automatically figure out the transit epoch, " "can't continue. please provide it in transitparams.") # assemble the returndict returndict = { 'fittype': 'traptransit', 'fitinfo': { 'initialparams': transitparams, 'finalparams': None, 'leastsqfit': None, 'fitmags': None, 'fitepoch': None, }, 'fitchisq': npnan, 'fitredchisq': npnan, 'fitplotfile': None, 'magseries': { 'phase': None, 'times': None, 'mags': None, 'errs': None, 'magsarefluxes': magsarefluxes, }, } return returndict else: # check the case when there are more than one transitepochs returned if transitepoch.size > 0: if verbose: LOGWARNING( "could not auto-find a single minimum in LC for " "transitepoch, using the first one returned") transitparams[1] = transitepoch[0] else: if verbose: LOGWARNING( 'using automatically determined transitepoch = %.5f' % transitepoch) transitparams[1] = transitepoch # next, check the transitdepth and fix it to the form required if magsarefluxes: if transitdepth < 0.0: transitparams[2] = -transitdepth[2] else: if transitdepth > 0.0: transitparams[2] = -transitdepth[2] # finally, do the fit try: leastsqfit = spleastsq(transits.trapezoid_transit_residual, transitparams, args=(stimes, smags, serrs), full_output=True) except Exception as e: leastsqfit = None # if the fit succeeded, then we can return the final parameters if leastsqfit and leastsqfit[-1] in (1, 2, 3, 4): finalparams = leastsqfit[0] covxmatrix = leastsqfit[1] # calculate the chisq and reduced chisq fitmags, phase, ptimes, pmags, perrs = transits.trapezoid_transit_func( finalparams, stimes, smags, serrs) fitchisq = npsum( ((fitmags - pmags) * (fitmags - pmags)) / (perrs * perrs)) fitredchisq = fitchisq / (len(pmags) - len(finalparams) - 1) # get the residual variance and calculate the formal 1-sigma errs on the # final parameters residuals = leastsqfit[2]['fvec'] residualvariance = (npsum(residuals * residuals) / (pmags.size - finalparams.size)) if covxmatrix is not None: covmatrix = residualvariance * covxmatrix stderrs = npsqrt(npdiag(covmatrix)) else: LOGERROR('covxmatrix not available, fit probably failed!') stderrs = None if verbose: LOGINFO('final fit done. chisq = %.5f, reduced chisq = %.5f' % (fitchisq, fitredchisq)) # get the fit epoch fperiod, fepoch = finalparams[:2] # assemble the returndict returndict = { 'fittype': 'traptransit', 'fitinfo': { 'initialparams': transitparams, 'finalparams': finalparams, 'finalparamerrs': stderrs, 'leastsqfit': leastsqfit, 'fitmags': fitmags, 'fitepoch': fepoch, }, 'fitchisq': fitchisq, 'fitredchisq': fitredchisq, 'fitplotfile': None, 'magseries': { 'phase': phase, 'times': ptimes, 'mags': pmags, 'errs': perrs, 'magsarefluxes': magsarefluxes, }, } # make the fit plot if required if plotfit and isinstance(plotfit, str): _make_fit_plot(phase, pmags, perrs, fitmags, fperiod, ptimes.min(), fepoch, plotfit, magsarefluxes=magsarefluxes) returndict['fitplotfile'] = plotfit return returndict # if the leastsq fit failed, return nothing else: LOGERROR('trapezoid-fit: least-squared fit to the light curve failed!') # assemble the returndict returndict = { 'fittype': 'traptransit', 'fitinfo': { 'initialparams': transitparams, 'finalparams': None, 'finalparamerrs': None, 'leastsqfit': leastsqfit, 'fitmags': None, 'fitepoch': None, }, 'fitchisq': npnan, 'fitredchisq': npnan, 'fitplotfile': None, 'magseries': { 'phase': None, 'times': None, 'mags': None, 'errs': None, 'magsarefluxes': magsarefluxes, }, } return returndict
def fourier_fit_magseries( times, mags, errs, period, fourierorder=None, fourierparams=None, fix_period=True, scale_errs_redchisq_unity=True, sigclip=3.0, magsarefluxes=False, plotfit=False, ignoreinitfail=True, verbose=True, curve_fit_kwargs=None, ): '''This fits a Fourier series to a mag/flux time series. Parameters ---------- times,mags,errs : np.array The input mag/flux time-series to fit a Fourier cosine series to. period : float The period to use for the Fourier fit. fourierorder : None or int If this is an int, will be interpreted as the Fourier order of the series to fit to the input mag/flux times-series. If this is None and `fourierparams` is specified, `fourierparams` will be used directly to generate the fit Fourier series. If `fourierparams` is also None, this function will try to fit a Fourier cosine series of order 3 to the mag/flux time-series. fourierparams : list of floats or None If this is specified as a list of floats, it must be of the form below:: [fourier_amp1, fourier_amp2, fourier_amp3,...,fourier_ampN, fourier_phase1, fourier_phase2, fourier_phase3,...,fourier_phaseN] to specify a Fourier cosine series of order N. If this is None and `fourierorder` is specified, the Fourier order specified there will be used to construct the Fourier cosine series used to fit the input mag/flux time-series. If both are None, this function will try to fit a Fourier cosine series of order 3 to the input mag/flux time-series. fix_period : bool If True, will fix the period with fitting the sinusoidal function to the phased light curve. scale_errs_redchisq_unity : bool If True, the standard errors on the fit parameters will be scaled to make the reduced chi-sq = 1.0. This sets the ``absolute_sigma`` kwarg for the ``scipy.optimize.curve_fit`` function to False. sigclip : float or int or sequence of two floats/ints or None If a single float or int, a symmetric sigma-clip will be performed using the number provided as the sigma-multiplier to cut out from the input time-series. If a list of two ints/floats is provided, the function will perform an 'asymmetric' sigma-clip. The first element in this list is the sigma value to use for fainter flux/mag values; the second element in this list is the sigma value to use for brighter flux/mag values. For example, `sigclip=[10., 3.]`, will sigclip out greater than 10-sigma dimmings and greater than 3-sigma brightenings. Here the meaning of "dimming" and "brightening" is set by *physics* (not the magnitude system), which is why the `magsarefluxes` kwarg must be correctly set. If `sigclip` is None, no sigma-clipping will be performed, and the time-series (with non-finite elems removed) will be passed through to the output. magsarefluxes : bool If True, will treat the input values of `mags` as fluxes for purposes of plotting the fit and sig-clipping. plotfit : str or False If this is a string, this function will make a plot for the fit to the mag/flux time-series and writes the plot to the path specified here. ignoreinitfail : bool If this is True, ignores the initial failure to find a set of optimized Fourier parameters using the global optimization function and proceeds to do a least-squares fit anyway. verbose : bool If True, will indicate progress and warn of any problems. curve_fit_kwargs : dict or None If not None, this should be a dict containing extra kwargs to pass to the scipy.optimize.curve_fit function. Returns ------- dict This function returns a dict containing the model fit parameters, the minimized chi-sq value and the reduced chi-sq value. The form of this dict is mostly standardized across all functions in this module:: { 'fittype':'fourier', 'fitinfo':{ 'finalparams': the list of final model fit params, 'finalparamerrs': list of errs for each model fit param, 'fitmags': the model fit mags, 'fitperiod': the fit period if this wasn't set to fixed, 'fitepoch': this is times.min() for this fit type, 'actual_fitepoch': time of minimum light from fit model ... other fit function specific keys ... }, 'fitchisq': the minimized value of the fit's chi-sq, 'fitredchisq':the reduced chi-sq value, 'fitplotfile': the output fit plot if fitplot is not None, 'magseries':{ 'times':input times in phase order of the model, 'phase':the phases of the model mags, 'mags':input mags/fluxes in the phase order of the model, 'errs':errs in the phase order of the model, 'magsarefluxes':input value of magsarefluxes kwarg } } NOTE: the returned value of 'fitepoch' in the 'fitinfo' dict returned by this function is the time value of the first observation since this is where the LC is folded for the fit procedure. To get the actual time of minimum epoch as calculated by a spline fit to the phased LC, use the key 'actual_fitepoch' in the 'fitinfo' dict. ''' stimes, smags, serrs = sigclip_magseries(times, mags, errs, sigclip=sigclip, magsarefluxes=magsarefluxes) # get rid of zero errs nzind = npnonzero(serrs) stimes, smags, serrs = stimes[nzind], smags[nzind], serrs[nzind] phase, pmags, perrs, ptimes, mintime = (get_phased_quantities( stimes, smags, serrs, period)) # get the fourier order either from the scalar order kwarg... if fourierorder and fourierorder > 0 and not fourierparams: fourieramps = [0.6] + [0.2] * (fourierorder - 1) fourierphas = [0.1] + [0.1] * (fourierorder - 1) fourierparams = fourieramps + fourierphas # or from the fully specified coeffs vector elif not fourierorder and fourierparams: fourierorder = int(len(fourierparams) / 2) else: LOGWARNING('specified both/neither Fourier order AND Fourier coeffs, ' 'using default Fourier order of 3') fourierorder = 3 fourieramps = [0.6] + [0.2] * (fourierorder - 1) fourierphas = [0.1] + [0.1] * (fourierorder - 1) fourierparams = fourieramps + fourierphas if verbose: LOGINFO('fitting Fourier series of order %s to ' 'mag series with %s observations, ' 'using period %.6f, folded at %.6f' % (fourierorder, len(phase), period, mintime)) # initial minimize call to find global minimum in chi-sq initialfit = spminimize(_fourier_chisq, fourierparams, args=(phase, pmags, perrs)) # make sure this initial fit succeeds before proceeding if initialfit.success or ignoreinitfail: if verbose: LOGINFO('initial fit done, refining...') leastsqparams = initialfit.x try: curvefit_params = npconcatenate((nparray([period]), leastsqparams)) # set up the bounds for the fit parameters if fix_period: curvefit_bounds = ([period - 1.0e-7] + [-npinf] * fourierorder + [-npinf] * fourierorder, [period + 1.0e-7] + [npinf] * fourierorder + [npinf] * fourierorder) else: curvefit_bounds = ([0.0] + [-npinf] * fourierorder + [-npinf] * fourierorder, [npinf] + [npinf] * fourierorder + [npinf] * fourierorder) curvefit_func = partial( sinusoidal.fourier_curvefit_func, zerolevel=npmedian(smags), epoch=mintime, fixed_period=period if fix_period else None, ) if curve_fit_kwargs is not None: finalparams, covmatrix = curve_fit( curvefit_func, stimes, smags, p0=curvefit_params, sigma=serrs, bounds=curvefit_bounds, absolute_sigma=(not scale_errs_redchisq_unity), **curve_fit_kwargs) else: finalparams, covmatrix = curve_fit( curvefit_func, stimes, smags, p0=curvefit_params, sigma=serrs, bounds=curvefit_bounds, absolute_sigma=(not scale_errs_redchisq_unity), ) except Exception: LOGEXCEPTION("curve_fit returned an exception") finalparams, covmatrix = None, None # if the fit succeeded, then we can return the final parameters if finalparams is not None and covmatrix is not None: # this is the fit period fperiod = finalparams[0] phase, pmags, perrs, ptimes, mintime = (get_phased_quantities( stimes, smags, serrs, fperiod)) # calculate the chisq and reduced chisq fitmags = _fourier_func(finalparams[1:], phase, pmags) fitchisq = npsum( ((fitmags - pmags) * (fitmags - pmags)) / (perrs * perrs)) n_free_params = len(pmags) - len(finalparams) if fix_period: n_free_params -= 1 fitredchisq = fitchisq / n_free_params stderrs = npsqrt(npdiag(covmatrix)) if verbose: LOGINFO('final fit done. chisq = %.5f, reduced chisq = %.5f' % (fitchisq, fitredchisq)) # figure out the time of light curve minimum (i.e. the fit epoch) # this is when the fit mag is maximum (i.e. the faintest) # or if magsarefluxes = True, then this is when fit flux is minimum if not magsarefluxes: fitmagminind = npwhere(fitmags == npmax(fitmags)) else: fitmagminind = npwhere(fitmags == npmin(fitmags)) if len(fitmagminind[0]) > 1: fitmagminind = (fitmagminind[0][0], ) # assemble the returndict returndict = { 'fittype': 'fourier', 'fitinfo': { 'fourierorder': fourierorder, # return coeffs only for backwards compatibility with # existing functions that use the returned value of # fourier_fit_magseries 'finalparams': finalparams[1:], 'finalparamerrs': stderrs, 'initialfit': initialfit, 'fitmags': fitmags, 'fitperiod': finalparams[0], # the 'fitepoch' is just the minimum time here 'fitepoch': mintime, # the actual fit epoch is calculated as the time of minimum # light OF the fit model light curve 'actual_fitepoch': ptimes[fitmagminind] }, 'fitchisq': fitchisq, 'fitredchisq': fitredchisq, 'fitplotfile': None, 'magseries': { 'times': ptimes, 'phase': phase, 'mags': pmags, 'errs': perrs, 'magsarefluxes': magsarefluxes }, } # make the fit plot if required if plotfit and isinstance(plotfit, str): make_fit_plot(phase, pmags, perrs, fitmags, fperiod, mintime, mintime, plotfit, magsarefluxes=magsarefluxes) returndict['fitplotfile'] = plotfit return returndict # if the leastsq fit did not succeed, return Nothing else: LOGERROR( 'fourier-fit: least-squared fit to the light curve failed') return { 'fittype': 'fourier', 'fitinfo': { 'fourierorder': fourierorder, 'finalparams': None, 'finalparamerrs': None, 'initialfit': initialfit, 'fitmags': None, 'fitperiod': None, 'fitepoch': None, 'actual_fitepoch': None, }, 'fitchisq': npnan, 'fitredchisq': npnan, 'fitplotfile': None, 'magseries': { 'times': ptimes, 'phase': phase, 'mags': pmags, 'errs': perrs, 'magsarefluxes': magsarefluxes } } # if the fit didn't succeed, we can't proceed else: LOGERROR('initial Fourier fit did not succeed, ' 'reason: %s, returning scipy OptimizeResult' % initialfit.message) return { 'fittype': 'fourier', 'fitinfo': { 'fourierorder': fourierorder, 'finalparams': None, 'finalparamerrs': None, 'initialfit': initialfit, 'fitmags': None, 'fitperiod': None, 'fitepoch': None, 'actual_fitepoch': None, }, 'fitchisq': npnan, 'fitredchisq': npnan, 'fitplotfile': None, 'magseries': { 'times': ptimes, 'phase': phase, 'mags': pmags, 'errs': perrs, 'magsarefluxes': magsarefluxes } }