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| Method | Role |
|---|---|
| PAINS | Common Alerts subset only. Other rulesets are calculated but not enforced by default. |
| NIBR | Screening-deck gate. Accumulated severity ≥ 10 fails; any EXCLUDE rule sets severity to 10. Native pass_filter is also required. |
| Lilly160 | Independent medicinal-chemistry gate at demerit cutoff 160. |
| MolGraph | Graph-sanity check. Severity 5, 8, or 10 fails. |
| Protecting groups | Curated Fmoc / Boc-family and related terminal protecting-group SMARTS. |
| Undefined stereo | Uses 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:
- PAINSCommon Alerts: Calculate vs Filter
These controls are independent.
| Key | Effect |
|---|---|
calculate_common_alerts | Run the Common Alerts implementation. |
include_rulesets | Which rulesets to calculate. all = every ruleset in alerts_data_path. [] / null = calculate none. A list = only those names. |
exclude_smarts | Drop exact SMARTS strings from calculation (catalog smarts column). |
filter_common_alerts | Whether Common Alerts participate in the hard gate. |
common_alerts_filter_include_rulesets | Which calculated rulesets are enforced. Empty list = enforce every calculated ruleset. |
common_alerts_filter_exclude_rulesets | Remove 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.
| Profile | Config | Hard gate | Smoke on 20 MOSES (results/profile_smoke_struct_filters/) |
|---|---|---|---|
| default | config_structFilters.yml | PAINS + NIBR<10 + Lilly160 + MolGraph<5 + protecting groups + undefined stereo ≤2 | 15/20 hard pass |
| exploration | config_structFilters_exploration.yml | NIBR + MolGraph + protecting groups + undefined stereo ≤3 (no PAINS, no Lilly) | 18/20 hard pass |
| strict | config_structFilters_strict.yml | PAINS + LD50-Oral + Toxicophore + Skin + MLSMR + NIBR + Lilly100 + Bredt + MolGraph + protecting groups + molcomplexity + undefined ≤2 + total stereo <5 | 0/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 / method | Hit rate | Role in default |
|---|---|---|
| Toxicophore | 75.85% | Diagnostic only (strict hard) |
| Skin | 68.35% | Diagnostic only (strict hard) |
| MLSMR | 46.04% | Diagnostic only (strict hard) |
| Lilly | 17.36% | Hard at cutoff 160 |
| NIBR | 5.54% | Hard |
| PAINS | 3.56% | Hard |
| MolGraph | 0.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_symmetryheuristic)
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
| File | Description |
|---|---|
structural_liability_profile.csv | Hard decision plus every diagnostic field |
filtered_molecules.csv / failed_molecules.csv | Hard-gate survivors / rejects |
{filter}/metrics.csv, extended.csv, filtered_molecules.csv | Per-method artifacts when write_per_filter_outputs is true |
common_alerts/hits_long.csv | One 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_filtersPoint 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.