Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
194 changes: 34 additions & 160 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# bluenamer

A deterministic SMILES → IUPAC name generator implementing the rules of the
A deterministic SMILES → IUPAC name generator derived from the rules of the
IUPAC Blue Book (2013 recommendations).

The package walks the molecular graph (parsed via RDKit), perceives functional
Expand All @@ -11,7 +11,7 @@ decision trace so the *why* of a name is recoverable, not just the *what*.
> **Status:** alpha. The naming engine handles a broad slice of organic
> structures (alkanes/alkenes/alkynes, common functional groups, simple
> heterocycles, fused/spiro/bridged systems, retained names from the Blue
> Book). PubChem/QM9 coverage is being measured; see `examples/`.
> Book). PubChem/QM9/ZINC22 coverage is being measured; see `examples/`.

## Install

Expand Down Expand Up @@ -84,6 +84,18 @@ analysis = analyze_smiles("CC(=O)Nc1ccccc1")
for step in analysis.decisions:
print(step.phase, step.decision, step.reason)
```
### CLI

```bash
bluenamer name "CC(=O)Nc1ccccc1" # → N-phenylacetamide
bluenamer name "CC(=O)Nc1ccccc1" --json # JSON with trace + rules
bluenamer batch smiles.txt --output names.jsonl --processes auto
```
The CLI tool has a OPSIN verification turned on by default. It can be turned-off with
Comment thread
sourcery-ai[bot] marked this conversation as resolved.
Outdated

```bash
bluenamer name "CN1C=NC2=C1C(=O)N(C(=O)N2C)C" --no-verify
```

### Natural-language description (`describe`)

Expand All @@ -103,12 +115,27 @@ d.components[0] # DescribedComponent(phase='parse', text='RDKit parsed ...')

Same input → same output. No LLM in the loop.

### CLI
## Human-like description

Bluenamer can generate uncanny human-like descriptions of molecules.
```python

from bluenamer import describe_human

d = describe_human("CN1C=NC2=C1C(=O)N(C(=O)N2C)C")
print(d.text)

""" Processed SMILES: Cn1cnc2c1c(=O)n(C)c(=O)n2C
Atom ids in that SMILES: C{0}n{1}1c{2}n{3}c{4}2c{5}1c{6}(=O{7})n{8}(C{13})c{9}(=O{10})n{11}2C{12}

The molecule is named 2,4,7-trimethyl-2,4,7,9-tetraazabicyclo[4.3.0]nona-1(6),8-diene-3,5-dione.

The molecule is built around a 9-membered bicyclic [4.3.0] heteroskeleton.
Within that parent framework, there is nitrogen at positions 2 (atom id 11), 4 (atom id 8), 7 (atom id 1), and 9 (atom id 3).
Within that parent framework, there is a double bond between position 1 (atom id 4) and position 6 (atom id 5) and a double bond between position 8 (atom id 2) and position 9 (atom id 3).
The principal characteristic feature is oxo groups at positions 3 (atom id 9) and 5 (atom id 6).
Attached to this framework are methyl groups at positions 2 (atom id 11), 4 (atom id 8), and 7 (atom id 1). """

```bash
bluenamer name "CC(=O)Nc1ccccc1" # → N-phenylacetamide
bluenamer name "CC(=O)Nc1ccccc1" --json # JSON with trace + rules
bluenamer batch smiles.txt --output names.jsonl --processes auto
```

## Development
Expand Down Expand Up @@ -165,159 +192,6 @@ curl -X POST localhost:8000/describe -H 'content-type: application/json' \
OpenAPI docs are served at `http://localhost:8000/docs`.


## Human-like description

