Metadata-Version: 2.5
Name: harmonsmile
Version: 0.3.3
Summary: Toolkit for harmonizing SMILES strings to canonical + isomeric + Kekulized convention (RDKit)
Project-URL: Homepage, https://github.com/NanoBiostructuresRG/harmonsmile
Project-URL: Repository, https://github.com/NanoBiostructuresRG/harmonsmile
Project-URL: Issues, https://github.com/NanoBiostructuresRG/harmonsmile/issues
Project-URL: Documentation, https://nanobiostructuresrg.github.io/harmonsmile
Project-URL: Changelog, https://github.com/NanoBiostructuresRG/harmonsmile/blob/main/CHANGELOG.md
Author-email: "Flavio F. Contreras-Torres" <contreras.flavio@tec.mx>
License-Expression: LGPL-3.0-or-later
License-File: COPYING
License-File: COPYING.LESSER
License-File: LICENSE
Keywords: ChEMBL,PubChem,RDKit,SMILES,cheminformatics,drug-discovery
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
Classifier: Topic :: Scientific/Engineering :: Chemistry
Requires-Python: >=3.11
Requires-Dist: openpyxl>=3.1
Requires-Dist: pandas>=2.0
Requires-Dist: rdkit>=2022.09
Requires-Dist: requests>=2.28
Provides-Extra: dev
Requires-Dist: build>=1.0; extra == 'dev'
Requires-Dist: mypy>=1.11; extra == 'dev'
Requires-Dist: pytest>=7.0; extra == 'dev'
Requires-Dist: ruff>=0.16; extra == 'dev'
Requires-Dist: twine>=4.0; extra == 'dev'
Provides-Extra: docs
Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
Requires-Dist: mkdocs>=1.6; extra == 'docs'
Requires-Dist: mkdocstrings[python]>=0.25; extra == 'docs'
Description-Content-Type: text/markdown

# HARMONSMILE: Harmonize SMILES Strings for Cheminformatics and Machine Learning

