Post-Production Workflow Guide¶
This guide explains how to write and run a pycWB post-production workflow. Post-production starts after search jobs have produced catalog and progress Parquet files. A workflow YAML then selects analysis subsets, matches simulation triggers to injection truth, trains or applies ranking models, builds FAR products, computes simulation efficiency, and writes report artifacts.
The main command is:
pycwb post-process path/to/postprocess_workflow.yaml
To inspect the dependency graph without running any action:
pycwb post-process path/to/postprocess_workflow.yaml --diagram-only
The user-facing template
examples/postproduction/standard_analysis_10pct_workflow.yaml is a good
starting point for a complete background, simulation, FAR, efficiency, and
HTML-report workflow.
What The Workflow Consumes¶
A complete post-production workflow usually consumes these files:
Artifact |
Typical path |
Purpose |
|---|---|---|
Background catalog |
|
Trigger rows used for training, FAR holdout, and zero-lag scoring. |
Background progress |
|
Job/livetime rows used for train/FAR splits and reports. |
Simulation trigger catalog |
|
Recovered simulation triggers. |
Simulation progress |
|
Job metadata for selecting a simulation training subset. |
Simulation summary |
|
One row per injected signal, generated before post-production. |
XGBoost config |
|
Feature and ranking-statistic configuration. |
Public alerts |
|
Optional known-event candidates for the background report. |
For simulation studies, build simulations.parquet before running the
workflow:
pycwb simulation-summary user_parameters.yaml \
--work-dir /path/to/sim/run \
--output /path/to/sim/run/catalog/simulations.parquet
You can also run matching as a standalone command, but complete workflows
should prefer the explicit postprocess.matching.match_simulations action:
pycwb match-simulations catalog.parquet simulations.parquet \
--how right --output matched_right.parquet
YAML Structure¶
A workflow has three top-level sections:
varsUser-editable values and reusable paths. Put run names, chunks, split fractions, output locations, and input file paths here.
runtimeExecution settings owned by the workflow runner, especially the temporary directory and cleanup policy.
stepsOrdered actions. Each step declares an
id, human-readablename, action path,inputs, optionalargs, andoutputs.
Minimal shape:
vars:
work_dir: .
paths:
bkg_catalog: BKG/run/catalog/catalog.parquet
runtime:
tmp_dir: ${work_dir}/tmp/postprod
cleanup_tmp: never
steps:
- id: example_step
name: Example Step
action: postprocess.selection.trigger_selection
inputs:
catalog_file: ${paths.bkg_catalog}
progress_file: BKG/run/catalog/progress.parquet
args:
returns: [jobs, triggers, livetime]
outputs:
triggers_file: tmp://selected.parquet
Variables, References, And Temporary Files¶
Use ${...} to expand values from vars. Dotted paths are supported:
vars:
work_dir: .
chunks:
train_bkg_1: K20
train_bkg_2: K22
target: K21
tag: k20_k22_k21
train_fraction_label: 10pct
run:
output_name: O4_${chunks.target}_run1
analysis_slug: bkg_${chunks.tag}_${train_fraction_label}
paths:
output_dir: public/${run.output_name}
model_file: ${paths.output_dir}/models/${run.analysis_slug}_xgb_model_blf.ubj
Use @step.path to read values returned by earlier steps:
catalog_file: "@k21_bkg_split.far.triggers_file"
livetime: "@k21_bkg_split.far.livetime.seconds"
Use tmp:// for intermediate files. The workflow runner resolves those
paths under runtime.tmp_dir:
runtime:
tmp_dir: ${work_dir}/tmp/${run.analysis_slug}
outputs:
output_file: tmp://bkg_far_scored.parquet
Cleanup policies are:
neverKeep temporary files. This is best while developing or debugging.
on_successRemove
runtime.tmp_dirafter a successful workflow.alwaysRemove
runtime.tmp_direven if a later step fails.
Recommended Complete Workflow¶
The standard 10 percent workflow has six phases. The names below match the template and test workflows, but the same pattern works for other chunks or fractions.
1. Split Background¶
Split the target background into a training subset and a FAR holdout. Use
interval_livetime when the same physical jobs can appear in different lag
or shift intervals; this keeps train and FAR livetime disjoint by interval.
- id: k21_bkg_split
name: Split Target BKG Train/FAR
action: postprocess.selection.trigger_selection
inputs:
catalog_file: ${paths.target_bkg_catalog}
progress_file: ${paths.target_bkg_progress}
args:
exclude_zero_lag: true
returns: [jobs, triggers, livetime]
split:
by: interval_livetime
seed: 42
fractions:
train: ${k21_train_fraction}
far: ${k21_far_fraction}
outputs:
train:
jobs_file: tmp://k21_bkg_train_jobs.txt
progress_file: tmp://k21_bkg_train_progress.parquet
intervals_file: tmp://k21_bkg_train_intervals.parquet
intervals_csv_file: tmp://k21_bkg_train_intervals.csv
triggers_file: tmp://k21_bkg_train.parquet
far:
jobs_file: tmp://k21_bkg_far_jobs.txt
progress_file: tmp://k21_bkg_far_progress.parquet
intervals_file: tmp://k21_bkg_far_intervals.parquet
intervals_csv_file: tmp://k21_bkg_far_intervals.csv
triggers_file: tmp://k21_bkg_far.parquet
2. Match And Filter Simulation Training¶
Match the target simulation training trigger catalog to simulations.parquet.
Use how: outer for training cleanup so the matched table includes recovered
and unrecovered simulation truth rows. Then keep recovered, non-vetoed, real
simulation triggers for classifier training.
- id: k21_sim_train_match
name: Match Target SIM Training
action: postprocess.matching.match_simulations
inputs:
catalog_file: ${paths.target_sim_train_catalog}
simulation_file: ${paths.target_sim_train_simulations}
args:
how: outer
window_buffer: 0.0
outputs:
output_file: tmp://k21_sim_train_matched_outer.parquet
- id: k21_sim_train_real
name: Filter Target Real SIM Training
action: postprocess.selection.filter_real_simulation
inputs:
matched_file: "@k21_sim_train_match.matched_file"
sim_catalog: ${paths.target_sim_train_catalog}
args:
require_recovered: true
exclude_vetoed: true
output_schema: matched
outputs:
output_file: tmp://k21_sim_training_real.parquet
3. Select Simulation Training Fraction¶
Select the target simulation training fraction after filtering. Keep
exclude_zero_lag: false for simulation catalogs.
- id: k21_sim_train_select
name: Select Target SIM Training Fraction
action: postprocess.selection.trigger_selection
inputs:
catalog_file: "@k21_sim_train_real.triggers_file"
progress_file: ${paths.target_sim_train_progress}
args:
exclude_zero_lag: false
returns: [jobs, triggers, livetime]
selection:
fraction: ${k21_train_fraction}
seed: 43
outputs:
jobs_file: tmp://k21_sim_train_jobs.txt
triggers_file: tmp://k21_sim_train.parquet
4. Train And Build FAR¶
Train the ranking model with the full training chunks plus the selected target training subset. Then score the target FAR holdout and build the FAR lookup.
- id: model
name: Train XGBoost
action: postprocess.train_xgboost.train_xgboost
inputs:
bkg_catalogs:
- ${paths.train_bkg_1_catalog}
- ${paths.train_bkg_2_catalog}
- "@k21_bkg_split.train.triggers_file"
sim_catalogs:
- ${paths.train_sim_1_catalog}
- ${paths.train_sim_2_catalog}
- "@k21_sim_train_select.triggers_file"
config_file: ${paths.config_file}
args:
model_file: ${paths.model_file}
dump: false
dump_training_review: true
verbose: false
outputs:
training_settings_file: ${paths.xgb_training_settings}
training_output_file: ${paths.xgb_training_output}
- id: far_rho
name: Score Target Holdout BKG And Build FAR
action: postprocess.evaluate.evaluate_far_rho
inputs:
catalog_file: "@k21_bkg_split.far.triggers_file"
model_file: ${paths.model_file}
config_file: ${paths.config_file}
args:
livetime: "@k21_bkg_split.far.livetime.seconds"
ranking_par: rhor
bin_size: 0.0001
vmin: 0.0
vmax: 10.0
outputs:
output_file: ${paths.far_rho_file}
scored_catalog: ${paths.bkg_far_scored}
5. Score Evaluation Simulations¶
Select and score the simulation evaluation catalog. Match it with how:
right when computing efficiency so the matched table has one row per
simulation, including missed injections.
- id: sim_eval_selection
name: Select SIM Evaluation
action: postprocess.selection.trigger_selection
inputs:
catalog_file: ${paths.sim_eval_catalog}
progress_file: ${paths.sim_eval_progress}
args:
exclude_zero_lag: false
returns: [jobs, triggers, livetime]
selection:
fraction: 1.0
seed: 44
outputs:
jobs_file: tmp://sim_eval_jobs.txt
triggers_file: tmp://sim_eval.parquet
- id: sim_eval_match
name: Match SIM Evaluation
action: postprocess.matching.match_simulations
inputs:
catalog_file: ${paths.sim_eval_catalog}
simulation_file: ${paths.sim_eval_simulations}
args:
how: right
window_buffer: 0.0
outputs:
output_file: tmp://sim_eval_matched_right.parquet
- id: sim_efficiency_score
name: Score SIM Evaluation
action: postprocess.evaluate.evaluate_efficiency
inputs:
catalog_file: "@sim_eval_selection.triggers_file"
model_file: ${paths.model_file}
config_file: ${paths.config_file}
args:
threshold: 0.5
outputs:
output_file: ${paths.sim_eval_scored}
- id: waveform_hrss_curves_100yr
name: Waveform HRSS Curves At 100 Years
action: postprocess.plot_efficiency.compute_efficiency_vs_hrss_by_waveform
inputs:
sim_catalog: ${paths.sim_eval_catalog}
matched_file: "@sim_eval_match.matched_file"
bkg_catalog: ${paths.bkg_far_scored}
model_file: ${paths.model_file}
config_file: ${paths.config_file}
args:
livetime: "@k21_bkg_split.far.livetime.seconds"
ifar: 100yr
use_unique_sim: true
exclude_vetoed: false
outputs:
output_file: ${paths.output_dir}/simulations/${run.analysis_slug}_efficiency_vs_hrss_by_waveform_100yr.png
fit_parameters_file: ${paths.output_dir}/simulations/${run.analysis_slug}_fit_parameters_by_waveform_100yr.csv
6. Build Reports¶
Score zero lag if needed, produce the standard background report, optionally score MDC detections at an IFAR threshold, and aggregate everything into the post-production HTML report.
- id: zero_lag_score
name: Score Target Zero Lag BKG
action: postprocess.evaluate.score_catalog
inputs:
catalog_file: ${paths.target_bkg_catalog}
model_file: ${paths.model_file}
config_file: ${paths.config_file}
args:
lag_selection: zero_lag
outputs:
output_file: ${paths.bkg_zero_lag_scored}
- id: background_report
name: Standard Background Report
action: postprocess.report.standard_background_report
inputs:
catalog_file: ${paths.bkg_far_scored}
zero_lag_catalog_file: ${paths.bkg_zero_lag_scored}
fake_openbox_intervals_file: "@k21_bkg_split.far.intervals_csv_file"
progress_file: ${paths.target_bkg_progress}
job_ids_file: "@k21_bkg_split.far.jobs_file"
args:
livetime: "@k21_bkg_split.far.livetime.seconds"
ranking_par: rhor
exclude_zero_lag: true
far_rho_file: ${paths.far_rho_file}
output_dir: ${paths.output_dir}
include_zero_lag: true
include_fake_openbox: true
- id: postproduction_report
name: Build Postproduction Report
action: postprocess.report_builder.postproduction_report
inputs:
workflow_file: ${paths.workflow_filename}
production_catalog_file: ${paths.target_bkg_catalog}
args:
title: O4 ${chunks.train_bkg_1}+${chunks.train_bkg_2} full plus ${chunks.target} ${train_fraction_label} BKG training postproduction report
output_file: ${paths.output_dir}/index.html
data_file: ${paths.output_dir}/report_data.json
bkg:
scored_catalog: ${paths.bkg_far_scored}
far_json: ${paths.far_rho_file}
progress_file: "@k21_bkg_split.far.progress_file"
intervals_file: "@k21_bkg_split.far.intervals_file"
zero_lag_progress_file: ${paths.target_bkg_progress}
zero_lag_catalog_file: ${paths.bkg_zero_lag_scored}
livetime: "@k21_bkg_split.far.livetime.seconds"
ranking_par: rhor
training:
bkg_catalog: "@k21_bkg_split.train.triggers_file"
sim_catalog: "@k21_sim_train_select.triggers_file"
model_file: ${paths.model_file}
config_file: ${paths.config_file}
simulation_runs:
- label: STDINJs Set1
scored_catalog: ${paths.sim_eval_scored}
matched_file: "@sim_eval_match.matched_file"
plots:
- ${paths.output_dir}/simulations/${run.analysis_slug}_efficiency_vs_hrss_by_waveform_100yr.png
Changing Chunks Or Fractions¶
For a chunk-swap workflow, keep the paths derived from a small set of values:
train_fraction_label: 10pct
chunks:
train_bkg_1: K20
train_bkg_2: K22
target: K21
tag: k20_k22_k21
run:
output_name: O4_${chunks.target}_run1
analysis_slug: bkg_${chunks.tag}_${train_fraction_label}
To use K23 instead of K22, update:
chunks:
train_bkg_1: K20
train_bkg_2: K23
target: K21
tag: k20_k23_k21
The derived catalog paths, temporary directory, model name, FAR JSON, plots, and report paths will update together.
If you change the training fraction, update all three values together:
k21_train_fraction: 0.3
k21_far_fraction: 0.7
train_fraction_label: 30pct
Action Reference¶
postprocess.selection.trigger_selectionSelects or splits trigger catalogs by jobs or intervals. Use it for BKG train/FAR splits, SIM training fractions, and full SIM evaluation selections.
postprocess.matching.match_simulationsMatches a trigger catalog to
simulations.parquet. Usehow: outerbefore SIM training filters andhow: rightfor efficiency curves.postprocess.selection.filter_real_simulationKeeps recovered simulation triggers and optionally removes vetoed or across-segment injections.
postprocess.train_xgboost.train_xgboostTrains the XGBoost ranking model from BKG and SIM catalog lists.
postprocess.evaluate.evaluate_far_rhoScores the FAR holdout and writes the FAR-vs-ranking lookup.
postprocess.evaluate.score_catalogScores a catalog, commonly the target zero-lag background.
postprocess.evaluate.evaluate_efficiencyScores simulation evaluation triggers and writes a scored catalog.
postprocess.plot_efficiency.compute_efficiency_vs_hrss_by_waveformBuilds waveform-level efficiency curves using a matched simulation table.
postprocess.report.standard_background_reportWrites the standard background plots, fake-open-box outputs, and zero-lag report products.
postprocess.evaluate.score_mdc_catalogScores MDC detections against a background IFAR threshold.
postprocess.report_builder.postproduction_reportAggregates workflow artifacts into the final HTML and JSON report.
Validation And Debugging¶
Start with a diagram-only dry run:
pycwb post-process postprocess_workflow.yaml --diagram-only
Useful checks before a full run:
All
${...}variables resolve to the intended files.Every
@step.pathreference points to an earlier step.runtime.tmp_diris unique for this analysis.how: outeris used for SIM training matching.how: rightis used for SIM evaluation matching.BKG selections use
exclude_zero_lag: true.SIM selections use
exclude_zero_lag: false.Report paths point to the same files produced by earlier steps.
Common failure modes:
- Missing
simulations.parquet Run
pycwb simulation-summaryfor the simulation production, then rerun post-production.duckdbimport errorInstall the catalog matching dependency in the active environment.
- Reference resolution error
Check that the producing step has an
idand that the referenced key is returned by that action.- Unexpected train/FAR overlap
Use
split.by: interval_livetimeand make sure the input catalog carries job shift metadata.- Temporary file missing
Keep
cleanup_tmp: neverwhile debugging so intermediate parquet files remain inspectable.
Outputs To Expect¶
A complete workflow typically produces:
trained model under
${paths.output_dir}/models/;XGBoost training settings and training output logs;
FAR JSON lookup table;
scored FAR and zero-lag background catalogs;
scored simulation evaluation catalog;
waveform efficiency plot and fit-parameter CSV;
background report plots;
optional MDC detection CSV;
final
index.htmlandreport_data.json.
Keep the workflow YAML with the report output. It records the exact inputs, fractions, seeds, matching modes, and artifact paths used for the analysis.