OpenBlue can generate uncanny human-like descriptions.
```python

from bluenamer import describe_human

d = describe_human("CN1C=NC2=C1C(=O)N(C(=O)N2C)C")
print(d.text)

""" Processed SMILES: Cn1cnc2c1c(=O)n(C)c(=O)n2C
Atom ids in that SMILES: C{0}n{1}1c{2}n{3}c{4}2c{5}1c{6}(=O{7})n{8}(C{13})c{9}(=O{10})n{11}2C{12}

The molecule is named 2,4,7-trimethyl-2,4,7,9-tetraazabicyclo[4.3.0]nona-1(6),8-diene-3,5-dione.

The molecule is built around a 9-membered bicyclic [4.3.0] heteroskeleton.
Within that parent framework, there is nitrogen at positions 2 (atom id 11), 4 (atom id 8), 7 (atom id 1), and 9 (atom id 3).
Within that parent framework, there is a double bond between position 1 (atom id 4) and position 6 (atom id 5) and a double bond between position 8 (atom id 2) and position 9 (atom id 3).
The principal characteristic feature is oxo groups at positions 3 (atom id 9) and 5 (atom id 6).
Attached to this framework are methyl groups at positions 2 (atom id 11), 4 (atom id 8), and 7 (atom id 1). """

```



## Debugging

Token binding metadata is currently available through the assembly decision trace when `include_trace=True`.

```python
from blunamer import name

result = name("C(C1C(C(C(C(O1)O)O)O)O)O", include_trace=True)

print(result.name)
# 6-(hydroxymethyl)oxane-2,3,4,5-tetraol
```

To inspect token spans:

```python
def name_token_spans(result):
for step in reversed(result.decisions):
if isinstance(step.data, dict) and "name_token_spans" in step.data:
return step.data["name_token_spans"]
return []


for token in name_token_spans(result):
print(
token["text"],
"atoms=", token["atoms"],
"bonds=", token["bonds"],
"kind=", token["token_kind"],
"confidence=", token["confidence"],
"source=", token["source"],
)
```

Example output:

```text
6 atoms= [0, 11] bonds= [1, 11] kind= locant confidence= derived source= typed_rewrite
hydroxymethyl atoms= [0, 11] bonds= [11] kind= prefix confidence= derived source= substituent_renderer
oxane atoms= [1, 2, 3, 4, 5, 6] bonds= [2, 3, 4, 5, 6, 12] kind= parent confidence= derived source= typed_rewrite
2,3,4,5 atoms= [2, 3, 4, 5, 7, 8, 9, 10] bonds= [3, 4, 5, 7, 8, 9, 10] kind= locant confidence= derived source= typed_rewrite
tetraol atoms= [2, 3, 4, 5, 7, 8, 9, 10] bonds= [3, 4, 5, 7, 8, 9, 10] kind= suffix confidence= derived source= renderer_suffix
```

The token metadata is split into `token_kind`, `ownership`, `confidence`, and `source`.

### `token_kind`

What grammar role the token plays.

| Value | Meaning / example |
| ---------------- | --------------------------------------------------------------------------- |
| `parent` | Parent skeleton token, e.g. `ethan`, `benzene`, `spiro[...]`. |
| `prefix` | Prefix/substituent token, e.g. `chloro`, `methyl`, `hydroxy`. |
| `suffix` | Principal suffix token, e.g. `acid`, `ol`, `one`, `nitrile`. |
| `locant` | Locant token, e.g. `2`, `1,3`, `N`, `4a`. |
| `charge` | Charge-bearing name part, e.g. `ium`, `oxide`, `ammonio`. |
| `hydro` | Indicated hydrogen or hydro operation, e.g. `1H`, `dihydro`. |
| `replacement` | Replacement prefix token, e.g. `oxa`, `aza`, `thia`. |
| `unsaturation` | Unsaturation token, e.g. `en`, `yn`, `diene`. |
| `modifier` | Front or suffix modifier token, e.g. stereo, hydro, or functional modifier. |
| `grammar` | Pure grammar token, e.g. `di`, `bis`, or parentheses-bridging particles. |
| `structural` | Structural token that does not fit a narrower kind. |
| `retained_alias` | Token matched as a retained-name alias or context term. |

