def _initialize(self, phot_fmt, time=None, brightness=None, err_brightness=None, coords=None): """ Internal function to import photometric data into the correct form using a few numpy ndarrays. Parameters: phot_fmt - Specifies type of photometry. Either 'flux' or 'mag'. time - Date vector of the data brightness - vector of the photometric measurements err_brightness - vector of the errors in the phot measurements. coords - Sky coordinates of the event, optional """ if self._init_keys['add245'] and self._init_keys['add246']: raise ValueError('You cannot initialize MulensData with both ' + 'add_2450000 and add_2460000 being True') if time.dtype != np.float64: raise TypeError(('time vector in MulensData() must be of ' + 'numpy.float64 type, not {:}').format(time.dtype)) # Adjust the time vector as necessary. if self._init_keys['add245']: time += 2450000. elif self._init_keys['add246']: time += 2460000. # Store the time vector self._time = time self._n_epochs = len(time) # Check that the number of epochs equals the number of observations if ((len(brightness) != self._n_epochs) or (len(err_brightness) != self._n_epochs)): raise ValueError('input data in MulesData have different lengths') # Store the photometry self._brightness_input = brightness self._brightness_input_err = err_brightness self._input_fmt = phot_fmt # Create the complementary photometry (mag --> flux, flux --> mag) if phot_fmt == "mag": self._mag = self._brightness_input self._err_mag = self._brightness_input_err (self._flux, self._err_flux) = Utils.get_flux_and_err_from_mag( mag=self.mag, err_mag=self.err_mag) elif phot_fmt == "flux": self._flux = self._brightness_input self._err_flux = self._brightness_input_err self._mag = None self._err_mag = None else: msg = 'unknown brightness format in MulensData' raise ValueError(msg)
def get_chi2_format(self, data): """ Microlensing model in the format used for chi^2 calculation. The output is in flux space in most cases, but can be in magnitudes depending on dataset format. Parameters : data: :py:class:`~MulensModel.mulensdata.MulensData` A dataset for which model will be returned. Returns : model: *np.ndarray* Microlensing model in flux units or magnitudes (depending on the settings of input data). """ if data not in self._flux_blending: self._flux_blending[data] = 0. warnings.warn("Blending flux not set. This is strange...", SyntaxWarning) if data.chi2_fmt == "mag": result = Utils.get_mag_from_flux(self.get_flux(data)) elif data.chi2_fmt == "flux": result = self.get_flux(data) else: msg = 'Fit.get_chi2_format() unrecognized data input format' raise ValueError(msg) return result
def set_satellite_data(self, theta): """set satellite dataset magnitudes and fluxes""" self._run_cpm(theta) n_0 = self.n_datasets - self.n_sat for i in range(self.n_sat): ii = n_0 + i # OLD: sat_residuals = self.cpm_sources[i].residuals[self._sat_masks[i]] flux = self._sat_models[i][self._sat_masks[i]] + sat_residuals # NEW - IF ACCEPTED!!! : # sat_residuals = self.cpm_sources[i].residuals[ # self.cpm_sources[i].residuals_mask] # flux = self._sat_models[i][self.cpm_sources[i].residuals_mask] # flux += sat_residuals # # Below we use private properties from MulensModel - this # should be corrected (maybe make new MulensData object and # delete the old one). self.event.datasets[ii]._flux = flux mag_and_err = Utils.get_mag_and_err_from_flux( flux, self.event.datasets[ii].err_flux, zeropoint=K2_MAG_ZEROPOINT) self.event.datasets[ii]._mag = mag_and_err[0] self.event.datasets[ii]._err_mag = mag_and_err[1]
def get_input_format(self, data=None): """ Microlensing model in the same format as given dataset. The output is either in flux units or magnitudes, depending on format of the input data. Parameters : data: :py:class:`~MulensModel.mulensdata.MulensData` A dataset for which model will be returned. Returns : model: *np.ndarray* Microlensing model in flux units or magnitudes (depending on the format of input data). """ if data is None: raise ValueError('Fit.get_input_format() dataset not provided') if data not in self._flux_blending: self._flux_blending[data] = 0. warnings.warn("Blending flux not set. This is strange...", SyntaxWarning) # Return the model flux in either flux or magnitudes # (depending on data) if data.input_fmt == "mag": result = Utils.get_mag_from_flux(self.get_flux(data)) elif data.input_fmt == "flux": result = self.get_flux(data) else: msg = 'Fit.get_input_format() unrecognized data input format' raise ValueError(msg) return result
def _read_horizons_file(self): """ reads standard output from JPL Horizons """ # Read in the file self._get_start_end() data = np.genfromtxt(self._file_properties['file_name'], dtype=[('date', 'S17'), ('ra_dec', 'S23'), ('distance', 'f8'), ('foo', 'S23')], delimiter=[18, 29, 17, 24], autostrip=True, skip_header=self._file_properties['start_ind'] + 1, skip_footer=(self._file_properties['line_count'] - self._file_properties['stop_ind'])) # Fix time format for (i, date) in enumerate(data['date']): data['date'][i] = Utils.date_change(date) # Currently we assume HORIZONS works in UTC. dates = [text.decode('UTF-8') for text in data['date']] self._time = Time(dates, format='iso', scale='utc').tdb.jd ra_dec = [text.decode('UTF-8') for text in data['ra_dec']] xyz = SkyCoord(ra_dec, distance=data['distance'], unit=(u.hourangle, u.deg, u.au)) self._xyz = xyz.cartesian
def _get_y_value_y_err(self, phot_fmt, flux, flux_err): """ just calculate magnitudes if needed, or return input otherwise """ if phot_fmt == 'mag': return Utils.get_mag_and_err_from_flux(flux, flux_err) else: return (flux, flux_err)
def __init__(self, s, q, n_points=10000): self._s = s self._q = q self._n_points = n_points self._n_caustics = Utils.get_n_caustics(s=self.s, q=self.q) self._get_phi() self._integrate() self._find_inflections_and_correct()
def mag(self): """ *np.ndarray* magnitude vector """ if self._mag is None: (self._mag, self._err_mag) = Utils.get_mag_and_err_from_flux( flux=self.flux, err_flux=self.err_flux) return self._mag
def flux(self): """ *numpy.ndarray* Vector of the measured brightness in flux units. """ if self._flux is None: (self._flux, self._err_flux) = Utils.get_flux_and_err_from_mag( mag=self.mag, err_mag=self.err_mag) return self._flux
def _calculate_projected(self): """ Calculate North and East directions projected on the plane of the sky. """ direction = np.array(self.cartesian.xyz.value) if direction.shape == (3, 1): direction = direction[:, 0] north = np.array([0., 0., 1.]) self._east_projected = Utils.vector_product_normalized( north, direction) self._north_projected = np.cross(direction, self._east_projected)
def set_limb_coeff_u(self, bandpass, u): """ Remembers limb darkening *u* coefficient for given band Parameters : bandpass: *str* Name of the filter. u: *float* The value of *u* coefficient. """ self._gammas_for_band[bandpass] = Utils.u_to_gamma(u)
def _magnitude_to_sat_flux(self, magnitude): """ translates magnitude in reference frame (OGLE I-band in most cases) to satellite flux scale """ if self._sat_blending_flux != 0.: warnings.warn( "self._sat_blending_flux is not 0. - not sure if this works") flux = Utils.get_flux_from_mag(magnitude) (fs, fb) = self.event.model.get_ref_fluxes() magnification = (flux - fb) / fs[0] flux_sat = self._magnification_to_sat_flux(magnification) return flux_sat
def get_limb_coeff_u(self, bandpass): """ Gives limb darkening *u* coefficient for given band. Parameters : bandpass: *str* Name of the filter. Returns : u: *float* The value of *u* coefficient. """ gamma = self.get_limb_coeff_gamma(bandpass=bandpass) return Utils.gamma_to_u(gamma)
def plot_sat_magnitudes(self, **kwargs): """Plot satellite data in reference magnitude system""" raise NotImplementedError("plot_sat_magnitudes") data_ref = self.event.model.data_ref (fs, fb) = self.event.model.get_ref_fluxes() n = self.n_datasets - self.n_sat for i in range(self.n_sat): times = self._sat_times[i] - 2450000. (fs_sat, fb_sat) = self.event.model.get_ref_fluxes(n+i) mags = self._sat_magnifications[i] flux = (mags * self._sat_flux - fb_sat) * (fs[0] / fs_sat[0]) + fb # _sat_flux is not defined anymore plt.plot(times, Utils.get_mag_from_flux(flux), zorder=np.inf, # We want the satellite models to be at the very top. **kwargs) self.event.model.data_ref = data_ref
def get_model_magnitudes(self, **kwargs): """ Calculate model in magnitude space Parameters : ``**kwargs``: see :py:func:`get_model_fluxes()` Returns : model_mag: *np.ndarray* The model magnitude evaluated for each datapoint. """ model_flux = self.get_model_fluxes(**kwargs) model_mag = Utils.get_mag_from_flux(model_flux) return model_mag
def _calculate(self, n_points=5000): """ Solve the caustics polynomial to calculate the critical curve and caustic structure. Based on Eq. 6 Cassan 2008 modified so origin is center of mass and larger mass is on the left. Uses complex coordinates. """ # Find number of angles so that 4*n_angles is the multiple of 4 that # is closest to n_points. n_angles = int(n_points / 4. + .5) # Initialize variables self._x = [] self._y = [] self._critical_curve = self.CriticalCurve() # Distance between primary mass and center of mass xcm_offset = self.q * self.s / (1. + self.q) # Solve for the critical curve (and caustic) in complex coordinates. for phi in np.linspace(0., 2. * np.pi, n_angles, endpoint=False): # Change the angle to a complex number e_iphi = self.shear_G.conjugate() + ( 1 - self.convergence_K) * complex(cos(phi), -sin(phi)) # Coefficients of Eq. 6 coeff_4 = 1. coeff_3 = -2. * self.s coeff_2 = Utils.complex_fsum([self.s**2, -1 / e_iphi]) coeff_1 = 1. / e_iphi * (2. * self.s / (1. + self.q)) # The # additional parenthesis make it more stable numerically. coeff_0 = -self.s**2 * 1 / e_iphi * 1 / (1. + self.q) # Find roots coeff_list = [coeff_0, coeff_1, coeff_2, coeff_3, coeff_4] roots = np.polynomial.polynomial.polyroots(coeff_list) # Store results shift = -xcm_offset + self.convergence_K + self.shear_G.real for root in roots: self._critical_curve.x.append(root.real + shift) self._critical_curve.y.append(root.imag) source_plane_position = self._solve_lens_equation(root) self._x.append(source_plane_position.real + shift) self._y.append(source_plane_position.imag + self.shear_G.imag)
def plot_sat_magnitudes(self, **kwargs): """ Plot satellite model in reference magnitude system """ if not self._MM: raise NotImplementedError('not yet coded in pixel_lensing') data_ref = self.event.model.data_ref (fs, fb) = self.event.model.get_ref_fluxes() n = self.n_datasets - self.n_sat if 'zorder' not in kwargs: kwargs['zorder'] = np.inf for i in range(self.n_sat): times = self._sat_times[i] - 2450000. flux = self._sat_magnifications[i] * fs[0] + fb plt.plot(times, Utils.get_mag_from_flux(flux), **kwargs) self.event.model.data_ref = data_ref
def v_Earth_projected(self, full_BJD): """ Earth velocity at *full_BJD* projected on the plane of sky towards given coordinates. Parameters : full_BJD: *float* Epoch for which projected velocity is requested. In most cases it is :py:attr:`~MulensModel.modelparameters.ModelParameters.t_0_par` Returns : v_Earth_perp_N: *float* North component of Earth's projected velocity in km/s. v_Earth_perp_E: *float* East component of Earth's projected velocity in km/s. """ velocity = Utils.velocity_of_Earth(full_BJD) v_Earth_perp_N = np.dot(velocity, self.north_projected) v_Earth_perp_E = np.dot(velocity, self.east_projected) return (v_Earth_perp_N, v_Earth_perp_E)
def standard_plot(self, t_start, t_stop, ylim, title=None, label_list=None, color_list=None, line_width=1.5, legend_order=None, separate_residuals=False, model_line_width=4., legend_kwargs=None, fluxes_y_axis=None, ground_model_zorder=None, sat_model_zorder=None): """ Make plot of the event and residuals. Parameters : XXX ylim: [*float*, *float*] A list o two values that are used to set y axis limits (in mag). fluxes_y_axis: *list* or *np.ndarray* of *floats* K2 fluxes which will be marked on right side of Y axis. ground_model_zorder: *float* Passed to pyplot to control if ground model is plotted at the top or bottom. sat_model_zorder: *float* Passed to pyplot to control if satellite model is plotted at the top or bottom. Defualts to *np.inf*. """ if not self._MM: raise NotImplementedError('not yet coded in pixel_lensing') if (label_list is None) != (color_list is None): raise ValueError('wrong input in standard_plot') if not separate_residuals: grid_spec = gridspec.GridSpec(2, 1, height_ratios=[5, 1], hspace=0.12) else: grid_spec = gridspec.GridSpec(3, 1, height_ratios=[5, 1, 1], hspace=0.13) plt.figure() plt.subplot(grid_spec[0]) if title is not None: plt.title(title) alphas = [0.5] * self.n_datasets for i in range(self.n_sat): alphas[-(i + 1)] = 1. self.event.plot_model(color='black', subtract_2450000=True, t_start=t_start + 2450000., t_stop=t_stop + 2450000., label="ground-based model", lw=model_line_width, zorder=ground_model_zorder) self.plot_sat_magnitudes(color='orange', lw=2, label="K2 model", zorder=sat_model_zorder) if color_list is not None: color_list_ = color_list else: if self.n_sat == 0: color_list_ = None else: color_list_ = ['black'] * (self.n_datasets - self.n_sat) color_list_ += ['red'] * self.n_sat zorder_list = np.arange(self.n_datasets, 0, -1) zorder_list[1] = self.n_datasets + 1 self.event.plot_data( # alpha_list=alphas, zorder_list=zorder_list, mfc='none', lw=line_width, mew=line_width, marker='o', markersize=6, subtract_2450000=True, color_list=color_list_, label_list=label_list) if ylim is not None: plt.ylim(ylim[0], ylim[1]) else: ylim = plt.ylim() plt.xlim(t_start, t_stop) plt.gca().tick_params(top=True, direction='in') if legend_kwargs is None: legend_kwargs = dict() self._legend_standard_plot(legend_order, legend_kwargs, color_list, label_list, alphas) y_K2_max = self._magnitude_to_sat_flux(np.min(ylim)) y_K2_min = self._magnitude_to_sat_flux(np.max(ylim)) print("Y-axis limits:") print(" mag: {:.3f} {:.3f}".format(*ylim)) print(" K2 flux: {:.2f} {:.2f}".format(y_K2_min, y_K2_max)) if fluxes_y_axis is not None: y_color = 'red' y_label = r'K2 differential counts [e$^-$s$^{-1}$]' min_ = np.min(fluxes_y_axis) if min_ < y_K2_min or np.max(fluxes_y_axis) > y_K2_max: raise ValueError('ylim incompatible with fluxes_y_axis') (fs, fb) = self.event.model.get_ref_fluxes() mags = self._sat_flux_to_magnification(np.array(fluxes_y_axis)) mags_fluxes = Utils.get_mag_from_flux(fb + fs[0] * mags) ax2 = plt.gca().twinx() ax2.set_ylabel(y_label).set_color(y_color) ax2.spines['right'].set_color(y_color) ax2.set_ylim(ylim[0], ylim[1]) ax2.tick_params(axis='y', colors=y_color) plt.yticks(mags_fluxes.tolist(), fluxes_y_axis, color=y_color) plt.subplot(grid_spec[1]) kwargs_ = dict(mfc='none', lw=line_width, mew=line_width) if not separate_residuals: self.event.plot_residuals(subtract_2450000=True, **kwargs_) plt.xlim(t_start, t_stop) else: plt.plot([0., 3000000.], [0., 0.], color='black') self.event.datasets[-1].plot(phot_fmt='mag', show_errorbars=True, subtract_2450000=True, model=self.event.model, plot_residuals=True, **kwargs_) plt.ylim(0.29, -0.29) # XXX plt.ylabel('K2 residuals') plt.xlim(t_start, t_stop) plt.gca().tick_params(top=True, direction='in') plt.gca().tick_params(right=True, direction='in') plt.subplot(grid_spec[2]) plt.plot([0., 3000000.], [0., 0.], color='black') for data in self.event.datasets[:-1]: data.plot(phot_fmt='mag', show_errorbars=True, subtract_2450000=True, model=self.event.model, plot_residuals=True, **kwargs_) plt.ylabel('Residuals') plt.xlim(t_start, t_stop) plt.gca().tick_params(top=True, direction='in') plt.gca().tick_params(right=True, direction='in')
def get_residuals( self, phot_fmt=None, source_flux=None, blend_flux=None, bad=False, type=None): """ Calculate the residuals for each datapoint relative to the model. Parameters : phot_fmt: *str*, optional specify whether the residuals should be returned in magnitudes ('mag') or in flux ('flux'). Default is 'mag'. If 'scaled', will return the residuals in magnitudes scaled to source_flux, blend_flux. source_flux, blend_flux: *float* reference source and blend fluxes for scaling the residuals bad: *bool* Default is *False*. If *True* recalculates the data magnification for each point to ensure that there are values even for bad datapoints. type: DEPRECATED, see "phot_fmt" above. Returns : residuals: *np.ndarray* the residuals for the corresponding dataset. errorbars: *np.ndarray* the scaled errorbars for each point. For plotting errorbars for the residuals. """ if type is not None: if type == 'mag': warnings.warn( '"mag" returns residuals in the original data flux' + 'system. To scale the residuals, use "scaled".') warnings.warn( 'type keyword will be deprecated. Use "phot_fmt" instead.', FutureWarning) phot_fmt = type if bad: self._calculate_magnifications(bad=True) if phot_fmt == 'mag': residuals = self._dataset.mag - self.get_model_magnitudes() errorbars = self._dataset.err_mag elif phot_fmt == 'flux': residuals = self._dataset.flux - self.get_model_fluxes() errorbars = self._dataset.err_flux elif phot_fmt == 'scaled': if source_flux is None or blend_flux is None: raise ValueError( 'If phot_fmt=scaled, source_flux and blend_flux must ' + 'also be specified.') magnification = self._data_magnification if self._model.n_sources == 1: model_flux = source_flux * magnification else: model_flux = source_flux[0] * magnification[0] model_flux += source_flux[1] * magnification[1] model_flux += blend_flux model_mag = Utils.get_mag_from_flux(model_flux) (flux, err_flux) = self.scale_fluxes(source_flux, blend_flux) (mag, errorbars) = Utils.get_mag_and_err_from_flux(flux, err_flux) residuals = mag - model_mag else: raise ValueError( 'phot_fmt must be one of "mag", "flux", or "scaled". Your ' + 'value: {0}'.format(phot_fmt)) return (residuals, errorbars)
def chi2_gradient(self, parameters, fit_blending=None): """ Calculate chi^2 gradient (also called Jacobian), i.e., :math:`d chi^2/d parameter`. Parameters : parameters: *str* or *list*, required Parameters with respect to which gradient is calculated. Currently accepted parameters are: ``t_0``, ``u_0``, ``t_eff``, ``t_E``, ``pi_E_N``, and ``pi_E_E``. The parameters for which you request gradient must be defined in py:attr:`~model`. fit_blending: *boolean*, optional Are we fitting for blending flux? If not then blending flux is fixed to 0. Default is the same as :py:func:`MulensModel.fit.Fit.fit_fluxes()`. Returns : gradient: *float* or *np.ndarray* chi^2 gradient """ if not isinstance(parameters, list): parameters = [parameters] implemented = {'t_0', 't_E', 'u_0', 't_eff', 'pi_E_N', 'pi_E_E'} if len(set(parameters) - implemented) > 0: raise NotImplementedError(( "chi^2 gradient is implemented only for {:}\nCannot work " + "with {:}").format(implemented, parameters)) gradient = {param: 0 for param in parameters} if self.model.n_sources != 1: raise NotImplementedError("Sorry, chi2 for binary sources is " + "not implemented yet") if self.model.n_lenses != 1: raise NotImplementedError( 'Event.chi2_gradient() works only ' + 'single lens models currently') as_dict = self.model.parameters.as_dict() if 'rho' in as_dict or 't_star' in as_dict: raise NotImplementedError( 'Event.chi2_gradient() is not working ' + 'for finite source models yet') # Define a Fit given the model and perform linear fit for fs and fb self._update_data_in_model() self.fit = Fit( data=self.datasets, magnification=self.model.data_magnification) # For binary source cases, the above line would need to be replaced, # so that it uses self.model.fit. if fit_blending is not None: self.fit.fit_fluxes(fit_blending=fit_blending) else: self.fit.fit_fluxes() for (i, dataset) in enumerate(self.datasets): (data, err_data) = dataset.data_and_err_in_chi2_fmt() factor = data - self.fit.get_chi2_format(data=dataset) factor *= -2. / err_data**2 if dataset.chi2_fmt == 'mag': factor *= -2.5 / (log(10.) * Utils.get_flux_from_mag(data)) factor *= self.fit.flux_of_sources(dataset)[0] kwargs = {} if dataset.ephemerides_file is not None: kwargs['satellite_skycoord'] = dataset.satellite_skycoord trajectory = Trajectory( dataset.time, self.model.parameters, self.model.get_parallax(), self.coords, **kwargs) u_2 = trajectory.x**2 + trajectory.y**2 u_ = np.sqrt(u_2) d_A_d_u = -8. / (u_2 * (u_2 + 4) * np.sqrt(u_2 + 4)) factor *= d_A_d_u factor_d_x_d_u = (factor * trajectory.x / u_)[dataset.good] sum_d_x_d_u = np.sum(factor_d_x_d_u) factor_d_y_d_u = (factor * trajectory.y / u_)[dataset.good] sum_d_y_d_u = np.sum(factor_d_y_d_u) dt = dataset.time[dataset.good] - as_dict['t_0'] # Exactly 2 out of (u_0, t_E, t_eff) must be defined and # gradient depends on which ones are defined. if 't_eff' not in as_dict: t_E = as_dict['t_E'].to(u.day).value if 't_0' in parameters: gradient['t_0'] += -sum_d_x_d_u / t_E if 'u_0' in parameters: gradient['u_0'] += sum_d_y_d_u if 't_E' in parameters: gradient['t_E'] += np.sum(factor_d_x_d_u * -dt / t_E**2) elif 't_E' not in as_dict: t_eff = as_dict['t_eff'].to(u.day).value if 't_0' in parameters: gradient['t_0'] += -sum_d_x_d_u * as_dict['u_0'] / t_eff if 'u_0' in parameters: gradient['u_0'] += sum_d_y_d_u + np.sum( factor_d_x_d_u * dt / t_eff) if 't_eff' in parameters: gradient['t_eff'] += np.sum( factor_d_x_d_u * -dt * as_dict['u_0'] / t_eff**2) elif 'u_0' not in as_dict: t_E = as_dict['t_E'].to(u.day).value t_eff = as_dict['t_eff'].to(u.day).value if 't_0' in parameters: gradient['t_0'] += -sum_d_x_d_u / t_E if 't_E' in parameters: gradient['t_E'] += ( np.sum(factor_d_x_d_u * dt) - sum_d_y_d_u * t_eff) / t_E**2 if 't_eff' in parameters: gradient['t_eff'] += sum_d_y_d_u / t_E else: raise KeyError( 'Something is wrong with ModelParameters in ' + 'Event.chi2_gradient():\n', as_dict) # Below we deal with parallax only. if 'pi_E_N' in parameters or 'pi_E_E' in parameters: parallax = { 'earth_orbital': False, 'satellite': False, 'topocentric': False} trajectory_no_piE = Trajectory( dataset.time, self.model.parameters, parallax, self.coords, **kwargs) dx = (trajectory.x - trajectory_no_piE.x)[dataset.good] dy = (trajectory.y - trajectory_no_piE.y)[dataset.good] delta_E = dx * as_dict['pi_E_E'] + dy * as_dict['pi_E_N'] delta_N = dx * as_dict['pi_E_N'] - dy * as_dict['pi_E_E'] det = as_dict['pi_E_N']**2 + as_dict['pi_E_E']**2 if 'pi_E_N' in parameters: gradient['pi_E_N'] += np.sum( factor_d_x_d_u * delta_N + factor_d_y_d_u * delta_E) gradient['pi_E_N'] /= det if 'pi_E_E' in parameters: gradient['pi_E_E'] += np.sum( factor_d_x_d_u * delta_E - factor_d_y_d_u * delta_N) gradient['pi_E_E'] /= det if len(parameters) == 1: out = gradient[parameters[0]] else: out = np.array([gradient[p] for p in parameters]) return out