Skill Tracer
Skills › Scientific Agent Skills › medchem › api_guide.md

api_guide.md

364 lines · skills/medchem/references/api_guide.md at 5f2bc03

Open on GitHub
1# Medchem API Reference
2
3Reference for **medchem 2.1.1**, checked 2026-10-01 against current documentation and installed release source. Requires Python 3.11+. Official API: https://medchem-docs.datamol.io/stable/api/medchem.rules.html
4

Medchem API Reference

Reference for medchem 2.1.1, checked 2026-10-01 against current documentation and installed release source. Requires Python 3.11+. Official API: https://medchem-docs.datamol.io/stable/api/medchem.rules.html

Raw lines 1 to 4
5## Module: medchem.rules
6

Module: medchem.rules

Raw lines 5 to 6
7### Class: RuleFilters
8
9Filter molecules by multiple medicinal chemistry rules. Returns a **pandas DataFrame**.
11**Constructor:**
13```python
14RuleFilters(rule_list: List[Union[str, Callable]], rule_list_names: Optional[List[str]] = None)
15```
17**Call signature:**
19```python
20__call__(
21 mols: Sequence[Union[str, Mol]],
22 n_jobs: int = -1,
23 progress: bool = False,
24 progress_leave: bool = False,
25 scheduler: str = "auto",
26 keep_props: bool = False,
27 fail_if_invalid: bool = True,
28) -> pd.DataFrame
29```
31**Return columns:** `mol`, `pass_all`, `pass_any`, plus one boolean column per rule. With `keep_props=True`, descriptor columns (`mw`, `clogp`, `tpsa`, etc.) are included.
33**Class methods:**
35```python
36RuleFilters.list_available_rules_names() # list of 22 rule names
37RuleFilters.list_available_rules() # rules with property metadata
38```
40**Example:**
42```python
43rfilter = mc.rules.RuleFilters(rule_list=["rule_of_five", "rule_of_cns"])
44df = rfilter(mols=mol_list, n_jobs=-1, progress=True)
45passing = df[df["pass_all"]]
46```

Class: RuleFilters

Filter molecules by multiple medicinal chemistry rules. Returns a pandas DataFrame.

Constructor:

RuleFilters(rule_list: List[Union[str, Callable]], rule_list_names: Optional[List[str]] = None)

Call signature:

__call__(
    mols: Sequence[Union[str, Mol]],
    n_jobs: int = -1,
    progress: bool = False,
    progress_leave: bool = False,
    scheduler: str = "auto",
    keep_props: bool = False,
    fail_if_invalid: bool = True,
) -> pd.DataFrame

Return columns: mol, pass_all, pass_any, plus one boolean column per rule. With keep_props=True, descriptor columns (mw, clogp, tpsa, etc.) are included.

Class methods:

RuleFilters.list_available_rules_names()  # list of 22 rule names
RuleFilters.list_available_rules()        # rules with property metadata

Example:

rfilter = mc.rules.RuleFilters(rule_list=["rule_of_five", "rule_of_cns"])
df = rfilter(mols=mol_list, n_jobs=-1, progress=True)
passing = df[df["pass_all"]]
Raw lines 7 to 47
48### Module: medchem.rules.basic_rules
50Individual rule functions for single molecules. Each returns `bool` (True = passes).
52| Function | Description |
53|----------|-------------|
54| `rule_of_five(mol)` | Lipinski Rule of Five |
55| `rule_of_five_beyond(mol)` | Beyond Ro5 (large binding sites) |
56| `rule_of_four(mol)` | Rule of Four |
57| `rule_of_three(mol)` | Fragment library Rule of Three |
58| `rule_of_three_extended(mol)` | Extended Ro3 |
59| `rule_of_two(mol)` | Rule of Two |
60| `rule_of_ghose(mol)` | Ghose filter |
61| `rule_of_veber(mol)` | Veber oral bioavailability |
62| `rule_of_reos(mol)` | REOS filter |
63| `rule_of_chemaxon_druglikeness(mol)` | ChemAxon drug-likeness |
64| `rule_of_egan(mol)` | Egan permeability |
65| `rule_of_pfizer_3_75(mol)` | Pfizer 3/75 filter |
66| `rule_of_gsk_4_400(mol)` | GSK 4/400 filter |
67| `rule_of_oprea(mol)` | Oprea lead-like |
68| `rule_of_xu(mol)` | Xu filter |
69| `rule_of_cns(mol)` | CNS drug-likeness |
70| `rule_of_respiratory(mol)` | Respiratory drug-likeness |
71| `rule_of_zinc(mol)` | ZINC-like |
72| `rule_of_leadlike_soft(mol)` | Soft lead-like |
73| `rule_of_druglike_soft(mol)` | Soft drug-like |
74| `rule_of_generative_design(mol)` | Generative design space |
75| `rule_of_generative_design_strict(mol)` | Strict generative design |

