Metadata-Version: 2.4
Name: molprim
Version: 0.1.0
Summary: Primitive structure extraction and graph kernels for molecular graph classification, with a scikit-learn API
Author: Peemapat Wongsriphisant
License-Expression: BSD-3-Clause
Project-URL: Homepage, https://github.com/PeemapatW/primitive-structure-extraction
Project-URL: Repository, https://github.com/PeemapatW/primitive-structure-extraction
Project-URL: Issues, https://github.com/PeemapatW/primitive-structure-extraction/issues
Keywords: graph classification,graph kernels,cheminformatics,molecular graphs,scikit-learn
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Science/Research
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
Classifier: Topic :: Scientific/Engineering :: Chemistry
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: joblib
Requires-Dist: networkx>=3.1
Requires-Dist: numpy
Requires-Dist: scikit-learn>=1.0
Requires-Dist: scipy
Provides-Extra: test
Requires-Dist: pytest; extra == "test"
Dynamic: license-file

# molprim

**Mol**ecular **prim**itives: classify molecular graphs by their **primitive
structures**, that is, rings, branching points and bonds. The package rewrites
each molecule as a small graph whose vertices are these structures, then
compares molecules with graph kernels.
Everything is a scikit-learn transformer, so it plugs into `Pipeline`,
`GridSearchCV` and `cross_val_score`.

This is the implementation of:

> P. Wongsriphisant, C. Lursinsap, A. Suratanee and K. Plaimas,
> "A classification of biochemical compounds based on their primitive structures and graph kernels", IEEE, 2020, pp. 104–109.

## Install

```bash
pip install molprim
```

Requires Python ≥ 3.9 with networkx, numpy, scipy and scikit-learn. It needs neither graph-tool nor grakel.

## Quickstart

```python
from sklearn.model_selection import cross_val_score
from sklearn.pipeline import make_pipeline
from sklearn.svm import SVC

from molprim import GraphKernelTransformer, PrimitiveStructureExtractor, fetch_tudataset

graphs, y = fetch_tudataset("MUTAG")  # list of networkx.Graph with a "label" node attribute

model = make_pipeline(
    PrimitiveStructureExtractor(),
    GraphKernelTransformer(kernel="wl_sp", n_iter=2),
    SVC(kernel="precomputed"),
)
print(cross_val_score(model, graphs, y, cv=5).mean())
```

Inputs are lists of undirected `networkx.Graph` whose nodes carry the atom type
in a node attribute (`"label"` by default; change it with `node_label=`).
For a fuller example, see [`examples/quickstart.py`](examples/quickstart.py).

## How it works

1. **Selection** (`PrimitiveStructureExtractor.fit`): collects every labelled
   cycle (3–10 atoms), star (3–7 atoms) and bond that occurs in the training
   molecules, and keeps those whose frequency is at least the `threshold`
   percentile. Bonds are always kept.
2. **Extraction** (`PrimitiveStructureExtractor.transform`): finds the
   primitives in each molecule, largest cycles first, then stars, then bonds.
   An occurrence that shares more than half of its atoms with one already taken
   is skipped. Each kept occurrence becomes a vertex labelled by its primitive
   (e.g. `"C6-1"`, the most frequent 6-ring). Two vertices are joined when
   their occurrences share an atom.
3. **Similarity**: either a graph kernel on the primitive graphs, or a count of
   adjacent primitive pairs fed to an RBF SVM.

| Component | Purpose |
|---|---|
| `PrimitiveStructureExtractor` | molecules → primitive graphs |
| `GraphKernelTransformer` | Weisfeiler-Lehman subtree (`"wl_subtree"`), WL shortest-path (`"wl_sp"`) or shortest-path (`"sp"`) kernel. `fit_transform` returns the training kernel matrix and `transform` the kernel against the training graphs, for `SVC(kernel="precomputed")`. Values match grakel's `GraphKernel`. |
| `LabelPairEdgeCounter` | counts edges per pair of end labels (the paper's "Edges" measure) |
| `fetch_tudataset`, `read_tudataset` | load TU Dortmund benchmark datasets (MUTAG, NCI1, BZR, COX2, …) as networkx graphs |

Tune the extraction like any other hyper-parameter, for example
`GridSearchCV(model, {"primitivestructureextractor__threshold": [0, 50], "graphkerneltransformer__n_iter": [1, 2, 3]})`.

## Implementation notes

- Primitives are learned in `fit`, from the training graphs only.
- Candidate cycles, stars and bonds are enumerated directly and identified by
  a canonical form of their labels, rather than with general subgraph
  isomorphism. Extracting the primitive graphs of all 3,865 NCI1 molecules
  takes a few seconds.
- When occurrences overlap, the one kept is chosen deterministically: larger
  shapes first, then more frequent primitives, then vertex order.

## Benchmarks

Scripts that evaluate the package on TU datasets (MUTAG, BZR, COX2, NCI1),
with their per-split results and runtimes, are in [`benchmarks/`](benchmarks/).

## Repository layout

```
src/molprim/               the package
tests/                     pytest suite
examples/                  usage examples
benchmarks/                benchmark scripts and results of this implementation
legacy/                    original notebooks and results from the 2020 paper (see legacy/README.md)
```

## Development

```bash
pip install -e ".[test]"
pytest
```

## License

BSD-3-Clause, the same as scikit-learn. See [LICENSE](LICENSE).