[![License: LGPL v3](https://img.shields.io/badge/License-LGPL_v3-blue.svg)](LICENSE)
[![Version](https://img.shields.io/badge/version-v0.3.3-blue.svg)](https://pypi.org/project/harmonsmile/)
[![PyPI](https://img.shields.io/pypi/v/harmonsmile.svg)](https://pypi.org/project/harmonsmile/)
[![Python](https://img.shields.io/pypi/pyversions/harmonsmile.svg)](https://pypi.org/project/harmonsmile/)
[![Docs](https://img.shields.io/badge/docs-GitHub%20Pages-teal.svg)](https://nanobiostructuresrg.github.io/harmonsmile/)

---

## Description

**HARMONSMILE** solves a common problem in cheminformatics: SMILES strings for the same
molecule look different depending on the source (PubChem, ChEMBL, COCONUT, in-house
databases). This inconsistency breaks comparisons, deduplication, and machine learning
pipelines that expect a uniform molecular representation.

It is intended for computational chemists, cheminformatics researchers, ML
practitioners preparing molecular datasets, and maintainers integrating
PubChem, ChEMBL, and in-house sources.

---

## Purpose

The primary objective of **HARMONSMILE** is to automate the preparation,
standardization, and harmonization of molecular structure tables for
cheminformatics workflows and machine learning applications within the
computational drug discovery pipeline.

The platform enables:

* **Source-aware molecular table preparation**: Ingests molecular identifiers or
  SMILES from PubChem, ChEMBL, and independent tabular datasets while preserving
  source traceability.
* **Deterministic identifier handling**: Uses robust, alias-aware input detection
  for PubChem CIDs and emits a canonical `PubChem_CID` output column for
  downstream interoperability.
* **RDKit canonicalization**: Generates `SMILES_RDKit` as a
  canonical/isomeric/Kekulé RDKit representation, preserving compatibility with
  workflows that use Kekulized SMILES for deduplication or comparison.
* **SMILES harmonization**: Generates `SMILES_Harmonized` as a
  canonical/isomeric/aromatic representation after controlled RDKit-native
  standardization and harmonization.
* **Auditable status reporting**: Reports harmonization outcomes through
  `SMILES_Harmonization_Status` and `SMILES_Harmonization_Message`,
  distinguishing successful harmonization, controlled transformations,
  unsupported ambiguous structures, and failures.
* **Cross-source comparison**: Preserves structural bridge fields such as
  `InChI` and `InChIKey` to support comparison across PubChem, ChEMBL, and
  independent molecular databases.

---

## SMILES Column Contract

HARMONSMILE exposes three universal SMILES representations in pipeline
outputs:

- `SMILES`: source/input SMILES. This is the value used as input for RDKit
  canonicalization and lab harmonization.
- `SMILES_RDKit`: RDKit canonical + isomeric + Kekulized representation,
  produced by `RDKitStandardizer.to_iso_kek(SMILES)`. This column is preserved
  for compatibility with v0.2.5. It is not a full chemical harmonization layer
  and does not intentionally desalt, neutralize, reionize, or canonicalize
  tautomers.
- `SMILES_Harmonized`: lab-harmonized representation, produced by
  `RDKitStandardizer.to_lab_harmonized(SMILES)`. It uses an RDKit-native
  `MolStandardize` policy with validation before automatic modification,
  controlled parent generation for simple salts/counterions, normalization,
  uncharging, reionization, optional tautomer canonicalization, and final
  canonical/isomeric/aromatic serialization.

`SMILES_Harmonized` is intended for database harmonization, deduplication, and
cross-source matching. It is not guaranteed to represent the most stable, most
abundant, pH-specific, or biologically active tautomer. HARMONSMILE does not
export tautomer ensembles; it stores one canonical harmonized representation per
input row. Source traceability is preserved through `SMILES` and `SMILES_RDKit`.

Rows are not dropped when harmonization fails. Instead, the result is carried in:

- `SMILES_Harmonization_Status`: short filterable status returned by the
  harmonization engine: `ok`, `ok_with_warnings`, `unsupported`, or `failed`.
- `SMILES_Harmonization_Message`: short auditable message for warnings,
  unsupported structures, or failures; empty/None for successful `ok` rows.

Some sources may also provide source-specific SMILES columns. For example,
`ConnectivitySMILES` is a PubChem-provided connectivity SMILES column preserved
when available; it is not generated by HARMONSMILE and is not present for all
sources.

---

## Installation

### For package users

Create and activate a Python environment:

```bash
conda create -n harmonsmile_env python=3.11
conda activate harmonsmile_env
```

Install **HARMONSMILE** from PyPI:

```bash
pip install harmonsmile
```

RDKit (`rdkit>=2022.09`) is a required runtime dependency and is installed
automatically as part of the package dependencies.

### For contributors and developers

Clone the repository and install in editable mode with development
dependencies:

```bash
git clone https://github.com/NanoBiostructuresRG/harmonsmile.git
cd harmonsmile
conda create -n harmonsmile_env python=3.11
conda activate harmonsmile_env
python -m pip install -e ".[dev]"
```

To build the documentation locally, install the `docs` extra as well:

```bash
python -m pip install -e ".[dev,docs]"
mkdocs serve
```

---

## Quick Start

This quickstart walks through the entry points of HARMONSMILE: standardizing a
single SMILES string in memory, and running the three pipelines — two
identifier-driven (PubChem, ChEMBL) and one for any table that already contains
SMILES. Each pipeline reads an input table, resolves and harmonizes structures,
and returns a `pandas.DataFrame` carrying the SMILES column contract described
above.

> **Example data.** The commands below use the datasets in `examples/`, which
> ship with the repository but not with the PyPI wheel. To run them as written,
> clone the repository and work from its root:
>
> ```bash
> git clone https://github.com/NanoBiostructuresRG/harmonsmile.git
> cd harmonsmile
> ```
>
> To use your own data instead, point `input_path` at any CSV, TSV, TXT, XLSX,
> XLSM, or XLS file with the columns listed under [Input Format](#input-format).

### Python API

Standardize a single SMILES string, without any input file. The three methods
show the difference between the compatibility layer and the harmonization
layer:

```python
from harmonsmile import RDKitStandardizer

std = RDKitStandardizer()
print(std.to_iso_kek("c1ccccc1"))    # 'C1=CC=CC=C1'  canonical + isomeric + Kekulized
print(std.to_conn_kek("c1ccccc1"))   # 'C1=CC=CC=C1'  connectivity only, no stereochemistry

result = std.to_lab_harmonized("c1ccccc1")
print(result.value)                  # 'c1ccccc1'     lab-harmonized, aromatic
print(result.status)                 # 'ok'
```

`to_lab_harmonized` returns a `HarmonizationResult` with `value`, `status`,
`error`, and `warning` fields, rather than a bare string.

Fetch properties from PubChem and harmonize. The input needs a CID column;
`PubChem_CID` is canonical, and `PubChem CID`, `PubChemCID`, and `CID` are
accepted as aliases:

```python
from harmonsmile import PubChemIngest, PubChemConfig, save_table

cfg = PubChemConfig(
    input_path="examples/example_pubchem.csv",   # columns: id, PubChem_CID
)
df = PubChemIngest(cfg).run()
save_table(df, "results/example_pubchem_harmonized.csv")
```

Fetch properties from ChEMBL and harmonize:

```python
from harmonsmile import ChEMBLIngest, ChEMBLConfig, save_table

cfg = ChEMBLConfig(
    input_path="examples/example_chembl.csv",    # columns: id, ChEMBL ID
)
df = ChEMBLIngest(cfg).run()
save_table(df, "results/example_chembl_harmonized.csv")
```

Harmonize any table that already contains SMILES (COCONUT, in-house, etc.).
The example file is tab-delimited, and the SMILES column may carry any name:

```python
from harmonsmile import SMILESPrep, SMILESConfig, save_table

cfg = SMILESConfig(
    input_path="examples/example_smiles.txt",    # columns: id, name, SMILES
    smiles_col="SMILES",
)
df = SMILESPrep(cfg).run()
save_table(df, "results/example_smiles_harmonized.csv")
```

Pipeline `.run()` methods return a DataFrame and never write to disk.
Persistence is explicit through `save_table(df, path)`, which always writes CSV.

### Command-Line Interface

The CLI covers the same pipelines and writes output files directly:

```bash
# PubChem batch
harmonsmile --pubchem-in  examples/example_pubchem.csv \
            --pubchem-out results/example_pubchem_harmonized.csv

# ChEMBL batch
harmonsmile --chembl-in  examples/example_chembl.csv \
            --chembl-out results/example_chembl_harmonized.csv

# SMILES batch (COCONUT, in-house, etc.)
harmonsmile --smiles-in  examples/example_smiles.txt --smiles-col SMILES \
            --smiles-out results/example_smiles_harmonized.csv

# Several pipelines in one call
harmonsmile \
  --pubchem-in  examples/example_pubchem.csv \
  --pubchem-out results/example_pubchem_harmonized.csv \
  --smiles-in   examples/example_smiles.txt --smiles-col SMILES \
  --smiles-out  results/example_smiles_harmonized.csv

# Single entry - fetch and harmonize one compound by identifier
# Output is written to results/ automatically
harmonsmile --pubchem-cid 2723949
harmonsmile --chembl-id CHEMBL294199

# Check version
harmonsmile --version
```

When the identifier column carries a non-standard name, declare it explicitly:

```bash
harmonsmile --pubchem-in  data/my_table.csv --pubchem-cidcol "compound_cid" \
            --pubchem-out results/out.csv

harmonsmile --chembl-in  data/my_table.csv --chembl-idcol "chembl_identifier" \
            --chembl-out results/out.csv
```

Also available as a module:

```bash
python -m harmonsmile --pubchem-in  examples/example_pubchem.csv \
                      --pubchem-out results/out.csv
```

Run `harmonsmile --help` for the full argument reference.

### Next steps

- [SMILES Column Contract](#smiles-column-contract) — what each output column
  means and how to filter rows on harmonization status.
- [Usage guide](https://nanobiostructuresrg.github.io/harmonsmile/usage/) —
  longer worked examples.
- [API reference](https://nanobiostructuresrg.github.io/harmonsmile/api/) —
  full signatures for every public class and function.
- `examples/fetch_pubchem.py` and `examples/fetch_chembl.py` — build larger
  input tables from a free-text query before running a pipeline.

---

## Pipelines

| Pipeline | Config | Source | Input | API |
|---|---|---|---|---|
| `PubChemIngest` | `PubChemConfig` | PubChem | Table with a `PubChem_CID` column | REST (public) |
| `ChEMBLIngest` | `ChEMBLConfig` | ChEMBL | Table with a `ChEMBL ID` column | REST (public) |
| `SMILESPrep` | `SMILESConfig` | Any | Table with any SMILES column | Local file |

All pipelines preserve the source `SMILES`, append `SMILES_RDKit`, and append
the lab harmonization columns `SMILES_Harmonized`,
`SMILES_Harmonization_Status`, and `SMILES_Harmonization_Message`.
Pipeline `.run()` methods return a `pandas.DataFrame` and do not write files.
Use `save_table(df, path)` to persist results from Python, or the CLI
`--*-out` options.

---

## Input Format

| Pipeline | Required columns | Column option |
|---|---|---|
| `PubChemIngest` | `id` (optional), `PubChem_CID` | `cid_col` / `--pubchem-cidcol` |
| `ChEMBLIngest` | `id` (optional), `ChEMBL ID` | `chembl_id_col` / `--chembl-idcol` |
| `SMILESPrep` | `id` (optional), `<smiles_col>` (any name) | `smiles_col` / `--smiles-col` |

`PubChemIngest` accepts `PubChem_CID` (canonical), `PubChem CID`, `PubChemCID`,
and `CID` as input aliases, and always emits `PubChem_CID` in the output.

Supported file formats: CSV, TSV, TXT, XLSX, XLSM, XLS. `.csv` is read as
comma-delimited and `.tsv` / `.txt` as tab-delimited, deterministically and
without delimiter sniffing.

---

## Roadmap

Released:

- **v0.2.x** — RDKit canonicalization contract (`SMILES_RDKit`), DataFrame-only
  pipeline returns with `save_table` as the single write boundary.
- **v0.3.x** — RDKit-native lab harmonization layer (`SMILES_Harmonized`) with
  auditable per-row status reporting, and deterministic alias-aware PubChem CID
  handling emitting a canonical `PubChem_CID` column.

Deliberately out of scope: molecular fingerprint generation and descriptor
calculation, which are handled downstream rather than inside HARMONSMILE.
HARMONSMILE stops at the harmonized structure table.

---

## Development

### Project Structure

```text
HARMONSMILE/
|-- harmonsmile/
|   |-- __init__.py        # Public API
|   |-- __main__.py        # python -m harmonsmile entry point
|   |-- _cli.py            # CLI implementation
|   |-- chembl.py          # ChEMBL REST client
|   |-- config.py          # PubChemConfig, ChEMBLConfig, SMILESConfig dataclasses
|   |-- io.py              # Table I/O utilities (load_table, save_table)
|   |-- pipelines.py       # PubChemIngest, ChEMBLIngest, SMILESPrep
|   |-- pubchem.py         # PubChem REST client
|   |-- standardize.py     # RDKitStandardizer, HarmonizationResult
|   `-- version.py         # Package version metadata
|-- tests/                 # Unit test suite (pytest) - 187 tests
|-- examples/              # Example datasets and fetch scripts
|-- docs/                  # MkDocs sources (index, usage, api, changelog)
|-- pyproject.toml
|-- mkdocs.yml
|-- requirements-dev.txt
|-- CHANGELOG.md
|-- CITATION.cff
|-- CODE_OF_CONDUCT.md
|-- CONTRIBUTING.md
|-- COPYING
|-- COPYING.LESSER
|-- LICENSE
`-- README.md
```

### Running Tests

```bash
python -m pytest tests -p no:cacheprovider --basetemp .pytest_tmp
```

### Contributing

Contributions are welcome. Please open an issue before submitting a pull
request. Follow the existing code style: NumPy-style docstrings, type hints,
and SPDX license headers in all source files.

See [CONTRIBUTING.md](CONTRIBUTING.md) for full guidelines, including the
development setup and the pull request target branch.
Please also read our [Code of Conduct](CODE_OF_CONDUCT.md).

---

## Citation

If you use HARMONSMILE in your research, please cite it using the metadata in
[CITATION.cff](CITATION.cff) or the format below:

```text
Contreras-Torres, F. F. (2026). HARMONSMILE: Harmonize SMILES Strings for
Cheminformatics and Machine Learning. Zenodo. https://doi.org/10.5281/zenodo.20275498
```

---

## Author

Developed by **Flavio F. Contreras-Torres** (Tecnologico de Monterrey)
Monterrey, Mexico

---

## License

This project is licensed under the terms of the
[GNU Lesser General Public License v3.0 or later](LICENSE).
SPDX identifier: `LGPL-3.0-or-later`.

---

## Statement on AI Assistance

I used conversational AI assistance (Claude, Anthropic) during the development
and maintenance of HARMONSMILE. Claude Code was also used to audit the
repository against packaging requirements; it reported findings, and I applied
the resulting changes myself.

The assistance was primarily conversational and focused on documentation,
packaging configuration, dependency management, and incremental refinement. It
also covered small, localized code changes such as type annotations, variable
naming, and automated import ordering. I engaged with the tool as I would with
a technical colleague — asking questions, discussing trade-offs, and iterating
on solutions.

The scientific design — including the harmonization policy, column contracts,
pipeline boundaries, and reproducibility strategy — is mine. I made the
decisions about what the tool should do, how it should behave, and what
constitutes a correct result.

I reviewed all changes before integration, ran the test suite, and take full
responsibility for the final output. The repository history shows incremental
commits, CI runs, and a detailed CHANGELOG, reflecting iterative development
rather than generated output.
