def test1minimize(self): """ I sigproc.fit.Minimizer Jacobian minimize """ self.model.setup_parameters(value=[3.0, 1.0, 1.0]) result = fit.minimize(self.x, self.y, self.model) names = self.model.parameters.name val = self.model.parameters.value err = self.model.parameters.stderr valr = [2.4272, 1.2912, 0.4928] errr = [0.0665, 0.0772, 0.0297] print(self.model.param2str(accuracy=4, output='result')) msg = 'Resulting value for {:s} is not correct within {:.0f}\%' pc = 0.05 # 5% difference is allowed for i in range(3): self.assertAlmostEqual(val[i], valr[i], delta=pc * valr[i], msg=msg.format(names[i], pc * 100)) msg = 'Resulting std-error for {:s} is not correct within {:.0f}\%' pc = 0.1 # 10% difference is allowed for i in range(3): self.assertAlmostEqual(err[i], errr[i], delta=pc * errr[i], msg=msg.format(names[i], pc * 100))
def test5mc_error(self): """ I sigproc.fit.Minimizer Function calculate_MC_error """ result = fit.minimize(self.x, self.y, self.model) np.random.seed(11) mcerrors = result.calculate_MC_error(errors=0.5, points=100, short_output=True, verbose=False) msg = 'Short output returned wrong type (array expected)' self.assertEqual(np.shape(mcerrors), (3,), msg=msg) msg = 'Calculated MC errors are wrong' self.assertArrayAlmostEqual(mcerrors, [0.02156, 0.00108, 0.00607], places=3, msg=msg) np.random.seed(11) mcerrors = result.calculate_MC_error(errors=0.5, points=50, short_output=False, verbose=False) msg = 'Long output returned wrong type (dict expected)' self.assertEqual(type(mcerrors), dict, msg=msg) msg = 'Not all parameters are included in output' self.assertTrue('ampl' in mcerrors, msg=msg) self.assertTrue('freq' in mcerrors, msg=msg) self.assertTrue('phase' in mcerrors, msg=msg) msg = 'MC error is not stored in the parameters object' params = self.model.parameters self.assertEqual(params['ampl'].mcerr, mcerrors['ampl'], msg=msg) self.assertEqual(params['freq'].mcerr, mcerrors['freq'], msg=msg) self.assertEqual(params['phase'].mcerr, mcerrors['phase'], msg=msg)
def fitFunction(x, y, initial, function, vary): """ Fit a function to a set of x and y values. @param x: The x grid @type x: array @param y: The y grid @type y: array @param initial: initial parameters @type initial: list @param function: The function to be fitted @type function: funclib.function (e.g. funclib.soft_parabola,funclib.gauss) @param vary: Allow initial parameter to be changed in fitting process. Must have same length as initial. @type vary: list[bool] @return: The model after minimization @rtype: funclib.function() (some function) """ #-- fit only soft parabola # 1. setup model #mymodel = funclib.soft_parabola() if function == funclib.gauss and False in vary: mymodel = function(use_jacobian=False) else: mymodel = function() # 2. Initial values: e.g. for SP [int,vlsr,vexp,gamma] mymodel.setup_parameters(values=initial, vary=vary) # 3. minimize and evaluate fit result = fit.minimize(x, y, mymodel) return mymodel
def fitFunction(x, y, initial, function, vary): """ Fit a function to a set of x and y values. @param x: The x grid @type x: array @param y: The y grid @type y: array @param initial: initial parameters @type initial: list @param function: The function to be fitted @type function: funclib.function (e.g. funclib.soft_parabola,funclib.gauss) @param vary: Allow initial parameter to be changed in fitting process. Must have same length as initial. @type vary: list[bool] @return: The model after minimization @rtype: funclib.function() (some function) """ # -- fit only soft parabola # 1. setup model # mymodel = funclib.soft_parabola() if function == funclib.gauss and False in vary: mymodel = function(use_jacobian=False) else: mymodel = function() # 2. Initial values: e.g. for SP [int,vlsr,vexp,gamma] mymodel.setup_parameters(values=initial, vary=vary) # 3. minimize and evaluate fit result = fit.minimize(x, y, mymodel) return mymodel
def test3ci_interval(self): """ I sigproc.fit.Minimizer Function calculate_CI """ model = self.model result = fit.minimize(self.x, self.y, model) ci = result.calculate_CI(parameters=None, sigmas=[0.674, 0.997]) expected_ci = "\n\t{'ampl': {0.674: (1.4955, 1.5036), 0.997: (1.4873, 1.5117)}"\ +"\n\t'phase': {0.674: (3.1372, 3.1394), 0.997: (3.1349, 3.1417)},"\ +"\n\t'freq': {0.674: (0.1202, 0.1206), 0.997: (0.1197, 0.1210)}}" msg = 'dict output format is not correct:\nexpected: '+expected_ci+\ '\nreceived: \n\t'+str(ci) self.assertTrue('ampl' in ci, msg=msg) self.assertTrue('phase' in ci, msg=msg) self.assertTrue('freq' in ci, msg=msg) self.assertTrue(0.674 in ci['ampl'] and 0.997 in ci['ampl'], msg=msg) self.assertTrue(0.674 in ci['phase'] and 0.997 in ci['phase'], msg=msg) self.assertTrue(0.674 in ci['freq'] and 0.997 in ci['freq'], msg=msg) msg = 'ci results are not correct, expected: \n' + expected_ci self.assertArrayAlmostEqual(ci['ampl'][0.674], [1.4955, 1.5036], places=2, msg=msg) self.assertArrayAlmostEqual(ci['ampl'][0.997], [1.4873, 1.5117], places=2, msg=msg) self.assertArrayAlmostEqual(ci['phase'][0.674], [3.1372, 3.1394], places=2, msg=msg) self.assertArrayAlmostEqual(ci['phase'][0.997], [3.1349, 3.1417], places=2, msg=msg) self.assertArrayAlmostEqual(ci['freq'][0.674], [0.1202, 0.1206], places=2, msg=msg) self.assertArrayAlmostEqual(ci['freq'][0.997], [0.1197, 0.1210], places=2, msg=msg) msg = 'ci intervals are not stored in the parameter object' for name, param in list(model.parameters.items()): self.assertTrue(0.674 in param.cierr, msg=msg) self.assertTrue(0.997 in param.cierr, msg=msg) msg = 'Reruning calculate_CI fails and raises an exception!' try: result.calculate_CI(parameters=None, sigma=0.85, maxiter=200, short_output=True) result.calculate_CI(parameters=None, sigma=0.95, maxiter=200, short_output=True) except Exception: self.fail(msg)
def test2setupJacobian(self, mocked_class): """ sigproc.fit.Minimizer _setup_jacobian_function """ result = fit.minimize(self.x, self.y, self.model, err=self.errors) msg = 'attr jacobian is not set' self.assertTrue(hasattr(result, 'jacobian'), msg=msg) msg = 'jacobian should be None in this case' self.assertTrue(result.jacobian == None, msg=msg)
def test1minimize(self): """ I sigproc.fit.Minimizer Function minimize """ model = self.model result = fit.minimize(self.x, self.y, model) values = model.get_parameters()[0] msg = 'Fit did not converge to the correct values' self.assertAlmostEqual(values[0], self.value[0], places=2, msg=msg) self.assertAlmostEqual(values[1], self.value[1], places=2, msg=msg) self.assertAlmostEqual(values[2], self.value[2], places=2, msg=msg)
def testMinimize(self): """ I sigproc.fit.Minimizer Model minimize """ model = self.fitModel result = fit.minimize(self.x, self.y, model) functions = model.functions values1 = functions[0].get_parameters()[0] values2 = functions[1].get_parameters()[0] self.assertArrayAlmostEqual(values1, self.value1, places=2) self.assertArrayAlmostEqual(values2, self.value2, places=2)
def test1setupResiduals(self, mocked_class): """ sigproc.fit.Minimizer _setup_residual_function """ result = fit.minimize(self.x, self.y, self.model) msg = 'attr residuals is not set' self.assertTrue(hasattr(result, 'residuals'), msg=msg) msg = 'residuals are not correctly calculated' residuals = result.residuals(self.parameters, self.x, self.y, weights=self.weights) self.assertTrue(np.all(np.abs(residuals) < 1.0), msg=msg)
def test4ci2d_interval(self): """ I sigproc.fit.Minimizer Jacobian calculate_CI_2D """ self.model.setup_parameters(value=[3.0, 1.0, 1.0]) result = fit.minimize(self.x, self.y, self.model) x, y, ci = result.calculate_CI_2D(xpar='p1', ypar='p2', res=10, limits=None, ctype='prob') cir = [99.9977, 99.9019, 97.5451, 74.5012, 20.5787, 38.3632, 87.5572, 99.1614, 99.9734, 99.9994] msg = 'The 2D CI values are not correct within 2\%' pc = 0.05 for i in range(3): self.assertArrayAlmostEqual(ci[4], cir, delta = 2, msg = msg)
def test3ci_interval(self): """ I sigproc.fit.Minimizer Jacobian calculate_CI """ self.model.setup_parameters(value=[3.0, 1.0, 1.0]) result = fit.minimize(self.x, self.y, self.model) ci = result.calculate_CI(parameters=None, sigmas=[0.994]) cir = [[2.2378,2.6210],[1.0807,1.5276],[0.4015,0.5740]] val = self.model.parameters.value names = self.model.parameters.name msg = 'The 99.4\% CI interval for {:s} is not correct within {:.0f}\%' pc = 0.05 for i,p in enumerate(names): self.assertArrayAlmostEqual(ci[p][0.994], cir[i], delta = pc*val[i], msg=msg.format(p, pc*100))
def test5mc_error(self): """ I sigproc.fit.Minimizer Jacobian calculate_MC_error """ self.model.setup_parameters(value=[3.0, 1.0, 1.0]) result = fit.minimize(self.x, self.y, self.model) result.calculate_MC_error(errors=self.err, points=100, short_output=True, verbose=False) names = self.model.parameters.name err = self.model.parameters.mcerr errr = [0.0795, 0.0688, 0.0281] msg = 'Resulting MC-error for {:s} is not correct within {:.0f}\%' pc = 0.05 # 10% difference is allowed for i in range(3): self.assertAlmostEqual(err[i], errr[i], delta = pc*errr[i], msg=msg.format(names[i], pc*100))
def test4ci2d_interval(self): """ I sigproc.fit.Minimizer Function calculate_CI_2D """ model = self.model result = fit.minimize(self.x, self.y, model) x, y, ci = result.calculate_CI_2D(xpar='ampl', ypar='freq', res=10, limits=None, ctype='prob') values, errors = model.get_parameters() msg = 'Auto limits are not value +- 5 * stderr' vmin = values - 5 * errors vmax = values + 5 * errors self.assertAlmostEqual(min(x), vmin[0], places=4, msg=msg) self.assertAlmostEqual(max(x), vmax[0], places=4, msg=msg) self.assertAlmostEqual(min(y), vmin[1], places=4, msg=msg) self.assertAlmostEqual(max(y), vmax[1], places=4, msg=msg) msg = 'Grid values are not correctly distributed' exp1 = [ 1.4790, 1.4835, 1.4881, 1.4927, 1.4972, 1.5018, 1.5064, 1.5109, 1.5155, 1.5200 ] exp2 = [ 0.11937, 0.11960, 0.11983, 0.12006, 0.12028, 0.12051, 0.12074, 0.12097, 0.12120, 0.12143 ] self.assertArrayAlmostEqual(x, exp1, places=3, msg=msg) self.assertArrayAlmostEqual(y, exp2, places=4, msg=msg) msg = 'resolution of the grid is not correct' self.assertEqual(len(ci), 10, msg=msg) self.assertEqual(len(ci[0]), 10, msg=msg) msg = 'CI values are not correct' exp1 = [ 99.9996, 99.9572, 98.2854, 79.4333, 27.5112, 25.6265, 77.7912, 98.0525, 99.9489, 99.9995 ] exp2 = [ 99.9996, 99.9565, 98.2751, 79.4030, 27.5112, 25.6436, 77.8194, 98.0628, 99.9496, 99.9996 ] self.assertArrayAlmostEqual(ci[4], exp1, places=2, msg=msg) self.assertArrayAlmostEqual(ci[:, 4], exp2, places=2, msg=msg)
def test3ci_interval(self): """ I sigproc.fit.Minimizer Jacobian calculate_CI """ self.model.setup_parameters(value=[3.0, 1.0, 1.0]) result = fit.minimize(self.x, self.y, self.model) ci = result.calculate_CI(parameters=None, sigmas=[0.994]) cir = [[2.2378, 2.6210], [1.0807, 1.5276], [0.4015, 0.5740]] val = self.model.parameters.value names = self.model.parameters.name msg = 'The 99.4\% CI interval for {:s} is not correct within {:.0f}\%' pc = 0.05 for i, p in enumerate(names): self.assertArrayAlmostEqual(ci[p][0.994], cir[i], delta=pc * val[i], msg=msg.format(p, pc * 100))
def test4ci2d_interval(self): """ I sigproc.fit.Minimizer Jacobian calculate_CI_2D """ self.model.setup_parameters(value=[3.0, 1.0, 1.0]) result = fit.minimize(self.x, self.y, self.model) x, y, ci = result.calculate_CI_2D(xpar='p1', ypar='p2', res=10, limits=None, ctype='prob') cir = [ 99.9977, 99.9019, 97.5451, 74.5012, 20.5787, 38.3632, 87.5572, 99.1614, 99.9734, 99.9994 ] msg = 'The 2D CI values are not correct within 2\%' pc = 0.05 for i in range(3): self.assertArrayAlmostEqual(ci[4], cir, delta=2, msg=msg)
def test5mc_error(self): """ I sigproc.fit.Minimizer Jacobian calculate_MC_error """ self.model.setup_parameters(value=[3.0, 1.0, 1.0]) result = fit.minimize(self.x, self.y, self.model) result.calculate_MC_error(errors=self.err, points=100, short_output=True, verbose=False) names = self.model.parameters.name err = self.model.parameters.mcerr errr = [0.0795, 0.0688, 0.0281] msg = 'Resulting MC-error for {:s} is not correct within {:.0f}\%' pc = 0.05 # 10% difference is allowed for i in range(3): self.assertAlmostEqual(err[i], errr[i], delta=pc * errr[i], msg=msg.format(names[i], pc * 100))
def test3ci_interval(self): """ I sigproc.fit.Minimizer Function calculate_CI """ model = self.model result = fit.minimize(self.x, self.y, model) ci = result.calculate_CI(parameters=None, sigmas=[0.674, 0.997]) expected_ci = "\n\t{'ampl': {0.674: (1.4955, 1.5036), 0.997: (1.4873, 1.5117)}"\ +"\n\t'phase': {0.674: (3.1372, 3.1394), 0.997: (3.1349, 3.1417)},"\ +"\n\t'freq': {0.674: (0.1202, 0.1206), 0.997: (0.1197, 0.1210)}}" msg = 'dict output format is not correct:\nexpected: '+expected_ci+\ '\nreceived: \n\t'+str(ci) self.assertTrue('ampl' in ci, msg=msg) self.assertTrue('phase' in ci, msg=msg) self.assertTrue('freq' in ci, msg=msg) self.assertTrue(0.674 in ci['ampl'] and 0.997 in ci['ampl'], msg=msg) self.assertTrue(0.674 in ci['phase'] and 0.997 in ci['phase'], msg=msg) self.assertTrue(0.674 in ci['freq'] and 0.997 in ci['freq'], msg=msg) msg = 'ci results are not correct, expected: \n' + expected_ci self.assertArrayAlmostEqual(ci['ampl'][0.674], [1.4955, 1.5036], places=2, msg=msg) self.assertArrayAlmostEqual(ci['ampl'][0.997], [1.4873, 1.5117], places=2, msg=msg) self.assertArrayAlmostEqual(ci['phase'][0.674], [3.1372, 3.1394], places=2, msg=msg) self.assertArrayAlmostEqual(ci['phase'][0.997], [3.1349, 3.1417], places=2, msg=msg) self.assertArrayAlmostEqual(ci['freq'][0.674], [0.1202, 0.1206], places=2, msg=msg) self.assertArrayAlmostEqual(ci['freq'][0.997], [0.1197, 0.1210], places=2, msg=msg) msg = 'ci intervals are not stored in the parameter object' for name, param in model.parameters.items(): self.assertTrue(0.674 in param.cierr, msg=msg) self.assertTrue(0.997 in param.cierr, msg=msg) msg = 'Reruning calculate_CI fails and raises an exception!' try: result.calculate_CI(parameters=None, sigma=0.85, maxiter=200, short_output=True) result.calculate_CI(parameters=None, sigma=0.95, maxiter=200, short_output=True) except Exception: self.fail(msg)
def test3perturbdata(self, mocked_class): """ sigproc.fit.Minimizer _perturb_input_data """ result = fit.minimize(self.x, self.y, self.model, errors=self.errors) result.x = self.x.reshape(2, 50) result.y = self.y.reshape(2, 50) result.errors = result.errors.reshape(2, 50) np.random.seed(11) npoints = 100 y_ = result._perturb_input_data(100) dy = np.abs(np.ravel(y_[0]) - np.ravel(result.y)) print(dy) msg = 'Perturbed data has wrong shape' self.assertEqual(y_.shape, (100, ) + result.y.shape, msg=msg) msg = 'Perturbed data is NOT perturbed' self.assertTrue(np.all(dy > 0.), msg=msg) msg = 'Perturbed data is to strongly perturbed' self.assertTrue(np.all(dy < 3.), msg=msg)
def test3perturbdata(self, mocked_class): """ sigproc.fit.Minimizer _perturb_input_data """ result = fit.minimize(self.x, self.y, self.model, errors=self.errors) result.x = self.x.reshape(2,50) result.y = self.y.reshape(2,50) result.errors = result.errors.reshape(2,50) np.random.seed(11) npoints = 100 y_ = result._perturb_input_data(100) dy = np.abs(np.ravel(y_[0]) - np.ravel(result.y)) print dy msg = 'Perturbed data has wrong shape' self.assertEqual(y_.shape, (100,) + result.y.shape, msg=msg) msg = 'Perturbed data is NOT perturbed' self.assertTrue(np.all(dy > 0.), msg=msg) msg = 'Perturbed data is to strongly perturbed' self.assertTrue(np.all(dy < 3.), msg=msg)
def test5mc_error(self): """ I sigproc.fit.Minimizer Function calculate_MC_error """ result = fit.minimize(self.x, self.y, self.model) np.random.seed(11) mcerrors = result.calculate_MC_error(errors=0.5, points=100, short_output=True, verbose=False) msg = 'Short output returned wrong type (array expected)' self.assertEqual(np.shape(mcerrors), (3, ), msg=msg) msg = 'Calculated MC errors are wrong' self.assertArrayAlmostEqual(mcerrors, [0.02156, 0.00108, 0.00607], places=3, msg=msg) np.random.seed(11) mcerrors = result.calculate_MC_error(errors=0.5, points=50, short_output=False, verbose=False) msg = 'Long output returned wrong type (dict expected)' self.assertEqual(type(mcerrors), dict, msg=msg) msg = 'Not all parameters are included in output' self.assertTrue('ampl' in mcerrors, msg=msg) self.assertTrue('freq' in mcerrors, msg=msg) self.assertTrue('phase' in mcerrors, msg=msg) msg = 'MC error is not stored in the parameters object' params = self.model.parameters self.assertEqual(params['ampl'].mcerr, mcerrors['ampl'], msg=msg) self.assertEqual(params['freq'].mcerr, mcerrors['freq'], msg=msg) self.assertEqual(params['phase'].mcerr, mcerrors['phase'], msg=msg)
def test1minimize(self): """ I sigproc.fit.Minimizer Jacobian minimize """ self.model.setup_parameters(value=[3.0, 1.0, 1.0]) result = fit.minimize(self.x, self.y, self.model) names = self.model.parameters.name val = self.model.parameters.value err = self.model.parameters.stderr valr = [2.4272, 1.2912, 0.4928] errr = [0.0665, 0.0772, 0.0297] print self.model.param2str(accuracy=4, output='result') msg = 'Resulting value for {:s} is not correct within {:.0f}\%' pc = 0.05 # 5% difference is allowed for i in range(3): self.assertAlmostEqual(val[i], valr[i], delta = pc*valr[i], msg=msg.format(names[i], pc*100)) msg = 'Resulting std-error for {:s} is not correct within {:.0f}\%' pc = 0.1 # 10% difference is allowed for i in range(3): self.assertAlmostEqual(err[i], errr[i], delta = pc*errr[i], msg=msg.format(names[i], pc*100))
def test4ci2d_interval(self): """ I sigproc.fit.Minimizer Function calculate_CI_2D """ model = self.model result = fit.minimize(self.x, self.y, model) x, y, ci = result.calculate_CI_2D(xpar='ampl', ypar='freq', res=10, limits=None, ctype='prob') values, errors = model.get_parameters() msg = 'Auto limits are not value +- 5 * stderr' vmin = values - 5*errors vmax = values + 5*errors self.assertAlmostEqual(min(x), vmin[0], places=4, msg=msg) self.assertAlmostEqual(max(x), vmax[0], places=4, msg=msg) self.assertAlmostEqual(min(y), vmin[1], places=4, msg=msg) self.assertAlmostEqual(max(y), vmax[1], places=4, msg=msg) msg = 'Grid values are not correctly distributed' exp1 = [1.4790, 1.4835, 1.4881, 1.4927, 1.4972, 1.5018, 1.5064, 1.5109, 1.5155, 1.5200] exp2 = [0.11937, 0.11960, 0.11983, 0.12006, 0.12028, 0.12051, 0.12074, 0.12097, 0.12120, 0.12143] self.assertArrayAlmostEqual(x, exp1, places=3, msg=msg) self.assertArrayAlmostEqual(y, exp2, places=4, msg=msg) msg = 'resolution of the grid is not correct' self.assertEqual(len(ci), 10, msg=msg) self.assertEqual(len(ci[0]), 10, msg=msg) msg = 'CI values are not correct' exp1 = [99.9996, 99.9572, 98.2854, 79.4333, 27.5112, 25.6265, 77.7912, 98.0525, 99.9489, 99.9995] exp2 = [99.9996, 99.9565, 98.2751, 79.4030, 27.5112, 25.6436, 77.8194, 98.0628, 99.9496, 99.9996] self.assertArrayAlmostEqual(ci[4], exp1, places=2, msg=msg) self.assertArrayAlmostEqual(ci[:,4], exp2, places=2, msg=msg)
def fitLP(filename=None,lprof=None,theory=0,show=0,cfg='',convert_ms_kms=0,\ vary_pars=['vexp'],i_vexp=15.0,i_gamma=1.0,do_gauss=0): ''' Fit a line profile with a soft parabola, and a Gaussian component if required. The method automatically checks if a second component is needed (eg an extra absorption component). An estimate of the expansion velocity (width of the profile) and an improved guess of the vlsr are given. A guess for the gas terminal velocity is returned, as well as its error and the fitted profile (sp/gaussian, and if applicable extra gaussian and the full fit). @keyword filename: The filename to the data file of the line profile. If None a line profile object is expected. (default: None) @type filename: string @keyword lprof: A line profile object (LPDataReader or inheriting classes) If None, a filename is expected! If not None, the results are saved in this object as well as returned upon method call (default: None) @type lprof: LPDataReader() @keyword convert_ms_kms: Convert velocity grid from m/s to km/s. (default: 0) @type convert_ms_kms: bool @keyword theory: If theoretical profile, and filename is given, set vlsr to 0 and read two columns. lprof not relevant if True. (default: 0) @type theory: bool @keyword vary_pars: The soft parabola parameters varied (can only be vexp or gamma for now). The initial values for parameters listed here are not used. If 'gamma' is requested, a reasonable guess for i_vexp when calling the method will improve the fitting results. This is done for the first guess only! If a Gaussian absorption is present improving these first guesses won't make much of a difference. However, the first guess value for gamma is still used. Vexp is always varied if absorption is present. (default: ['vexp']) @type vary_pars: list[string] @keyword i_vexp: The initial guess for the expansion velocity. Not relevant if vexp is included in vary_pars. (default: 15.0) @type i_vexp: float @keyword i_gamma: The initial guess for the gamma parameter of soft parab. Not relevant if gamma is included in vary_pars. (default: 1.0) @type i_gamma: float @keyword do_gauss: Force a Gaussian fit regardless of soft parabola fit results. Still does the soft parabola fit first to allow for comparison of parameters. (default: 0) @type do_gauss: bool @keyword show: Show the results of the fit (default: 0) @type show: bool @return: dictionary including [vexp,evexp,gamma,egamma,fitprof,gaussian,\ fullfit,dintint,fgintint] @rtype: dict[float,float,float,float,funclib.Function(),\ funclib.Function(),funclib.Function()] ''' print '*******************************************' if theory and filename <> None: d = DataIO.readCols(filename=filename) vel = d[0] flux = d[1] vlsr = 0.0 else: if filename is None: filename = lprof.filename print '** Fitting line profile in %s.' % filename if lprof is None: lprof = readLineProfile(filename) vel = lprof.getVelocity() flux = lprof.getFlux() vel = vel[-np.isnan(flux)] flux = flux[-np.isnan(flux)] vlsr = lprof.getVlsr() if convert_ms_kms: vel = vel / 1000. #-- Initial values: [peak tmb,vlsr,vexp,gamma] # For the central peak value, get a first guess from the data # Attempt multiple vexp values and return the best fitting case. # The initial values are given, with an arbitrary value for the vexp key i_mid = argmin(np.abs(vel - vlsr)) peak = mean(flux[i_mid - 2:i_mid + 3]) #-- Vary vexp or gamma if requested. If not requested i_vexp or i_gamma are # used. # Multiple values for gamma are tried and the best fitting model # is then chosen based on the relative error of the fitted gamma. if 'gamma' in vary_pars: igammas = array([-0.5, -0.1, 0.1, 0.5, 1.0, 2.0, 4.0]) firstguess = varyInitialFit(vel,flux,[peak,vlsr,i_vexp,0.0],index=3,\ values=igammas,vary_window=1,vary=[1,1,1,1],\ function=funclib.soft_parabola) i_gamma = firstguess.get_parameters()[0][3] #-- varyInitialFit adapts the velocity window itself. No more # assumptions needed for the expansion velocity ivexps = array([50., 40., 30., 25., 20., 15., 10.]) if 'vexp' in vary_pars: firstguess = varyInitialFit(vel,flux,[peak,vlsr,0.,i_gamma],index=2,\ values=ivexps,vary_window=1,vary=[1,1,1,1],\ function=funclib.soft_parabola) vexp = abs(firstguess.get_parameters()[0][2]) window = 2. print 'First guess fit, using a soft parabola:' print firstguess.param2str(accuracy=5) #-- If vexp > 100, replace with 50. This value is unrealistic, and might be # improved with an extra Gaussian. If not, it will be replaced with a # full Gaussian fit anyway if vexp > 100: vexp = 50. #-- Check whether irregularities are present in the profile. # Initial parameters for a gaussian are returned if true. # For this, a broad selection is made of the profile, to allow for a # decent noise determination outside the line profile keep = np.abs(vel - vlsr) <= (2 * window * vexp) velsel, fluxsel = vel[keep], flux[keep] include_gauss = checkLPShape(velsel, fluxsel, vlsr, vexp, window, show=show) #-- Do the fit of the line again, including an extra gaussian if # irregularities are present. if include_gauss <> None: #-- fit soft para model + gaussian # 1. Set up new soft parabola for several guesses of vexp ivexps = list(ivexps) initial = [peak, vlsr, 0., i_gamma] all_init = [[p] * len(ivexps) for i, p in enumerate(initial) if i != 2] all_init.insert(2, ivexps) functions = [funclib.soft_parabola() for i in ivexps] [ ff.setup_parameters(values=initi) for ff, initi in zip(functions, zip(*all_init)) ] # 2. setup gaussian gaussians = [funclib.gauss() for ff in functions] # initial guesses assuming an interstellar absorption line from the # checkLPShape method [ gg.setup_parameters(values=include_gauss, vary=[True, True, True, False]) for gg in gaussians ] # 3. combine soft para + gaussian, and minimize fit mymodels = [ fit.Model(functions=[ff, gg]) for ff, gg in zip(functions, gaussians) ] [fit.minimize(vel[np.abs(vel-vlsr)<=(init[2]*1.5)],\ flux[np.abs(vel-vlsr)<=(init[2]*1.5)],\ mymodel) for mymodel,init in zip(mymodels,zip(*all_init))] # 4. Select the best fitting result based on the error on vexp mymodels = [fg for fg in mymodels if fg.get_parameters()[1][2] != 0.] functions = [ ff for fg, ff in zip(mymodels, functions) if fg.get_parameters()[1][2] != 0. ] gaussians = [ gg for fg, gg in zip(mymodels, gaussians) if fg.get_parameters()[1][2] != 0. ] fitvalues = array([fg.get_parameters()[0][2] for fg in mymodels]) fiterrors = array([fg.get_parameters()[1][2] for fg in mymodels]) mymodel = mymodels[argmin(abs(fiterrors / fitvalues))] finalfit = functions[argmin(abs(fiterrors / fitvalues))] gaussian = gaussians[argmin(abs(fiterrors / fitvalues))] print 'Improved fit, including extra Gaussian:' print mymodel.param2str(accuracy=5) else: #-- if gamma is requested to be varied, allow another iteration on # gamma with the best vexp guess we already have. if 'gamma' in vary_pars: finalfit = varyInitialFit(vel,flux,[peak,vlsr,vexp,0.0],\ index=3,values=igammas,vary_window=1,\ function=funclib.soft_parabola,\ vary=[True,True,True,True]) print 'Final fit with soft parabola, second gamma iteration:' print finalfit.param2str(accuracy=5) #-- firstguess is best we can do at the moment else: finalfit = firstguess #-- If the relative error on vexp is larger than 30%, usually something # funky is going on in the emission line. Try a Gaussian instead. fvlsr = finalfit.get_parameters()[0][1] fevlsr = finalfit.get_parameters()[1][1] vexp = abs(finalfit.get_parameters()[0][2]) evexp = abs(finalfit.get_parameters()[1][2]) gamma = finalfit.get_parameters()[0][3] egamma = finalfit.get_parameters()[1][3] #-- Gamma has to be positive. If it isnt, dont bother with Gaussian # (double peaked line profile will not be fitted well with a Gaussian!) if (evexp/vexp > 0.40 and gamma > 0) or (evexp/vexp > 0.20 and vexp> 30.) \ or do_gauss: #-- Go back to default window to try a Gaussian fit #keep = np.abs(vel-vlsr)<=(80) #velselg,fluxselg = vel[keep],flux[keep] do_gauss = 1 include_gauss = None #-- FWHM is twice vexp! sigmas = 2 * ivexps / (2. * sqrt(2. * log(2.))) finalfit = varyInitialFit(vel,flux,[peak,vlsr,0.,0.],index=2,\ values=sigmas,function=funclib.gauss,\ vary_window=1,vary=[True,True,True,False]) vexp = abs( finalfit.get_parameters()[0][2]) * (2. * sqrt(2. * log(2.))) / 2. evexp = abs( finalfit.get_parameters()[1][2]) * (2. * sqrt(2. * log(2.))) / 2. fvlsr = finalfit.get_parameters()[0][1] fevlsr = finalfit.get_parameters()[1][1] gamma, egamma = None, None window = 3. print 'Improved fit, using a gaussian instead of soft parabola:' print finalfit.param2str(accuracy=5) #-- Compute numerical integrations. # After fitting, window for integration should be 0.6*window. vexp is # not expected to be too small anymore as in checkLPShape keep = np.abs(vel - vlsr) <= (0.6 * window * vexp) velsel = vel[keep] flux_first = firstguess.evaluate(velsel) flux_final = finalfit.evaluate(velsel) dimb = trapz(y=flux[keep], x=velsel) fi_final = trapz(y=flux_final, x=velsel) print('I_mb (emission line data): %f'\ %dimb) print('I_mb (SP -- initial guess): %f'\ %trapz(y=flux_first,x=velsel)) print('I_mb (SP -- final guess): %f'\ %fi_final) if include_gauss <> None: fitted_flux = mymodel.evaluate(velsel) print('I_mb (SP + Gauss fit): %f'\ %trapz(y=fitted_flux,x=velsel)) print('Final v_exp guess: %.4f +/- %.4f km/s' % (vexp, evexp)) if gamma <> None: print('Final gamma guess: %.4f +/- %.4f' % (gamma, egamma)) print('Final vlsr guess: %.4f +/- %.4f' % (fvlsr, fevlsr)) fwhm = getLPDataFWHM(lprof) print('The FWHM is %.2f km/s.' % (fwhm)) #-- plot if show or cfg: plt.clf() #-- improve velocity window for plotting keep = np.abs(vel - vlsr) <= (1.5 * window * vexp) velsel, fluxsel = vel[keep], flux[keep] vel_highres = np.linspace(velsel[0], velsel[-1], 10000) flux_final_highres = finalfit.evaluate(vel_highres) flux_first_highres = firstguess.evaluate(vel_highres) if include_gauss <> None: flux_full_highres = mymodel.evaluate(vel_highres) if show: plt.step(velsel,fluxsel,'-r',where='mid',lw=3,\ label='Observed profile') plt.plot(vel_highres,flux_first_highres,'b-',lw=3,\ label='First guess') plt.plot(vel_highres,flux_final_highres,'g--',lw=3,\ label='Improved guess') if include_gauss <> None: plt.plot(vel_highres,flux_full_highres,'g-',lw=2,\ label='Full fit (including Gaussian)') leg = plt.legend(loc='best', fancybox=True) leg.get_frame().set_alpha(0.5) plt.show() if cfg: pf = '%s_fitted_%s'%(do_gauss and 'gaussFit' or 'SPFit',\ os.path.split(filename)[1]) keytags = ['Observed profile', 'Improved guess'] line_types = [ '-r', '-b', ] x = [velsel, vel_highres] y = [fluxsel, flux_final_highres] if include_gauss <> None: line_types.append('g--') x.append(vel_highres) y.append(flux_full_highres) keytags.append('Full fit (including Gaussian)') pf = Plotting2.plotCols(x=x,y=y,filename=pf,cfg=cfg,linewidth=5,\ yaxis='$T_\mathrm{mb}\ (\mathrm{K})$',\ xaxis='$v (\mathrm{km}/\mathrm{s})$',\ keytags=keytags,line_types=line_types,\ histoplot=[0]) print 'Your figure can be found at %s .' % pf #-- Collecting all relevant results and returning. results = dict() #-- If a Gaussian was used for the main profile fit results['do_gauss'] = do_gauss #-- Fitted parameters and errors results['vlsr'] = fvlsr results['evlsr'] = fevlsr results['vexp'] = vexp results['evexp'] = evexp results['fwhm'] = fwhm #-- Gamma is None if no soft parabola was fitted results['gamma'] = gamma results['egamma'] = egamma #-- Integrated line strengths: main line fit, data themselves, fit window results['fgintint'] = fi_final results['dintint'] = dimb results['intwindow'] = window * 0.6 #-- Saving parameters for later evaluation. Full fit is accessible by # making the functions separately and setting pars, then using fit.Model results['fitprof'] = (do_gauss and 'gauss' or 'soft_parabola',\ list(finalfit.get_parameters()[0])) if include_gauss <> None: results['fitabs'] = ('gauss', list(gaussian.get_parameters()[0])) else: results['fitabs'] = None return results
def fitLP( filename=None, lprof=None, theory=0, show=0, cfg="", convert_ms_kms=0, vary_pars=["vexp"], i_vexp=15.0, i_gamma=1.0, do_gauss=0, ): """ Fit a line profile with a soft parabola, and a Gaussian component if required. The method automatically checks if a second component is needed (eg an extra absorption component). An estimate of the expansion velocity (width of the profile) and an improved guess of the vlsr are given. A guess for the gas terminal velocity is returned, as well as its error and the fitted profile (sp/gaussian, and if applicable extra gaussian and the full fit). @keyword filename: The filename to the data file of the line profile. If None a line profile object is expected. (default: None) @type filename: string @keyword lprof: A line profile object (LPDataReader or inheriting classes) If None, a filename is expected! If not None, the results are saved in this object as well as returned upon method call (default: None) @type lprof: LPDataReader() @keyword convert_ms_kms: Convert velocity grid from m/s to km/s. (default: 0) @type convert_ms_kms: bool @keyword theory: If theoretical profile, and filename is given, set vlsr to 0 and read two columns. lprof not relevant if True. (default: 0) @type theory: bool @keyword vary_pars: The soft parabola parameters varied (can only be vexp or gamma for now). The initial values for parameters listed here are not used. If 'gamma' is requested, a reasonable guess for i_vexp when calling the method will improve the fitting results. This is done for the first guess only! If a Gaussian absorption is present improving these first guesses won't make much of a difference. However, the first guess value for gamma is still used. Vexp is always varied if absorption is present. (default: ['vexp']) @type vary_pars: list[string] @keyword i_vexp: The initial guess for the expansion velocity. Not relevant if vexp is included in vary_pars. (default: 15.0) @type i_vexp: float @keyword i_gamma: The initial guess for the gamma parameter of soft parab. Not relevant if gamma is included in vary_pars. (default: 1.0) @type i_gamma: float @keyword do_gauss: Force a Gaussian fit regardless of soft parabola fit results. Still does the soft parabola fit first to allow for comparison of parameters. (default: 0) @type do_gauss: bool @keyword show: Show the results of the fit (default: 0) @type show: bool @return: dictionary including [vexp,evexp,gamma,egamma,fitprof,gaussian,\ fullfit,dintint,fgintint] @rtype: dict[float,float,float,float,funclib.Function(),\ funclib.Function(),funclib.Function()] """ print "*******************************************" if theory and filename <> None: d = DataIO.readCols(filename=filename) vel = d[0] flux = d[1] vlsr = 0.0 else: if filename is None: filename = lprof.filename print "** Fitting line profile in %s." % filename if lprof is None: lprof = readLineProfile(filename) vel = lprof.getVelocity() flux = lprof.getFlux() vel = vel[-np.isnan(flux)] flux = flux[-np.isnan(flux)] vlsr = lprof.getVlsr() if convert_ms_kms: vel = vel / 1000.0 # -- Initial values: [peak tmb,vlsr,vexp,gamma] # For the central peak value, get a first guess from the data # Attempt multiple vexp values and return the best fitting case. # The initial values are given, with an arbitrary value for the vexp key i_mid = argmin(np.abs(vel - vlsr)) peak = mean(flux[i_mid - 2 : i_mid + 3]) # -- Vary vexp or gamma if requested. If not requested i_vexp or i_gamma are # used. # Multiple values for gamma are tried and the best fitting model # is then chosen based on the relative error of the fitted gamma. if "gamma" in vary_pars: igammas = array([-0.5, -0.1, 0.1, 0.5, 1.0, 2.0, 4.0]) firstguess = varyInitialFit( vel, flux, [peak, vlsr, i_vexp, 0.0], index=3, values=igammas, vary_window=1, vary=[1, 1, 1, 1], function=funclib.soft_parabola, ) i_gamma = firstguess.get_parameters()[0][3] # -- varyInitialFit adapts the velocity window itself. No more # assumptions needed for the expansion velocity ivexps = array([50.0, 40.0, 30.0, 25.0, 20.0, 15.0, 10.0]) if "vexp" in vary_pars: firstguess = varyInitialFit( vel, flux, [peak, vlsr, 0.0, i_gamma], index=2, values=ivexps, vary_window=1, vary=[1, 1, 1, 1], function=funclib.soft_parabola, ) vexp = abs(firstguess.get_parameters()[0][2]) window = 2.0 print "First guess fit, using a soft parabola:" print firstguess.param2str(accuracy=5) # -- If vexp > 100, replace with 50. This value is unrealistic, and might be # improved with an extra Gaussian. If not, it will be replaced with a # full Gaussian fit anyway if vexp > 100: vexp = 50.0 # -- Check whether irregularities are present in the profile. # Initial parameters for a gaussian are returned if true. # For this, a broad selection is made of the profile, to allow for a # decent noise determination outside the line profile keep = np.abs(vel - vlsr) <= (2 * window * vexp) velsel, fluxsel = vel[keep], flux[keep] include_gauss = checkLPShape(velsel, fluxsel, vlsr, vexp, window, show=show) # -- Do the fit of the line again, including an extra gaussian if # irregularities are present. if include_gauss <> None: # -- fit soft para model + gaussian # 1. Set up new soft parabola for several guesses of vexp ivexps = list(ivexps) initial = [peak, vlsr, 0.0, i_gamma] all_init = [[p] * len(ivexps) for i, p in enumerate(initial) if i != 2] all_init.insert(2, ivexps) functions = [funclib.soft_parabola() for i in ivexps] [ff.setup_parameters(values=initi) for ff, initi in zip(functions, zip(*all_init))] # 2. setup gaussian gaussians = [funclib.gauss() for ff in functions] # initial guesses assuming an interstellar absorption line from the # checkLPShape method [gg.setup_parameters(values=include_gauss, vary=[True, True, True, False]) for gg in gaussians] # 3. combine soft para + gaussian, and minimize fit mymodels = [fit.Model(functions=[ff, gg]) for ff, gg in zip(functions, gaussians)] [ fit.minimize( vel[np.abs(vel - vlsr) <= (init[2] * 1.5)], flux[np.abs(vel - vlsr) <= (init[2] * 1.5)], mymodel ) for mymodel, init in zip(mymodels, zip(*all_init)) ] # 4. Select the best fitting result based on the error on vexp mymodels = [fg for fg in mymodels if fg.get_parameters()[1][2] != 0.0] functions = [ff for fg, ff in zip(mymodels, functions) if fg.get_parameters()[1][2] != 0.0] gaussians = [gg for fg, gg in zip(mymodels, gaussians) if fg.get_parameters()[1][2] != 0.0] fitvalues = array([fg.get_parameters()[0][2] for fg in mymodels]) fiterrors = array([fg.get_parameters()[1][2] for fg in mymodels]) mymodel = mymodels[argmin(abs(fiterrors / fitvalues))] finalfit = functions[argmin(abs(fiterrors / fitvalues))] gaussian = gaussians[argmin(abs(fiterrors / fitvalues))] print "Improved fit, including extra Gaussian:" print mymodel.param2str(accuracy=5) else: # -- if gamma is requested to be varied, allow another iteration on # gamma with the best vexp guess we already have. if "gamma" in vary_pars: finalfit = varyInitialFit( vel, flux, [peak, vlsr, vexp, 0.0], index=3, values=igammas, vary_window=1, function=funclib.soft_parabola, vary=[True, True, True, True], ) print "Final fit with soft parabola, second gamma iteration:" print finalfit.param2str(accuracy=5) # -- firstguess is best we can do at the moment else: finalfit = firstguess # -- If the relative error on vexp is larger than 30%, usually something # funky is going on in the emission line. Try a Gaussian instead. fvlsr = finalfit.get_parameters()[0][1] fevlsr = finalfit.get_parameters()[1][1] vexp = abs(finalfit.get_parameters()[0][2]) evexp = abs(finalfit.get_parameters()[1][2]) gamma = finalfit.get_parameters()[0][3] egamma = finalfit.get_parameters()[1][3] # -- Gamma has to be positive. If it isnt, dont bother with Gaussian # (double peaked line profile will not be fitted well with a Gaussian!) if (evexp / vexp > 0.40 and gamma > 0) or (evexp / vexp > 0.20 and vexp > 30.0) or do_gauss: # -- Go back to default window to try a Gaussian fit # keep = np.abs(vel-vlsr)<=(80) # velselg,fluxselg = vel[keep],flux[keep] do_gauss = 1 include_gauss = None # -- FWHM is twice vexp! sigmas = 2 * ivexps / (2.0 * sqrt(2.0 * log(2.0))) finalfit = varyInitialFit( vel, flux, [peak, vlsr, 0.0, 0.0], index=2, values=sigmas, function=funclib.gauss, vary_window=1, vary=[True, True, True, False], ) vexp = abs(finalfit.get_parameters()[0][2]) * (2.0 * sqrt(2.0 * log(2.0))) / 2.0 evexp = abs(finalfit.get_parameters()[1][2]) * (2.0 * sqrt(2.0 * log(2.0))) / 2.0 fvlsr = finalfit.get_parameters()[0][1] fevlsr = finalfit.get_parameters()[1][1] gamma, egamma = None, None window = 3.0 print "Improved fit, using a gaussian instead of soft parabola:" print finalfit.param2str(accuracy=5) # -- Compute numerical integrations. # After fitting, window for integration should be 0.6*window. vexp is # not expected to be too small anymore as in checkLPShape keep = np.abs(vel - vlsr) <= (0.6 * window * vexp) velsel = vel[keep] flux_first = firstguess.evaluate(velsel) flux_final = finalfit.evaluate(velsel) dimb = trapz(y=flux[keep], x=velsel) fi_final = trapz(y=flux_final, x=velsel) print ("I_mb (emission line data): %f" % dimb) print ("I_mb (SP -- initial guess): %f" % trapz(y=flux_first, x=velsel)) print ("I_mb (SP -- final guess): %f" % fi_final) if include_gauss <> None: fitted_flux = mymodel.evaluate(velsel) print ("I_mb (SP + Gauss fit): %f" % trapz(y=fitted_flux, x=velsel)) print ("Final v_exp guess: %.4f +/- %.4f km/s" % (vexp, evexp)) if gamma <> None: print ("Final gamma guess: %.4f +/- %.4f" % (gamma, egamma)) print ("Final vlsr guess: %.4f +/- %.4f" % (fvlsr, fevlsr)) fwhm = getLPDataFWHM(lprof) print ("The FWHM is %.2f km/s." % (fwhm)) # -- plot if show or cfg: plt.clf() # -- improve velocity window for plotting keep = np.abs(vel - vlsr) <= (1.5 * window * vexp) velsel, fluxsel = vel[keep], flux[keep] vel_highres = np.linspace(velsel[0], velsel[-1], 10000) flux_final_highres = finalfit.evaluate(vel_highres) flux_first_highres = firstguess.evaluate(vel_highres) if include_gauss <> None: flux_full_highres = mymodel.evaluate(vel_highres) if show: plt.step(velsel, fluxsel, "-r", where="mid", lw=3, label="Observed profile") plt.plot(vel_highres, flux_first_highres, "b-", lw=3, label="First guess") plt.plot(vel_highres, flux_final_highres, "g--", lw=3, label="Improved guess") if include_gauss <> None: plt.plot(vel_highres, flux_full_highres, "g-", lw=2, label="Full fit (including Gaussian)") leg = plt.legend(loc="best", fancybox=True) leg.get_frame().set_alpha(0.5) plt.show() if cfg: pf = "%s_fitted_%s" % (do_gauss and "gaussFit" or "SPFit", os.path.split(filename)[1]) keytags = ["Observed profile", "Improved guess"] line_types = ["-r", "-b"] x = [velsel, vel_highres] y = [fluxsel, flux_final_highres] if include_gauss <> None: line_types.append("g--") x.append(vel_highres) y.append(flux_full_highres) keytags.append("Full fit (including Gaussian)") pf = Plotting2.plotCols( x=x, y=y, filename=pf, cfg=cfg, linewidth=5, yaxis="$T_\mathrm{mb}\ (\mathrm{K})$", xaxis="$v (\mathrm{km}/\mathrm{s})$", keytags=keytags, line_types=line_types, histoplot=[0], ) print "Your figure can be found at %s ." % pf # -- Collecting all relevant results and returning. results = dict() # -- If a Gaussian was used for the main profile fit results["do_gauss"] = do_gauss # -- Fitted parameters and errors results["vlsr"] = fvlsr results["evlsr"] = fevlsr results["vexp"] = vexp results["evexp"] = evexp results["fwhm"] = fwhm # -- Gamma is None if no soft parabola was fitted results["gamma"] = gamma results["egamma"] = egamma # -- Integrated line strengths: main line fit, data themselves, fit window results["fgintint"] = fi_final results["dintint"] = dimb results["intwindow"] = window * 0.6 # -- Saving parameters for later evaluation. Full fit is accessible by # making the functions separately and setting pars, then using fit.Model results["fitprof"] = (do_gauss and "gauss" or "soft_parabola", list(finalfit.get_parameters()[0])) if include_gauss <> None: results["fitabs"] = ("gauss", list(gaussian.get_parameters()[0])) else: results["fitabs"] = None return results