### `ownership`

How the token claims graph atoms.

| Value | Meaning / example |
| ------------------------ | ------------------------------------------------------------------------------------------ |
| `exact` | Token was intentionally emitted for these atoms. Best case. |
| `preserves_binding` | Text matched a known binding directly after assembly. |
| `preserve_all` | Rewrite preserved all previous atom ownership. |
| `locanted_hydro` | Hydro token owns atoms through locants, e.g. `1H`. |
| `component_locant` | Locant belongs to a component namespace, often primed spiro/fused components. |
| `role_alias` | Token is an alias for a graph role. |
| `retained_alias_context` | Token matched retained parent alias context. |
| `stage_alias` | Token inferred from all bindings of a stage, e.g. generic suffix token. |
| `grammar_scope` | Grammar token applies to nearby graph-bound terms, not its own atom. |
| `multiplier_scope` | Multiplier token, e.g. `di`, scopes over repeated graph-bound terms. |
| `operation_scope` | Token recovered from a named operation trace. |
| `morphology_gap` | Token is a morphology bridge inside a compound token. |
| `ambiguous` | Best-effort broad binding. Diagnostic only. |
| `unbound` | No reliable graph binding found. Should be treated as a problem. |
| `absorbed` | Token was absorbed by a rewrite into another token. |
| rewrite-specific values | Values such as `retained_replace` or `merge_replaced_span`; these come from rewrite rules. |

### `confidence`

How strong the assignment is.

| Value | Meaning |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `exact` | Renderer emitted this token with atom metadata directly. |
| `derived` | Recovered from locants, rewrite history, context, or operation trace. |
| `fallback` | Best-effort binding. Useful for debugging, but not proof of correct graph naming. |

### `source`

Where the binding came from.

| Value | Meaning / example |
| ---------------------------------- | ---------------------------------------------------------------------------- |
| `renderer` | Direct renderer-emitted token. Strongest source. |
| `renderer_suffix` | Principal suffix renderer emitted it. |
| `functional_prefix_renderer` | Functional-prefix renderer emitted it. |
| `substituent_renderer` | Substituent renderer emitted it. |
| `default_binding` | Built from a broader `NameAtomBinding` when no finer token metadata existed. |
| `typed_rewrite` | Came through a typed post-processing rewrite; atom metadata was propagated. |
| `direct_text_match` | Token text directly matched an existing binding. |
| `locant_fallback` | Locant was matched back to a binding by locant metadata. |
| `charge_suffix_fallback` | Charge token inferred from charge suffix context. |
| `indicated_hydrogen_fallback` | `H` or indicated hydrogen inferred from hydro metadata. |
| `dihydro_locant_fallback` | Dihydro locants inferred from hydro operation metadata. |
| `primed_component_locant_fallback` | Primed/component locant inferred from component scope. |
| `role_alias_fallback` | Token matched a known role alias. |
| `retained_alias_context` | Token matched retained-name alias context. |
| `stage_fallback` | Token assigned to all bindings of a stage, e.g. parent, prefix, or suffix. |
| `grammar_token` | Pure grammar token. |
| `operation_trace` | Binding recovered from recorded naming operation. |
| `broad_fallback` | Last-resort plausible chemical token binding. Diagnostic only. |
| `unresolved` | No binding found. |
| `compound_gap_bridge` | Token bridges adjacent bound tokens inside a compound word. |
| `compound_gap_token` | Grammar-like token found inside a compound gap. |
| `compound_gap_unresolved` | Unresolved token inside a compound gap. |
| dynamic rewrite names | Any named rewrite can appear as a source if it changed the token. |

## License

MIT. See `LICENSE`.
134 changes: 0 additions & 134 deletions src/bluenamer/NAMER_REFACTOR.md

This file was deleted.

Loading
Loading