Module: medchem.rules.basic_rules

Individual rule functions for single molecules. Each returns bool (True = passes).

FunctionDescription
rule_of_five(mol)Lipinski Rule of Five
rule_of_five_beyond(mol)Beyond Ro5 (large binding sites)
rule_of_four(mol)Rule of Four
rule_of_three(mol)Fragment library Rule of Three
rule_of_three_extended(mol)Extended Ro3
rule_of_two(mol)Rule of Two
rule_of_ghose(mol)Ghose filter
rule_of_veber(mol)Veber oral bioavailability
rule_of_reos(mol)REOS filter
rule_of_chemaxon_druglikeness(mol)ChemAxon drug-likeness
rule_of_egan(mol)Egan permeability
rule_of_pfizer_3_75(mol)Pfizer 3/75 filter
rule_of_gsk_4_400(mol)GSK 4/400 filter
rule_of_oprea(mol)Oprea lead-like
rule_of_xu(mol)Xu filter
rule_of_cns(mol)CNS drug-likeness
rule_of_respiratory(mol)Respiratory drug-likeness
rule_of_zinc(mol)ZINC-like
rule_of_leadlike_soft(mol)Soft lead-like
rule_of_druglike_soft(mol)Soft drug-like
rule_of_generative_design(mol)Generative design space
rule_of_generative_design_strict(mol)Strict generative design
Raw lines 48 to 76
77### Descriptor helpers
79```python
80mc.rules.list_descriptors() # property names for query language
81```
83---

Descriptor helpers

mc.rules.list_descriptors()  # property names for query language

Raw lines 77 to 84
85## Module: medchem.structural

Module: medchem.structural

Raw lines 85 to 86
87### Class: CommonAlertsFilters
89ChEMBL-derived structural alert filter sets (Glaxo, Dundee, BMS, etc.).
91```python
92CommonAlertsFilters(alerts_set=["BMS", "Dundee", "Glaxo"], alerts_db_path=None)
93```
95The source defaults to **BMS only**, despite an older constructor docstring saying BMS+Dundee+Glaxo. Select sets explicitly.
97**Returns DataFrame columns:** `mol`, `pass_filter`, `status`, `reasons`
99- `status`: `"exclude"` or `"ok"` in the current common-alert implementation
100- `pass_filter`: bool — True if compound passes
102**Methods:**
104```python
105list_default_available_alerts() # DataFrame of alert definitions
106__call__(mols, n_jobs=-1, progress=False, ...) -> pd.DataFrame
107```

Class: CommonAlertsFilters

ChEMBL-derived structural alert filter sets (Glaxo, Dundee, BMS, etc.).

CommonAlertsFilters(alerts_set=["BMS", "Dundee", "Glaxo"], alerts_db_path=None)

The source defaults to BMS only, despite an older constructor docstring saying BMS+Dundee+Glaxo. Select sets explicitly.

Returns DataFrame columns: mol, pass_filter, status, reasons

  • status: "exclude" or "ok" in the current common-alert implementation
  • pass_filter: bool — True if compound passes

Methods:

list_default_available_alerts()  # DataFrame of alert definitions
__call__(mols, n_jobs=-1, progress=False, ...) -> pd.DataFrame
Raw lines 87 to 108
109### Class: NIBRFilters
111Novartis screening-deck curation filters ([Schuffenhauer et al., J. Med. Chem. 2020](https://dx.doi.org/10.1021/acs.jmedchem.0c01332)).
113```python
114NIBRFilters()
115```
117**Returns DataFrame columns:** `mol`, `pass_filter`, `status`, `severity`, `reasons`, `n_covalent_motif`, `special_mol`
119- `severity`: sum of flag scores; any explicit exclusion alert yields 10.
120- `status`: `"exclude"`, `"flag"`, `"annotations"`, or `"ok"`.
121- Class `pass_filter` rejects explicit exclusions only. Combine it with `severity < 10` to also reject accumulated flags. For prevalidated molecules, `functional.nibr_filter(max_severity=10)` supplies the severity cutoff. The functional wrapper checks severity only and can admit invalid inputs (reported by the class with severity zero); never use it as a parse-validity check.

Class: NIBRFilters

Novartis screening-deck curation filters (Schuffenhauer et al., J. Med. Chem. 2020).

NIBRFilters()

Returns DataFrame columns: mol, pass_filter, status, severity, reasons, n_covalent_motif, special_mol

  • severity: sum of flag scores; any explicit exclusion alert yields 10.
  • status: "exclude", "flag", "annotations", or "ok".
  • Class pass_filter rejects explicit exclusions only. Combine it with severity < 10 to also reject accumulated flags. For prevalidated molecules, functional.nibr_filter(max_severity=10) supplies the severity cutoff. The functional wrapper checks severity only and can admit invalid inputs (reported by the class with severity zero); never use it as a parse-validity check.
Raw lines 109 to 122
123### Lilly demerits (optional)
125Requires native tools installed with `medchem install-lilly` (C++/make/zlib/Ruby, source download and build; WSL on Windows). This native installation/execution was not exercised locally. Access via:
127```python
128mc.functional.lilly_demerit_filter(mols, max_demerits=160, n_jobs=-1)
129# or
130from medchem.structural.lilly_demerits import LillyDemeritsFilters
131```
133---

Lilly demerits (optional)

Requires native tools installed with medchem install-lilly (C++/make/zlib/Ruby, source download and build; WSL on Windows). This native installation/execution was not exercised locally. Access via:

mc.functional.lilly_demerit_filter(mols, max_demerits=160, n_jobs=-1)
# or
from medchem.structural.lilly_demerits import LillyDemeritsFilters

Raw lines 123 to 134
135## Module: medchem.functional
137High-level boolean-mask API. **True = passes** (no alert / passes all rules). `return_idx=True` returns indices instead of the default NumPy mask. `alert_filter` uses the names from `CommonAlertsFilters.list_default_available_alerts()`; it does not validate unknown names, which can silently create an empty alert set. For `brenk` and `pains_a`, use `catalog_filter`. Its string interface deliberately rejects `nibr` and `bredt`; use `nibr_filter` and `bredt_filter` respectively. Raw NIBR catalog matching ignores severity and includes annotations.
139| Function | Description |
140|----------|-------------|
141| `rules_filter(mols, rules, n_jobs=None, ...)` | Apply rule list |
142| `nibr_filter(mols, max_severity=10, n_jobs=None, ...)` | NIBR filter |
143| `alert_filter(mols, alerts, alerts_db=None, n_jobs=1, ...)` | Common-alert collection names, case-insensitive |
144| `catalog_filter(mols, catalogs, n_jobs=-1, ...)` | Named catalogs or RDKit FilterCatalog objects |
145| `complexity_filter(mols, complexity_metric="bertz", limit="99", ...)` | Complexity threshold |
146| `lilly_demerit_filter(mols, max_demerits=160, ...)` | Lilly demerits (optional) |
147| `chemical_group_filter(mols, chemical_group, ...)` | Exclude group matches |
148| `bredt_filter(mols, ...)` | Bredt instability filter |
149| `macrocycle_filter(mols, ...)` | Macrocycle filter |
150| `protecting_groups_filter(mols, ...)` | Protecting group filter |
151| `ring_infraction_filter(mols, ...)` | Ring infraction filter |
152| `symmetry_filter(mols, ...)` | Symmetry filter |
154---
156In 2.1.1, `bredt_filter` kekulizes supplied RDKit objects in place, changing later aromatic substructure/PAINS matches. Preserve the shared input molecules by copying them:
158```python
159from rdkit import Chem
160passes_bredt = mc.functional.bredt_filter([Chem.Mol(mol) for mol in mol_list], n_jobs=1)
161```

Module: medchem.functional

High-level boolean-mask API. True = passes (no alert / passes all rules). return_idx=True returns indices instead of the default NumPy mask. alert_filter uses the names from CommonAlertsFilters.list_default_available_alerts(); it does not validate unknown names, which can silently create an empty alert set. For brenk and pains_a, use catalog_filter. Its string interface deliberately rejects nibr and bredt; use nibr_filter and bredt_filter respectively. Raw NIBR catalog matching ignores severity and includes annotations.

FunctionDescription
rules_filter(mols, rules, n_jobs=None, ...)Apply rule list
nibr_filter(mols, max_severity=10, n_jobs=None, ...)NIBR filter
alert_filter(mols, alerts, alerts_db=None, n_jobs=1, ...)Common-alert collection names, case-insensitive
catalog_filter(mols, catalogs, n_jobs=-1, ...)Named catalogs or RDKit FilterCatalog objects
complexity_filter(mols, complexity_metric="bertz", limit="99", ...)Complexity threshold
lilly_demerit_filter(mols, max_demerits=160, ...)Lilly demerits (optional)
chemical_group_filter(mols, chemical_group, ...)Exclude group matches
bredt_filter(mols, ...)Bredt instability filter
macrocycle_filter(mols, ...)Macrocycle filter
protecting_groups_filter(mols, ...)Protecting group filter
ring_infraction_filter(mols, ...)Ring infraction filter
symmetry_filter(mols, ...)Symmetry filter

In 2.1.1, bredt_filter kekulizes supplied RDKit objects in place, changing later aromatic substructure/PAINS matches. Preserve the shared input molecules by copying them:

from rdkit import Chem
passes_bredt = mc.functional.bredt_filter([Chem.Mol(mol) for mol in mol_list], n_jobs=1)
Raw lines 135 to 162
163## Module: medchem.catalogs

Module: medchem.catalogs

Raw lines 163 to 164
165### NamedCatalogs
167Static methods returning RDKit `FilterCatalog` objects:
169```python
170mc.catalogs.list_named_catalogs()
171# tox, pains, pains_a, pains_b, pains_c, nih, zinc, brenk, dundee, bms,
172# glaxo, schembl, mlsmr, inpharmatica, lint, nibr, bredt, toxicophore, ...
174mc.catalogs.NamedCatalogs.pains()
175mc.catalogs.NamedCatalogs.brenk()
176mc.catalogs.NamedCatalogs.nibr()
177mc.catalogs.NamedCatalogs.bredt()
178```
180**Helpers:**
182```python
183catalog_from_smarts(smarts_list)
184merge_catalogs(*catalogs)
185list_named_catalogs()
186```
188---

NamedCatalogs

Static methods returning RDKit FilterCatalog objects:

mc.catalogs.list_named_catalogs()
# tox, pains, pains_a, pains_b, pains_c, nih, zinc, brenk, dundee, bms,
# glaxo, schembl, mlsmr, inpharmatica, lint, nibr, bredt, toxicophore, ...

mc.catalogs.NamedCatalogs.pains()
mc.catalogs.NamedCatalogs.brenk()
mc.catalogs.NamedCatalogs.nibr()
mc.catalogs.NamedCatalogs.bredt()

Helpers:

catalog_from_smarts(smarts_list)
merge_catalogs(*catalogs)
list_named_catalogs()

Raw lines 165 to 189
190## Module: medchem.groups

Module: medchem.groups

Raw lines 190 to 191
192### ChemicalGroup
194Detect functional groups from the global-chem curated library.
196```python
197ChemicalGroup(groups=None, n_jobs=None, groups_db=None)
198```
200**Methods:**
202```python
203has_match(mol, exact_match=False, terminal_only=False) -> bool
204get_matches(mol, use_smiles=True, exact_match=False, terminal_only=False) -> pd.DataFrame # None for invalid input
205filter(names, fuzzy=False) -> ChemicalGroup # narrows pattern names in place
206get_catalog(exact_match=True) -> FilterCatalog
207list_groups() -> list
208list_hierarchy_groups() -> list
209```
211Group collections contain substructure patterns, not whole-molecule classifications: `amino_acids` includes side-chain fragments that can match ethanol. Some collections contain unparseable SMILES patterns (observed for `electrophilic_warheads_for_kinases`); inspect `group.dataframe` and validate `mol`/`mol_smarts` before choosing a representation or curating a custom database.
213**Listing helpers:**
215```python
216mc.groups.list_default_chemical_groups(hierarchy=False)
217mc.groups.list_functional_group_names(unique=True)
218mc.groups.get_functional_group_map() # name → SMARTS
219```
221---

ChemicalGroup

Detect functional groups from the global-chem curated library.

ChemicalGroup(groups=None, n_jobs=None, groups_db=None)

Methods:

has_match(mol, exact_match=False, terminal_only=False) -> bool
get_matches(mol, use_smiles=True, exact_match=False, terminal_only=False) -> pd.DataFrame  # None for invalid input
filter(names, fuzzy=False) -> ChemicalGroup  # narrows pattern names in place
get_catalog(exact_match=True) -> FilterCatalog
list_groups() -> list
list_hierarchy_groups() -> list

Group collections contain substructure patterns, not whole-molecule classifications: amino_acids includes side-chain fragments that can match ethanol. Some collections contain unparseable SMILES patterns (observed for electrophilic_warheads_for_kinases); inspect group.dataframe and validate mol/mol_smarts before choosing a representation or curating a custom database.

Listing helpers:

mc.groups.list_default_chemical_groups(hierarchy=False)
mc.groups.list_functional_group_names(unique=True)
mc.groups.get_functional_group_map()  # name → SMARTS

Raw lines 192 to 222
223## Module: medchem.complexity

Module: medchem.complexity

Raw lines 223 to 224
225### Class: ComplexityFilter
227Compare a metric to ZINC-15 percentile thresholds. Operates on **single molecules**.
229```python
230ComplexityFilter(
231 limit="99",
232 complexity_metric="bertz",
233 threshold_stats_file="zinc_15_available",
235cf(mol) # -> bool
236```
238**Available metrics** (`ComplexityFilter.list_default_available_filters()`):
239`bertz`, `sas`, `qed`, `clogp`, `whitlock`, `barone`, `smcm`, `twc`, `spacialscore`.
240The default ZINC-15 table supports the first eight; `spacialscore` requires a custom threshold CSV.
241`limit` must be a provided table label: `median`, `90`, `99`, `999`, or `max` for ZINC-15; `999` is 99.9%. Thresholds depend on molecular-weight bins. The implementation uses `score < threshold` for every metric, including QED, and treats a NaN score as a pass. Validate finiteness separately when needed.
243**Direct metric functions:**
245```python
246mc.complexity.WhitlockCT(mol)
247mc.complexity.BaroneCT(mol)
248mc.complexity.SMCM(mol)
249mc.complexity.TWC(mol)
250mc.complexity.SPS(mol) # normalized SpacialScore
251```
253For batch filtering, use `mc.functional.complexity_filter()`.
255---

Class: ComplexityFilter

Compare a metric to ZINC-15 percentile thresholds. Operates on single molecules.

ComplexityFilter(
    limit="99",
    complexity_metric="bertz",
    threshold_stats_file="zinc_15_available",
)
cf(mol)  # -> bool

Available metrics (ComplexityFilter.list_default_available_filters()): bertz, sas, qed, clogp, whitlock, barone, smcm, twc, spacialscore. The default ZINC-15 table supports the first eight; spacialscore requires a custom threshold CSV. limit must be a provided table label: median, 90, 99, 999, or max for ZINC-15; 999 is 99.9%. Thresholds depend on molecular-weight bins. The implementation uses score < threshold for every metric, including QED, and treats a NaN score as a pass. Validate finiteness separately when needed.

Direct metric functions:

mc.complexity.WhitlockCT(mol)
mc.complexity.BaroneCT(mol)
mc.complexity.SMCM(mol)
mc.complexity.TWC(mol)
mc.complexity.SPS(mol)  # normalized SpacialScore

For batch filtering, use mc.functional.complexity_filter().


Raw lines 225 to 256
257## Module: medchem.constraints

Module: medchem.constraints

Raw lines 257 to 258
259### Class: Constraints
261Scaffold-based substructure matching with per-atom constraint functions — **not** simple property-range filters.
263```python
264Constraints(core: Mol, constraint_fns: Dict[str, Callable], prop_name: str = "query")
265constraints(mol) # -> bool
266constraints.get_matches(mol) # tuple of matching atom-index tuples
267```
269A constraint is active only when a core atom has `prop_name` set to a key in `constraint_fns`. Each function receives **one extracted side-chain molecule** and returns a boolean. See the executable example in `SKILL.md`; a three-argument lambda on an unannotated core never applies the intended check. Use `RuleFilters` or the query language for MW/LogP/TPSA bounds.
271---

Class: Constraints

Scaffold-based substructure matching with per-atom constraint functions — not simple property-range filters.

Constraints(core: Mol, constraint_fns: Dict[str, Callable], prop_name: str = "query")
constraints(mol)  # -> bool
constraints.get_matches(mol)  # tuple of matching atom-index tuples

A constraint is active only when a core atom has prop_name set to a key in constraint_fns. Each function receives one extracted side-chain molecule and returns a boolean. See the executable example in SKILL.md; a three-argument lambda on an unannotated core never applies the intended check. Use RuleFilters or the query language for MW/LogP/TPSA bounds.


Raw lines 259 to 272
273## Module: medchem.query

Module: medchem.query

Raw lines 273 to 274
275### Class: QueryFilter
277Parse and evaluate the medchem query language.
279```python
280QueryFilter(query: str, grammar: Optional[str] = None, parser: str = "lalr")
281qf(mols, n_jobs=-1, progress=True, scheduler="processes") -> list[bool]
282```
284**Grammar constructs:**
286| Construct | Example |
287|-----------|---------|
288| Rule match | `MATCHRULE("rule_of_five")` |
289| Alert catalog | `HASALERT("pains")` |
290| Property compare | `HASPROP("mw", <, 500)` |
291| Functional-group name | `HASGROUP("Primary amines")` |
292| Substructure | `HASSUBSTRUCTURE("c1ccccc1")` |
293| Superstructure | `HASSUPERSTRUCTURE("CCO")` |
294| Boolean | `true`, `false` |
295| Logic | `AND`, `OR`, `NOT` |
297**Example queries:**
299```python
300'MATCHRULE("rule_of_five") AND NOT HASALERT("pains")'
301'MATCHRULE("rule_of_cns") AND HASPROP("tpsa", <=, 90)'
302'NOT HASALERT("brenk") AND HASPROP("mw", >=, 200)'
303```
305`HASGROUP` accepts exact map keys from `mc.groups.get_functional_group_map()`, not collection names from `list_default_chemical_groups()`. Some aliases from `list_functional_group_names()` (for example `primary_amine`) are missing from the map in 2.1.1 and raise `KeyError`; use a map key such as `Primary amines`. `HASSUPERSTRUCTURE("CCO")` tests whether the input molecule is contained in the query molecule. Invalid inputs should be separated before evaluating queries.

Class: QueryFilter

Parse and evaluate the medchem query language.

QueryFilter(query: str, grammar: Optional[str] = None, parser: str = "lalr")
qf(mols, n_jobs=-1, progress=True, scheduler="processes") -> list[bool]

Grammar constructs:

ConstructExample
Rule matchMATCHRULE("rule_of_five")
Alert catalogHASALERT("pains")
Property compareHASPROP("mw", <, 500)
Functional-group nameHASGROUP("Primary amines")
SubstructureHASSUBSTRUCTURE("c1ccccc1")
SuperstructureHASSUPERSTRUCTURE("CCO")
Booleantrue, false
LogicAND, OR, NOT

Example queries:

'MATCHRULE("rule_of_five") AND NOT HASALERT("pains")'
'MATCHRULE("rule_of_cns") AND HASPROP("tpsa", <=, 90)'
'NOT HASALERT("brenk") AND HASPROP("mw", >=, 200)'

HASGROUP accepts exact map keys from mc.groups.get_functional_group_map(), not collection names from list_default_chemical_groups(). Some aliases from list_functional_group_names() (for example primary_amine) are missing from the map in 2.1.1 and raise KeyError; use a map key such as Primary amines. HASSUPERSTRUCTURE("CCO") tests whether the input molecule is contained in the query molecule. Invalid inputs should be separated before evaluating queries.

Raw lines 275 to 306
307### Class: QueryOperator
309Holds available properties, catalogs, rules, and functional groups used by the parser.
311---

Class: QueryOperator

Holds available properties, catalogs, rules, and functional groups used by the parser.


Raw lines 307 to 312
313## Common Patterns

Common Patterns

Raw lines 313 to 314
315### Parallel processing
317```python
318df = mc.rules.RuleFilters(rule_list=["rule_of_five"])(mols=mol_list, n_jobs=-1, progress=True)
319mask = mc.functional.nibr_filter(mols=mol_list, n_jobs=-1)
320```

Parallel processing

df = mc.rules.RuleFilters(rule_list=["rule_of_five"])(mols=mol_list, n_jobs=-1, progress=True)
mask = mc.functional.nibr_filter(mols=mol_list, n_jobs=-1)
Raw lines 315 to 321
322### Combining filters
324```python
325rules_df = mc.rules.RuleFilters(rule_list=["rule_of_five"])(mols=mol_list, n_jobs=-1)
326alerts_df = mc.structural.CommonAlertsFilters(alerts_set=["BMS", "Dundee", "Glaxo"])(mols=mol_list, n_jobs=-1)
328passing = [
329 mol for i, mol in enumerate(mol_list)
330 if rules_df.iloc[i]["pass_all"] and alerts_df.iloc[i]["pass_filter"]
332```

Combining filters

rules_df = mc.rules.RuleFilters(rule_list=["rule_of_five"])(mols=mol_list, n_jobs=-1)
alerts_df = mc.structural.CommonAlertsFilters(alerts_set=["BMS", "Dundee", "Glaxo"])(mols=mol_list, n_jobs=-1)

passing = [
    mol for i, mol in enumerate(mol_list)
    if rules_df.iloc[i]["pass_all"] and alerts_df.iloc[i]["pass_filter"]
]
Raw lines 322 to 333
334### Working with DataFrames
336```python
337import pandas as pd
338import datamol as dm
339import medchem as mc
341df = pd.read_csv("molecules.csv")
342df["mol"] = df["smiles"].apply(
343 lambda value: dm.to_mol(value) if isinstance(value, str) and value.strip() else None
345invalid = df[df["mol"].isna()].copy()
346df = df[df["mol"].notna()].reset_index(drop=True)
347# Keep invalid separately with original source IDs; these are parse failures.
349results = mc.rules.RuleFilters(rule_list=["rule_of_five", "rule_of_cns"])(
350 mols=df["mol"].tolist(), n_jobs=-1
352df = pd.concat([df, results.drop(columns=["mol"])], axis=1)
353filtered = df[df["pass_all"]]
354```
357Upstream NIBR 2.1.1 also converts the catalog special-molecule flag with `bool(text)`; a nonempty `"0"` therefore becomes true. Treat `special_mol` as unreliable metadata until the raw catalog entry is checked, rather than evidence of a peptide or glycoside.

Working with DataFrames

import pandas as pd
import datamol as dm
import medchem as mc

df = pd.read_csv("molecules.csv")
df["mol"] = df["smiles"].apply(
    lambda value: dm.to_mol(value) if isinstance(value, str) and value.strip() else None
)
invalid = df[df["mol"].isna()].copy()
df = df[df["mol"].notna()].reset_index(drop=True)
# Keep invalid separately with original source IDs; these are parse failures.

results = mc.rules.RuleFilters(rule_list=["rule_of_five", "rule_of_cns"])(
    mols=df["mol"].tolist(), n_jobs=-1
)
df = pd.concat([df, results.drop(columns=["mol"])], axis=1)
filtered = df[df["pass_all"]]

Upstream NIBR 2.1.1 also converts the catalog special-molecule flag with bool(text); a nonempty "0" therefore becomes true. Treat special_mol as unreliable metadata until the raw catalog entry is checked, rather than evidence of a peptide or glycoside.

Raw lines 334 to 358
359## Upstream verification sources
361- [Release 2.1.1](https://github.com/datamol-io/medchem/releases/tag/2.1.1) and [2.1 migration](https://medchem-docs.datamol.io/stable/migration.html)
362- [Structural APIs](https://medchem-docs.datamol.io/stable/api/medchem.structural.html), [functional APIs](https://medchem-docs.datamol.io/stable/api/medchem.functional.html), [catalog APIs](https://medchem-docs.datamol.io/stable/api/medchem.catalogs.html)
363- [Groups](https://medchem-docs.datamol.io/stable/api/medchem.groups.html), [complexity](https://medchem-docs.datamol.io/stable/api/medchem.complexity.html), [constraints](https://medchem-docs.datamol.io/stable/api/medchem.constraints.html), [queries](https://medchem-docs.datamol.io/stable/api/medchem.query.html)
364- [Versioned source](https://github.com/datamol-io/medchem/tree/2.1.1/medchem) resolves docstring discrepancies. No remote service endpoints, authentication, or pagination are used by these local APIs. Native Lilly filtering remains an unexecuted optional integration.

Upstream verification sources

Raw lines 359 to 364