Examples¶
Tutorial: end-to-end QC in 5 minutes¶
Run PQC and save output:
pqc --par /path/to/J1909-3744.par --out results/J1909-3744_qc.csv
Load the results:
import pandas as pd
df = pd.read_csv("results/J1909-3744_qc.csv")
See the key flags:
df[["bad_point", "event_member"]].mean()
Export just the clean subset:
clean = df.loc[~df["bad_point"].fillna(False)].copy()
clean.to_csv("results/J1909-3744_clean.csv", index=False)
Check the settings used:
# settings file is written alongside the CSV
with open("results/J1909-3744_qc.pqc_settings.toml") as f:
print(f.read().splitlines()[:20])
Configured pipeline¶
from pqc.pipeline import run_pipeline
from pqc.config import BadMeasConfig, TransientConfig, MergeConfig
df = run_pipeline(
"/path/to/pulsar.par",
backend_col="group",
bad_cfg=BadMeasConfig(tau_corr_days=0.03, fdr_q=0.02),
tr_cfg=TransientConfig(tau_rec_days=10.0, delta_chi2_thresh=30.0),
merge_cfg=MergeConfig(tol_days=3.0 / 86400.0),
)
Feature structure diagnostics¶
from pqc.pipeline import run_pipeline
from pqc.config import FeatureConfig, StructureConfig
df = run_pipeline(
"/path/to/pulsar.par",
feature_cfg=FeatureConfig(add_orbital_phase=True, add_solar_elongation=True),
struct_cfg=StructureConfig(
mode="both",
p_thresh=0.01,
structure_group_cols=("group", "freq_bin"),
),
)
Step and DM-step interpretation¶
Step events model abrupt offsets in the residuals, while DM-step events use frequency scaling consistent with dispersion:
\[r(t, f) = \frac{A}{f^2} H(t - t_0)\]
This is a direct consequence of cold-plasma dispersion and is standard in pulsar timing analyses [LKH2005].
Plotting event membership¶
from pqc.utils.diagnostics import event_membership_mask
import matplotlib.pyplot as plt
# Informative membership (default)
mask = event_membership_mask(df)
# Applicable membership
mask_app = event_membership_mask(df, use_applicable=True)
plt.scatter(df["mjd"], df["resid"], s=6, alpha=0.5, label="all")
plt.scatter(df.loc[mask, "mjd"], df.loc[mask, "resid"], s=8, label="event members")
plt.xlabel("MJD")
plt.ylabel("Residual (s)")
plt.legend()
Solar elongation diagnostic¶
import matplotlib.pyplot as plt
if "solar_elongation_deg" in df.columns:
plt.scatter(df["solar_elongation_deg"], df["resid"], s=6, alpha=0.5)
plt.xlabel("Solar elongation (deg)")
plt.ylabel("Residual (s)")
plt.title("Residuals vs. solar elongation")
Preprocessing before detectors¶
from pqc.config import PreprocConfig
df = run_pipeline(
"/path/to/pulsar.par",
preproc_cfg=PreprocConfig(
detrend_features=("orbital_phase",),
rescale_feature="solar_elongation_deg",
condition_on=("group", "freq_bin"),
use_preproc_for=("ou", "transient"),
),
)
Hard sigma gate¶
from pqc.config import OutlierGateConfig
df = run_pipeline(
"/path/to/pulsar.par",
gate_cfg=OutlierGateConfig(enabled=True, sigma_thresh=3.0),
)
Exponential dip events¶
from pqc.config import ExpDipConfig
df = run_pipeline(
"/path/to/pulsar.par",
dip_cfg=ExpDipConfig(
tau_rec_days=30.0,
window_mult=5.0,
min_points=6,
delta_chi2_thresh=25.0,
member_eta=1.0,
),
)
dip_members = df.loc[df["exp_dip_member"].fillna(False)]
Solar and eclipse events¶
from pqc.config import EclipseConfig, SolarCutConfig
df = run_pipeline(
"/path/to/pulsar.par",
solar_cfg=SolarCutConfig(
enabled=True,
freq_dependence=True,
min_points_global=20,
min_points_year=8,
),
eclipse_cfg=EclipseConfig(
enabled=True,
center_phase=0.25,
freq_dependence=True,
),
)
solar_members = df.loc[df["solar_event_member"].fillna(False)]
eclipse_members = df.loc[df["eclipse_event_member"].fillna(False)]
Gaussian-bumps and glitches¶
from pqc.config import GaussianBumpConfig, GlitchConfig
df = run_pipeline(
"/path/to/pulsar.par",
bump_cfg=GaussianBumpConfig(enabled=True),
glitch_cfg=GlitchConfig(enabled=True, min_duration_days=1000.0),
)
bumps = df.loc[df["gaussian_bump_member"].fillna(False)]
glitches = df.loc[df["glitch_member"].fillna(False)]
References¶
[LKH2005]
Lorimer, D. R., & Kramer, M. (2005). Handbook of Pulsar Astronomy. Cambridge University Press. citeturn1search3