Skip to Content
Pipeline StagesStructural Filters

Structural Filters

Stage 3 builds a structural liability profile and applies an explicit hard gate. calculate_* controls what is evaluated and written. filter_* controls what participates in survival. Enabling a diagnostic does not imply rejection.

Every enabled filter_* flag requires its backing calculate_* flag. Otherwise Stage 3 raises an error.

Parallelism uses one resolved worker count: stage n_jobs, else master n_jobs. There is no separate Common Alerts or parse-input worker ladder.

Default Hard Gate

Shipped config_structFilters.yml:

hard_pass = PAINS-free AND native NIBR pass AND NIBR severity < 10 AND Lilly pass at demerit cutoff 160 AND MolGraph max severity < 5 AND no curated protecting group AND undefined stereocenters ≤ 2
MethodRole
PAINSCommon Alerts subset only. Other rulesets are calculated but not enforced by default.
NIBRScreening-deck gate. Accumulated severity ≥ 10 fails; any EXCLUDE rule sets severity to 10. Native pass_filter is also required.
Lilly160Independent medicinal-chemistry gate at demerit cutoff 160.
MolGraphGraph-sanity check. Severity 5, 8, or 10 fails.
Protecting groupsCurated Fmoc / Boc-family and related terminal protecting-group SMARTS.
Undefined stereoUses calculate_stereo_center; rejects when undefined centers > stereo_max_undefined.

Default YAML:

filter_common_alerts: true filter_NIBR: true filter_lilly: true filter_molgraph_stats: true filter_protecting_groups: true filter_undefined_stereo_center: true # All other filter_* flags are false. nibr_max_severity: 10 molgraph_max_severity: 5 lilly_demerit_cutoff: 160 stereo_max_undefined: 2 common_alerts_filter_include_rulesets: - PAINS

Common Alerts: Calculate vs Filter

These controls are independent.

KeyEffect
calculate_common_alertsRun the Common Alerts implementation.
include_rulesetsWhich rulesets to calculate. all = every ruleset in alerts_data_path. [] / null = calculate none. A list = only those names.
exclude_smartsDrop exact SMARTS strings from calculation (catalog smarts column).
filter_common_alertsWhether Common Alerts participate in the hard gate.
common_alerts_filter_include_rulesetsWhich calculated rulesets are enforced. Empty list = enforce every calculated ruleset.
common_alerts_filter_exclude_rulesetsRemove rulesets from the hard gate. Exclusions win.

Default policy: calculate all 23 rulesets / 2,458 SMARTS; enforce PAINS only.

calculate_common_alerts: true include_rulesets: all exclude_smarts: [] filter_common_alerts: true common_alerts_filter_include_rulesets: - PAINS common_alerts_filter_exclude_rulesets: []

Outputs keep both views: native pass (all calculated alerts) and enforcement_pass (selected hard subset). Per-match rows live in common_alerts/hits_long.csv. Rule IDs are audit metadata, not configuration keys.

Catalog notes and pitfalls: src/hedgehog/struct_filters/data/README.md.

Profiles

Three shipped Stage 3 configs share the same diagnostic calculations. Hard gates differ. The former balanced profile is removed.

ProfileConfigHard gateSmoke on 20 MOSES (results/profile_smoke_struct_filters/)
defaultconfig_structFilters.ymlPAINS + NIBR<10 + Lilly160 + MolGraph<5 + protecting groups + undefined stereo ≤215/20 hard pass
explorationconfig_structFilters_exploration.ymlNIBR + MolGraph + protecting groups + undefined stereo ≤3 (no PAINS, no Lilly)18/20 hard pass
strictconfig_structFilters_strict.ymlPAINS + LD50-Oral + Toxicophore + Skin + MLSMR + NIBR + Lilly100 + Bredt + MolGraph + protecting groups + molcomplexity + undefined ≤2 + total stereo <50/20 hard pass

Exploration is softer than default. Strict adds high-prevalence Common Alerts rulesets (Toxicophore, Skin, MLSMR) plus Lilly100, Bredt, complexity, and total stereocenter enforcement; on this sample it rejects everything.

Why these gates

Prevalence on ZINC / ChEMBL / FDA / KRAS (molecule-level hit rates, not rejection rates for diagnostic-only rulesets): results/zinc250_thresholds/iteration_24_structural_ruleset_prevalence/REPORT.md.

Selected ZINC+ChEMBL hit rates from that report:

Ruleset / methodHit rateRole in default
Toxicophore75.85%Diagnostic only (strict hard)
Skin68.35%Diagnostic only (strict hard)
MLSMR46.04%Diagnostic only (strict hard)
Lilly17.36%Hard at cutoff 160
NIBR5.54%Hard
PAINS3.56%Hard
MolGraph0.04%Hard

High diagnostic prevalence must not be read as an equivalent Stage 3 rejection rate. Only PAINS among Common Alerts is enforced in the default profile.

Diagnostics (calculated, often not enforced)

Shipped profiles calculate:

  • Common Alerts: all 23 rulesets
  • MolGraph, NIBR, Lilly, protecting groups
  • Bredt, MolComplexity, ring infraction
  • Stereocenter counts (total and undefined)
  • Halogenicity (F / Br / Cl counts)
  • Symmetry (MedChem score_symmetry heuristic)

Binary methods keep native pass, status, and reason. Numeric methods also write observed values and cutoffs. The unified table is structural_liability_profile.csv.

Target-Aware Behavior

Alignment does not change structural filter_* flags, Common Alerts enforcement lists, or structural thresholds. The hard gate stays the source profile policy. Target evidence may justify an explicit future exception for one concrete rule; absence of a motif among actives does not create a new prohibition.

Descriptor bounds are aligned separately (alignment.descriptor_bounds_mode).

Output Files

FileDescription
structural_liability_profile.csvHard decision plus every diagnostic field
filtered_molecules.csv / failed_molecules.csvHard-gate survivors / rejects
{filter}/metrics.csv, extended.csv, filtered_molecules.csvPer-method artifacts when write_per_filter_outputs is true
common_alerts/hits_long.csvOne row per SMARTS match

Large-dataset mode writes the liability profile as CSV parts and materializes a single CSV when under the configured limit. Per-filter folders, plots, and failure analysis are ignored in large mode; the liability profile flag is still honored.

Usage

uv run hedgehog uv run hedgehog --stage struct_filters uv run hedge --stage struct_filters

Point config_structFilters in the master config at config_structFilters_exploration.yml or config_structFilters_strict.yml to switch profiles.

In single-stage mode, Stage 3 reuses descriptor output when present; otherwise it falls back to MolPrep or sampled input.

Last updated on