# backtracks plotting functions
import corner
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sb
from astropy.time import Time
from dynesty import plotting as dyplot
from matplotlib.ticker import FuncFormatter
from scipy.stats import norm
from backtracks.utils import transform_gengamm, radec2seppa, seppa2radec, transform_errors, utc2tt, radecdists
plt.style.use('default')
plt.rcParams['figure.figsize'] = [9., 6.]
plt.rcParams['figure.dpi'] = 300
plt.rcParams['font.family'] = 'monospace' # Fonts
plt.rcParams['font.monospace'] = 'DejaVu Sans Mono'
plt.rcParams["xtick.top"] = True
plt.rcParams["ytick.right"] = True
plt.rcParams["xtick.direction"] = "in"
plt.rcParams["xtick.minor.visible"] = True
plt.rcParams["ytick.direction"] = "in"
plt.rcParams["ytick.minor.visible"] = True
sb.set_context("talk")
[docs]
def diagnostic(backtracks, fileprefix='./', filepost='.pdf'):
"""
Function to plot the dynesty run summary plot.
Args:
backtracks (class): backtracks.System class which carries the needed methods and attributes.
fileprefix (str): Prefix to filename. Default "./" for current folder.
filepost (str): Postfix to filename. Default ".pdf" for outputting pdf files.
Returns:
fig (figure): Figure object corresponding to the saved plot.
"""
# Plot results.
fig, axes = dyplot.runplot(backtracks.results, color='cornflowerblue')
target_name = backtracks.target_name.replace(' ','_')
object_label = f"cc{backtracks.obj_num}"
plt.savefig(f'{fileprefix}{target_name}_{object_label}_evidence_backtracks'+filepost, dpi=300, bbox_inches='tight')
return fig
[docs]
def plx_prior(backtracks, fileprefix='./', filepost='.pdf'):
"""
Function to plot the parallax prior (in mas).
Args:
backtracks (class): backtracks.System class which carries the needed methods and attributes.
fileprefix (str): Prefix to filename. Default "./" for current folder.
filepost (str): Postfix to filename. Default ".pdf" for outputting pdf files.
Returns:
fig (figure): Figure object corresponding to the saved plot.
"""
u = np.arange(0., 1, 0.001)
ppf = 1000./transform_gengamm(u, backtracks.L, backtracks.alpha, backtracks.beta)
fig = plt.figure(facecolor='white')
label = rf'$\alpha=${backtracks.alpha:.2f}, $\beta=${backtracks.beta:.2f}, L={backtracks.L:.2f}'
plt.plot(u, ppf, label=label, color='cornflowerblue')
plt.xlabel('u')
plt.ylabel('PPF(p(u))')
plt.legend()
target_name = backtracks.target_name.replace(' ', '_')
object_label = f"cc{backtracks.obj_num}"
plt.savefig(f"{fileprefix}{target_name}_{object_label}_bjprior_backtracks"+filepost, dpi=300, bbox_inches='tight')
return fig
[docs]
def posterior(backtracks, fileprefix='./', filepost='.pdf'):
"""
Function to plot a cornerplot of the posteriors of the fit to a moving background star scenario.
Args:
backtracks (class): backtracks.System class which carries the needed methods and attributes.
fileprefix (str): Prefix to filename. Default "./" for current folder.
filepost (str): Postfix to filename. Default ".pdf" for outputting pdf files.
Returns:
fig (figure): Figure object corresponding to the saved plot.
Notes:
* Only the five parameters of the background star are plotted.
"""
labels = ["RA (deg, ep=2016)", "DEC (deg, ep=2016)", "pmra (mas/yr)", "pmdec (mas/yr)", "parallax (mas)"]
levels = 1.0 - np.exp(-0.5 * np.arange(1, 3.1, 1) ** 2) # 1,2,3 sigma levels for a 2d gaussian
# plot extended run (right)
if backtracks.results.samples.shape[1] == 2:
labels = labels[:2]
dims = range(2)
elif backtracks.results.samples.shape[1] == 4:
labels = labels[:4]
dims = range(4)
else:
dims = range(5)
fig, ax = dyplot.cornerplot(backtracks.results,
color='cornflowerblue',
dims=dims,
# truths=np.zeros(ndim),
# span=[(-4.5, 4.5) for i in range(ndim)],
labels=labels,
hist2d_kwargs={'levels':levels},
max_n_ticks=4,
# label_kwargs={},
show_titles=True,
title_kwargs={'fontsize':14},
quantiles=(0.1587,0.5,0.8413),
title_quantiles=(0.1587,0.5,0.8413)
# fig=(fig, axes[:, 4:])
)
fig.figsize = (9,9)
plt.subplots_adjust(wspace=0.3,
hspace=0.3)
tick_formatter = FuncFormatter(tickform)
for i,axis_row in enumerate(ax):
if i == 1:
axis_row[0].yaxis.set_major_formatter(tick_formatter) # make left hand ticks behave
for axis in axis_row:
axis.set(xlabel=None, ylabel=None)
for axis_col in ax[:,-1]:
axis_col.xaxis.set_major_formatter(tick_formatter)
# ax[-1,-1].set_xscale('log')
# ax[-1,-1].set_xlim(xmin=1e-2)
target_name = backtracks.target_name.replace(' ', '_')
object_label = f"cc{backtracks.obj_num}"
plt.savefig(f"{fileprefix}{target_name}_{object_label}_corner_backtracks"+filepost, dpi=300, bbox_inches='tight')
return fig
[docs]
def trackplot(
backtracks,
ref_epoch,
days_backward=3.*365.,
days_forward=3.*365.,
step_size=10.,
plot_radec=False,
plot_stationary=False,
fileprefix='./',
filepost='.pdf'
):
"""
Function to plot a fitted (non-stationary) background star scenario track with the data.
Args:
backtracks (class): backtracks.System class which carries the needed methods and attributes.
ref_epoch (float): Julian Date (UTC) as a reference point for the tracks
days_backward (float): Days backward from the reference point at which to start tracks
days_forward (float): Days forward from the reference point at which to end tracks
step_size (float): Step size with which tracks are generated.
plot_radec (bool): Option to plot RA vs time and DEC vs time in panels. Default: False for Sep vs time, PA vs time.
plot_stationary (bool): Option to overplot a track corresponding to an infinitely far stationary background star. Default: False.
fileprefix (str): Prefix to filename. Default "./" for current folder.
filepost (str): Postfix to filename. Default ".pdf" for outputting pdf files.
Returns:
fig (figure): Figure object corresponding to the saved plot.
"""
fig, axs = plt.subplot_mosaic(
'''
AABB
AACC
''',
figsize=(16, 8))
# Epochs for the background tracks
plot_epochs = ref_epoch + np.arange(-days_backward, days_forward, step_size)
plot_epochs_tt=utc2tt(plot_epochs)
# Create astropy times for observations and background model
plot_times = Time(plot_epochs, format='jd')
obs_times = Time(backtracks.epochs, format='jd')
# The corr_terms are True where rho is not a nan
corr_terms =~ np.isnan(backtracks.rho)
# Plot stationary background track at infinity
if plot_stationary:
stat_pars = backtracks.stationary_params
ra_stat, dec_stat = radecdists(backtracks, plot_epochs_tt, stat_pars)
axs['A'].plot(ra_stat, dec_stat, color="lightgray", ls='--',
label=rf"Stationary track, $\chi^2_r={backtracks.stationary_chi2_red:.2f}$")
if plot_radec:
axs['B'].plot(plot_times.decimalyear, ra_stat, color="lightgray", ls='--')
axs['C'].plot(plot_times.decimalyear, dec_stat, color="lightgray", ls='--')
else:
sep_stat, pa_stat = radec2seppa(ra_stat, dec_stat)
axs['B'].plot(plot_times.decimalyear, sep_stat, color='lightgray', ls='--')
axs['C'].plot(plot_times.decimalyear, pa_stat, color='lightgray', ls='--')
# Create background tracks from posterior samples
post_samples = backtracks.results.samples_equal()
n_samples = 200
random_idx = np.random.choice(post_samples.shape[0], size=n_samples)
ra_samples = np.zeros((n_samples, plot_epochs.size))
dec_samples = np.zeros((n_samples, plot_epochs.size))
for i, idx_item in enumerate(random_idx):
ra_samples[i, ], dec_samples[i, ] = radecdists(backtracks, plot_epochs_tt, post_samples[idx_item, ])
axs['A'].plot(ra_samples[i, ], dec_samples[i, ], color='cornflowerblue', lw=0.3, alpha=0.3)
# Create 1 sigma and 3 sigma percentiles for envelopes
ra_quant = np.percentile(ra_samples, [0.13, 16.0, 84.0, 99.87], axis=0)
dec_quant = np.percentile(dec_samples, [0.13, 16.0, 84.0, 99.87], axis=0)
# Convert quantile tracks to sep and PA
sep_q1, pa_q1 = radec2seppa(ra_quant[0, ], dec_quant[0, ])
sep_q2, pa_q2 = radec2seppa(ra_quant[1, ], dec_quant[1, ])
sep_q3, pa_q3 = radec2seppa(ra_quant[2, ], dec_quant[2, ])
sep_q4, pa_q4 = radec2seppa(ra_quant[3, ], dec_quant[3, ])
# Plot quantile envelopes for RA/Dec or sep/PA
if plot_radec:
axs['B'].fill_between(plot_times.decimalyear, y1=ra_quant[0, ], y2=ra_quant[3, ], color='cornflowerblue', alpha=0.2, lw=0.)
axs['C'].fill_between(plot_times.decimalyear, y1=dec_quant[0, ], y2=dec_quant[3, ], color='cornflowerblue', alpha=0.2, lw=0.)
axs['B'].fill_between(plot_times.decimalyear, y1=ra_quant[1, ], y2=ra_quant[2, ], color='cornflowerblue', alpha=0.4, lw=0.)
axs['C'].fill_between(plot_times.decimalyear, y1=dec_quant[1, ], y2=dec_quant[2, ], color='cornflowerblue', alpha=0.4, lw=0.)
else:
axs['B'].fill_between(plot_times.decimalyear, y1=sep_q1, y2=sep_q4, color='cornflowerblue', alpha=0.2, lw=0.)
axs['C'].fill_between(plot_times.decimalyear, y1=pa_q1, y2=pa_q4, color='cornflowerblue', alpha=0.2, lw=0.)
axs['B'].fill_between(plot_times.decimalyear, y1=sep_q2, y2=sep_q3, color='cornflowerblue', alpha=0.4, lw=0.)
axs['C'].fill_between(plot_times.decimalyear, y1=pa_q2, y2=pa_q3, color='cornflowerblue', alpha=0.4, lw=0.)
# Plot background track with best-fit parameters
ra_bg, dec_bg = radecdists(backtracks, plot_epochs_tt, backtracks.run_median)
axs['A'].plot(ra_bg, dec_bg, color="black", label=fr"Median track, $\chi^2_r={backtracks.median_chi2_red:.2f}$")
if plot_radec:
axs['B'].plot(plot_times.decimalyear, ra_bg, color='black')
axs['C'].plot(plot_times.decimalyear, dec_bg, color='black')
else:
sep_best, pa_best = radec2seppa(ra_bg, dec_bg)
axs['B'].plot(plot_times.decimalyear, sep_best, color='black')
axs['C'].plot(plot_times.decimalyear, pa_best, color='black')
# Connect data points with best-fit model epochs
ra_bg_best, dec_bg_best = radecdists(backtracks, backtracks.epochs_tt, backtracks.run_median)
for i in range(len(backtracks.ras)):
comp_ra = (backtracks.ras[i], ra_bg_best[i])
comp_dec = (backtracks.decs[i], dec_bg_best[i])
axs['A'].plot(comp_ra, comp_dec, ls='--', color='tab:grey', lw=2.0)
# Plot coordinates at observation epochs for best-fit parameters
axs['A'].plot(ra_bg_best, dec_bg_best, color="none", mec='tab:grey',
ms=5., mew=1.5, linestyle="none", marker="o")
# Plot data points (deltaRA, deltaDEC)
axs['A'].errorbar(backtracks.ras, backtracks.decs,
yerr=backtracks.decserr, xerr=backtracks.raserr,
color="tomato",ecolor='darkred', linestyle="none",
marker='o', ms=5., mew=1.5, mec='darkred')
# Plot deltaRA/deltaDec or sep/PA as function of date
if plot_radec:
# Plot the deltaRA and deltaDec data points
axs['B'].errorbar(obs_times.decimalyear, backtracks.ras,
yerr=backtracks.raserr, color="tomato",
ecolor='darkred', linestyle="none",
marker='o', ms=5., mew=1.5, mec='darkred')
axs['C'].errorbar(obs_times.decimalyear, backtracks.decs,
yerr=backtracks.decserr, color="tomato",
ecolor='darkred', linestyle="none",
marker='o', ms=5., mew=1.5, mec='darkred')
else:
# Plot the sep and PA data points
# The error calculation is adopted from orbitize!
if np.sum(~corr_terms) > 0:
# Plot the sep and PA data points that are not corr_terms
obs_sep, obs_pa = radec2seppa(backtracks.ras[~corr_terms], backtracks.decs[~corr_terms])
obs_sep_err = 0.5*backtracks.raserr[~corr_terms] + 0.5*backtracks.decserr[~corr_terms]
obs_pa_err = np.degrees(obs_sep_err/obs_sep)
axs['B'].errorbar(obs_times[~corr_terms].decimalyear, obs_sep,
yerr=obs_sep_err, color="tomato",
ecolor='darkred', linestyle="none",
marker='o', ms=5., mew=1.5, mec='darkred')
axs['C'].errorbar(obs_times[~corr_terms].decimalyear, obs_pa,
yerr=obs_pa_err, color="tomato",
ecolor='darkred', linestyle="none",
marker='o', ms=5., mew=1.5, mec='darkred')
if np.sum(corr_terms) > 0:
# Plot the sep and PA data points that are corr_terms
obs_sep, obs_pa = radec2seppa(backtracks.ras[corr_terms], backtracks.decs[corr_terms])
obs_sep_err = np.zeros(obs_sep.size)
obs_pa_err = np.zeros(obs_sep.size)
for i in np.arange(np.sum(corr_terms)):
# Transform the uncertainties from RA/Dec to sep/PA
obs_sep_err[i], obs_pa_err[i], _ = transform_errors(
backtracks.ras[corr_terms][i], backtracks.decs[corr_terms][i],
backtracks.raserr[corr_terms][i], backtracks.decserr[corr_terms][i],
backtracks.rho[corr_terms][i], radec2seppa)
axs['B'].errorbar(obs_times[corr_terms].decimalyear, obs_sep,
yerr=obs_sep_err, color="tomato",
ecolor='darkred', linestyle="none",
marker='o', ms=5., mew=1.5, mec='darkred')
axs['C'].errorbar(obs_times[corr_terms].decimalyear, obs_pa,
yerr=obs_pa_err, color="tomato",
ecolor='darkred', linestyle="none",
marker='o', ms=5., mew=1.5, mec='darkred')
axs['A'].invert_xaxis()
axs['A'].axis('equal')
axs['A'].set_xlabel("Delta RA (mas)")
axs['A'].set_ylabel("Delta DEC (mas)")
# axs['A'].legend()
# instead of plotting legend by default
# let's just print chi2 until we sort it out
print(f"[BACKTRACKS INFO]: Stationary track reduced chi squared is {backtracks.stationary_chi2_red:.2f}")
print(f"[BACKTRACKS INFO]: Median track reduced chi squared is {backtracks.median_chi2_red:.2f}")
axs['B'].set_xlabel('Date (year)')
axs['C'].set_xlabel('Date (year)')
if plot_radec:
axs['B'].set_ylabel('Delta RA (mas)')
axs['C'].set_ylabel('Delta DEC (mas)')
else:
axs['B'].set_ylabel('Separation (mas)')
axs['C'].set_ylabel('PA (degrees)')
plt.tight_layout()
target_name = backtracks.target_name.replace(' ', '_')
object_label = f"cc{backtracks.obj_num}"
plt.savefig(f"{fileprefix}{target_name}_{object_label}_model_backtracks"+filepost, dpi=300, bbox_inches='tight')
return fig
[docs]
def neighborhood(backtracks, fileprefix='./', filepost='.pdf'):
"""
Function to plot the best fitting background star parameters on top of the Gaia priors.
Args:
backtracks (class): backtracks.System class which carries the needed methods and attributes.
fileprefix (str): Prefix to filename. Default "./" for current folder.
filepost (str): Postfix to filename. Default ".pdf" for outputting pdf files.
Returns:
fig (figure): Figure object corresponding to the saved plot.
"""
# check how unlikely the fit motion is by comparing the distribution to the nearby Gaia distribution
nearby_table = pd.DataFrame(columns=['pmra', 'pmdec', 'parallax'])
nearby_table['pmra'] = backtracks.nearby['pmra'].data
nearby_table['pmdec'] = backtracks.nearby['pmdec'].data
nearby_table['parallax'] = backtracks.nearby['parallax'].data
truths = backtracks.run_median[2:5]
if len(truths) == 0:
truths = np.array([0., 0., 0.])
elif len(truths) == 2:
truths = np.append(truths, 0.)
nearby_array = nearby_table.to_numpy()
nan_idx = np.isnan(nearby_array).any(axis=1)
# Quantiles for the 1D distributions (-1, 1 sigma)
quantiles = [norm.cdf(n_sigma) for n_sigma in [-1, 1]]
# Quantiles for the titles (-1, 0, 1 sigma)
title_quantiles = [norm.cdf(n_sigma) for n_sigma in [-1, 0, 1]]
# Credible regions for the 2D distributions (1, 2 sigma)
# Radial integral of the Gaussian probability
levels = [1.0 - np.exp(-0.5 * n_sigma ** 2) for n_sigma in [1, 2]]
# Exclude 1% of the outliers from the posterior plot for clarity
range_select = np.full(nearby_table.shape[1], 0.99)
titles = [r"$\mu_\alpha \cos\delta$", r"$\mu_\delta$", r"$\pi$"]
labels = [r"$\mu_\alpha \cos\delta$ (mas/yr)", r"$\mu_\delta$ (mas/yr)", r"$\pi$ (mas)"]
fig = corner.corner(nearby_array[~nan_idx, ],
truths=truths,
truth_color='cornflowerblue',
labels=labels,
titles=titles,
title_quantiles=title_quantiles,
title_fmt=".1f",
show_titles=True,
smooth=None,
smooth_1d=None,
quantiles=quantiles,
levels=levels,
range=range_select,
labelpad=-0.08)
for ax in fig.axes:
ax.title.set_fontsize(14.)
ax.xaxis.label.set_fontsize(15.)
ax.yaxis.label.set_fontsize(15.)
ax.tick_params(axis='both', labelsize=13.)
target_name = backtracks.target_name.replace(' ', '_')
object_label = f"cc{backtracks.obj_num}"
plt.savefig(f'{fileprefix}{target_name}_{object_label}_nearby_gaia_distribution'+filepost, dpi=300, bbox_inches='tight')
return fig
[docs]
def stationtrackplot(
backtracks,
ref_epoch,
days_backward=3.*365.,
days_forward=3.*365.,
step_size=10.,
plot_radec=False,
fileprefix='./',
filepost='.pdf'
):
"""
Function to plot an infinitely far stationary scenario track with the data.
Args:
backtracks (class): backtracks.System class which carries the needed methods and attributes.
ref_epoch (float): Julian Date (UTC) as a reference point for the tracks
days_backward (float): Days backward from the reference point at which to start tracks
days_forward (float): Days forward from the reference point at which to end tracks
step_size (float): Step size with which tracks are generated.
plot_radec (bool): Option to plot RA vs time and DEC vs time in panels. Default: False for Sep vs time, PA vs time.
fileprefix (str): Prefix to filename. Default "./" for current folder.
filepost (str): Postfix to filename. Default ".pdf" for outputting pdf files.
Returns:
fig (figure): Figure object corresponding to the saved plot.
"""
fig, axs = plt.subplot_mosaic(
'''
AABB
AACC
''',
figsize=(16, 8))
# Epochs for the background tracks
plot_epochs = ref_epoch + np.arange(-days_backward, days_forward, step_size)
plot_epochs_tt = utc2tt(plot_epochs)
# Create astropy times for observations and background model
plot_times = Time(plot_epochs, format='jd')
obs_times = Time(backtracks.epochs, format='jd')
# The corr_terms are True where rho is not a nan
corr_terms =~ np.isnan(backtracks.rho)
# Plot stationary background track at infinity
stat_pars = backtracks.stationary_params
ra_stat, dec_stat = radecdists(backtracks, plot_epochs_tt, stat_pars)
axs['A'].plot(ra_stat, dec_stat, color="lightgray", ls='--',
label=fr"Stationary track, $\chi^2_r={backtracks.stationary_chi2_red:.2f}$"
)
if plot_radec:
axs['B'].plot(plot_times.decimalyear, ra_stat, color="lightgray", ls='--')
axs['C'].plot(plot_times.decimalyear, dec_stat, color="lightgray", ls='--')
else:
sep_stat, pa_stat = radec2seppa(ra_stat, dec_stat)
axs['B'].plot(plot_times.decimalyear, sep_stat, color='lightgray', ls='--')
axs['C'].plot(plot_times.decimalyear, pa_stat, color='lightgray', ls='--')
# Connect data points with best-fit model epochs
ra_bg_best, dec_bg_best = radecdists(backtracks, backtracks.epochs_tt, stat_pars)
for i in range(len(backtracks.ras)):
comp_ra = (backtracks.ras[i], ra_bg_best[i])
comp_dec = (backtracks.decs[i], dec_bg_best[i])
axs['A'].plot(comp_ra, comp_dec, ls='--', color='tab:grey', lw=2.0)
# Plot coordinates at observation epochs for best-fit parameters
axs['A'].plot(ra_bg_best, dec_bg_best, color="none", mec='tab:grey',
ms=5., mew=1.5, linestyle="none", marker="o")
# Plot data points (deltaRA, deltaDEC)
axs['A'].errorbar(backtracks.ras, backtracks.decs,
yerr=backtracks.decserr, xerr=backtracks.raserr,
color="tomato", ecolor='darkred', mec='darkred',
label="Data",
linestyle="none", marker="o", ms=5., mew=1.5)
# Plot deltaRA/deltaDec or sep/PA as function of date
if plot_radec:
# Plot the deltaRA and deltaDec data points
axs['B'].errorbar(obs_times.decimalyear, backtracks.ras,
yerr=backtracks.raserr, color="tomato",
ecolor='darkred', linestyle="none",
marker='o', ms=5., mew=1.5, mec='darkred')
axs['C'].errorbar(obs_times.decimalyear, backtracks.decs,
yerr=backtracks.decserr, color="tomato",
ecolor='darkred', linestyle="none",
marker='o', ms=5., mew=1.5, mec='darkred')
else:
# Plot the sep and PA data points
# The error calculation is adopted from orbitize!
if np.sum(~corr_terms) > 0:
# Plot the sep and PA data points that are not corr_terms
obs_sep, obs_pa = radec2seppa(backtracks.ras[~corr_terms], backtracks.decs[~corr_terms])
obs_sep_err = 0.5*backtracks.raserr[~corr_terms] + 0.5*backtracks.decserr[~corr_terms]
obs_pa_err = np.degrees(obs_sep_err/obs_sep)
axs['B'].errorbar(obs_times[~corr_terms].decimalyear, obs_sep,
yerr=obs_sep_err, color="tomato",
ecolor='darkred', linestyle="none",
marker='o', ms=5., mew=1.5, mec='darkred')
axs['C'].errorbar(obs_times[~corr_terms].decimalyear, obs_pa,
yerr=obs_pa_err, color="tomato",
ecolor='darkred', linestyle="none",
marker='o', ms=5., mew=1.5, mec='darkred')
if np.sum(corr_terms) > 0:
# Plot the sep and PA data points that are corr_terms
obs_sep, obs_pa = radec2seppa(backtracks.ras[corr_terms], backtracks.decs[corr_terms])
obs_sep_err = np.zeros(obs_sep.size)
obs_pa_err = np.zeros(obs_sep.size)
for i in np.arange(np.sum(corr_terms)):
# Transform the uncertainties from RA/Dec to sep/PA
obs_sep_err[i], obs_pa_err[i], _ = transform_errors(
backtracks.ras[corr_terms][i], backtracks.decs[corr_terms][i],
backtracks.raserr[corr_terms][i], backtracks.decserr[corr_terms][i],
backtracks.rho[corr_terms][i], radec2seppa)
axs['B'].errorbar(obs_times[corr_terms].decimalyear, obs_sep,
yerr=obs_sep_err, color="tomato",
ecolor='darkred', linestyle="none",
marker='o', ms=5., mew=1.5, mec='darkred')
axs['C'].errorbar(obs_times[corr_terms].decimalyear, obs_pa,
yerr=obs_pa_err, color="tomato",
ecolor='darkred', linestyle="none",
marker='o', ms=5., mew=1.5, mec='darkred')
axs['A'].invert_xaxis()
axs['A'].axis('equal')
axs['A'].set_xlabel("Delta RA (mas)")
axs['A'].set_ylabel("Delta DEC (mas)")
# axs['A'].legend()
print(f"[BACKTRACKS INFO]: Stationary track reduced chi squared is {backtracks.stationary_chi2_red:.2f}")
axs['B'].set_xlabel('Date (year)')
axs['C'].set_xlabel('Date (year)')
if plot_radec:
axs['B'].set_ylabel('Delta RA (mas)')
axs['C'].set_ylabel('Delta DEC (mas)')
else:
axs['B'].set_ylabel('Separation (mas)')
axs['C'].set_ylabel('PA (degrees)')
plt.tight_layout()
target_name = backtracks.target_name.replace(' ', '_')
object_label = f"cc{backtracks.obj_num}"
plt.savefig(f"{fileprefix}{target_name}_{object_label}_stationary_backtracks"+filepost, dpi=300, bbox_inches='tight')
return fig