Metadata-Version: 2.4
Name: pypyrus-map
Version: 0.1.0
Summary: Metabolic neighborhood visualization for COBRApy genome-scale models
Author: Nourelden Rihan
License-Expression: MIT
Project-URL: Homepage, https://github.com/NoureldenRihan/PyPyrus-Map
Project-URL: Documentation, https://github.com/NoureldenRihan/PyPyrus-Map#readme
Project-URL: Bug Tracker, https://github.com/NoureldenRihan/PyPyrus-Map/issues
Keywords: metabolic engineering,synthetic biology,COBRApy,genome-scale model,flux balance analysis,metabolomics,bioinformatics,networkx
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Science/Research
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: cobra>=0.26
Requires-Dist: networkx>=3.0
Requires-Dist: matplotlib>=3.7
Requires-Dist: numpy>=1.24
Provides-Extra: graphviz
Requires-Dist: pygraphviz>=1.11; extra == "graphviz"
Provides-Extra: pydot
Requires-Dist: pydot>=2.0; extra == "pydot"
Provides-Extra: full
Requires-Dist: pygraphviz>=1.11; extra == "full"
Requires-Dist: pydot>=2.0; extra == "full"
Provides-Extra: dev
Requires-Dist: pytest>=7.4; extra == "dev"
Requires-Dist: pytest-cov>=4.1; extra == "dev"
Requires-Dist: black>=23.0; extra == "dev"
Requires-Dist: ruff>=0.1; extra == "dev"
Requires-Dist: mypy>=1.5; extra == "dev"
Dynamic: license-file

# PyPyrus Map

**Metabolic neighborhood visualization for COBRApy genome-scale models.**

PyPyrus Map lets you instantly visualize the full reaction neighborhood of any metabolite in a COBRApy-compatible genome-scale model, including heterologous and non-native pathways that curated databases like Escher cannot represent. Output is publication-quality vector figures (SVG, PDF) suitable for direct journal submission.

---

## Why PyPyrus Map exists

PyPyrus Map was built out of a concrete research problem during [Project Menhed](https://djosergenomics.github.io/Introducing-Project-Menhed/), an _in silico_ metabolic engineering effort to optimize lycopene production across multiple chassis organisms. When working with heterologous carotenoid biosynthesis pathways, standard tools like Escher were limited to pre-drawn maps, they could not show the neighborhood of a non-native intermediate like GGPP or Phytoene, making it difficult to quickly identify overexpression and knockout targets.

The need was simple: given a metabolite of interest, show every reaction that produces or consumes it, who the other substrates and products are, and when an FBA solution is available, what the flux through each reaction looks like. PyPyrus Map was written to answer that question in one function call.

---

## Installation

**Recommended (includes Graphviz for publication-quality layout):**

```bash
pip install "pypyrus-map[graphviz]"
```

**Minimal:**

```bash
pip install pypyrus-map
```

**System dependency for Graphviz layout** (required for `layout="dot"`):\*\*

```bash
# Ubuntu / Debian / Google Colab
apt-get install graphviz graphviz-dev
pip install pygraphviz

# macOS
brew install graphviz
pip install pygraphviz
```

**Install directly from GitHub:**

```bash
pip install git+https://github.com/NoureldenRihan/PyPyrus-Map.git
```

---

## Quick start

```python
import cobra
from pypyrus_map import PyPyrusMap

# Load any COBRApy-compatible genome-scale model
model = cobra.io.load_json_model("iML1515.json")
solution = model.optimize()

# Create a session and explore a metabolite neighborhood
session = PyPyrusMap(model, solution=solution)
session.add("ggpp_c")

fig = session.render(title="GGPP Neighborhood")
fig.show()

# Export publication-quality vector figures
session.export("ggpp_neighborhood.svg")
session.export("ggpp_neighborhood.pdf")
```

---

## Chaining metabolites

PyPyrus Map is designed for pathway chains. Call `.add()` multiple times to build up a connected pathway graph, shared nodes are never duplicated.

```python
session = PyPyrusMap(model, solution=solution)

# Build a carotenoid biosynthesis chain
session.add("ipdp_c")    # IPP neighborhood
session.add("frdp_c")   # FPP neighborhood neighborhood
# session.add("lycopene_c")  # works for heterologous and non native introduced metabolites too!

fig = session.render(
    title="Isoprenoid Pathway: IPP → FPP",
    orientation="portrait",
    label_mode="id",
)
session.export("isoprenoid_chain.svg")
```

When you call `session.add("frdp_c")`, PyPyrus Map queries FPP's full depth-1 neighborhood and merges it into the existing graph. The IPP node is already present, it gets promoted to anchor status rather than duplicated. Any other producers of Phytoene also appear automatically, because this is the full neighborhood, not just a single edge.

---

## Visualization options

```python
fig = session.render(
    layout="dot",              # 'dot' (default) | 'spring' | 'circular'
    orientation="landscape",   # 'landscape' (default) | 'portrait'
    figsize=None,              # auto-sized from node count; override with (width, height)
    title=None,                # auto-generated from anchor IDs if None
    label_mode="id",           # 'id' (default) | 'truncate' | 'full'
    show_flux_labels=False,    # show flux values (4 dp) on edge midpoints
    show_stoichiometry=False,  # show stoichiometric coefficients on edges
    dpi=110,                   # screen resolution; SVG/PDF always lossless
    font_family="DejaVu Sans", # 'Arial' matches Nature/Cell/Science guidelines
)
```

### Label modes

| Mode         | Shows                    | Best for                      |
| ------------ | ------------------------ | ----------------------------- |
| `"id"`       | BiGG ID (e.g. `ggpp_c`)  | Default, compact, unambiguous |
| `"truncate"` | Human name, max 20 chars | Readable without overlap      |
| `"full"`     | Full human name          | Presentations, wide figures   |

### Visual encoding

| Element                               | Meaning                           |
| ------------------------------------- | --------------------------------- |
| **Deep blue square**                  | Focal metabolite (anchor)         |
| **Sky blue square**                   | Neighbor metabolite               |
| **Amber diamond**                     | Reaction                          |
| **Amber diamond, thick black border** | Reaction with zero flux (blocked) |
| **Teal-green arrow**                  | Produces (reaction → metabolite)  |
| **Magenta-pink arrow**                | Consumes (metabolite → reaction)  |
| **Double-headed arrow**               | Reversible reaction               |
| **Colored arrow with black outline**  | Edge through a blocked reaction   |

---

## Flux overlay

When a `cobra.Solution` is passed at session construction, flux values are attached to each reaction node. Blocked reactions (flux ≈ 0) are visually distinguished by a thick black border on the reaction diamond and a black outline on their connecting arrows, visible even on short connections.

```python
solution = model.optimize()
session = PyPyrusMap(model, solution=solution)
session.add("ggpp_c")

# Show flux values on edges
fig = session.render(show_flux_labels=True)
```

---

## Currency metabolite suppression

High-connectivity cofactors (ATP, NADH, H₂O, CoA, Pi, etc.) connect nearly every reaction in the model to every other. By default, PyPyrus Map suppresses these from neighbor nodes to keep figures clean. The focal metabolite is always shown regardless.

```python
# Default: currency suppressed
session = PyPyrusMap(model)

# Show everything including currency metabolites
session = PyPyrusMap(model, suppress_currency=False)

# Custom currency list
my_currency = frozenset({"h_c", "h_e", "atp_c", "adp_c"})
session = PyPyrusMap(model, custom_currency_ids=my_currency)
```

The default currency ID list is available as `pypyrus_map.DEFAULT_CURRENCY_IDS` and follows the conventions of Huss & Holme (2007) and KEGG RPAIR.

---

## Error handling

PyPyrusMap uses strict BiGG IDs, no fuzzy matching. If an ID is not found, a clear error is raised with prefix-matched candidate suggestions:

```python
from pypyrus_map import MetaboliteNotFoundError

try:
    session.add("ggpp_m")   # wrong compartment
except MetaboliteNotFoundError as e:
    print(e)
# MetaboliteNotFoundError: 'ggpp_m' not found in model.
# Did you mean: ggpp_c?
```

---

## Session API

### `PyPyrusMap(model, solution=None, suppress_currency=True, custom_currency_ids=None)`

| Parameter             | Type             | Default  | Description                                              |
| --------------------- | ---------------- | -------- | -------------------------------------------------------- |
| `model`               | `cobra.Model`    | required | Any COBRApy-compatible genome-scale model                |
| `solution`            | `cobra.Solution` | `None`   | FBA solution - enables flux overlay                      |
| `suppress_currency`   | `bool`           | `True`   | Suppress high-connectivity cofactors from neighbor nodes |
| `custom_currency_ids` | `frozenset[str]` | `None`   | Replace the default currency list entirely               |

### Session methods

| Method                          | Returns        | Description                                                |
| ------------------------------- | -------------- | ---------------------------------------------------------- |
| `session.add(metabolite_id)`    | `self`         | Add a metabolite neighborhood. Chainable.                  |
| `session.remove(metabolite_id)` | `self`         | Remove a metabolite and its orphaned reactions. Chainable. |
| `session.clear()`               | `self`         | Reset the session graph entirely.                          |
| `session.render(**kwargs)`      | `Figure`       | Render and return a Matplotlib figure.                     |
| `session.export(path, dpi=300)` | `Path`         | Save figure to SVG, PDF, PNG, or TIFF.                     |
| `session.build()`               | `PathwayGraph` | Return the raw graph object for custom processing.         |
| `session.summary()`             | `str`          | Print a text summary of current session state.             |

---

## Development

```bash
git clone https://github.com/NoureldenRihan/PyPyrus-Map.git
cd PyPyrusMap
pip install -e ".[dev]"
pytest tests/ -v
```

---

## Citing PyPyrusMap

If PyPyrusMap contributes to a published work, please cite:

> Rihan, N. _PyPyrus Map: Metabolic neighborhood visualization for COBRApy genome-scale models._ GitHub, 2026. https://github.com/NoureldenRihan/PyPyrus-Map

A Zenodo DOI and formal citation file (CITATION.cff) will be added shortly.

---

## Acknowledgements

PyPyrusMap was developed with the assistance of Claude (Anthropic), a large language model used for code generation and implementation. All research direction, architectural decisions, feature design, and validation were conceived and directed by the author.

PyPyrus Map was built as part of [Project Menhed](https://djosergenomics.github.io/Introducing-Project-Menhed/), an _in silico_ metabolic engineering initiative targeting lycopene biosynthesis optimization.

---

## License

MIT License — Copyright (c) 2026 Nourelden Rihan. See [LICENSE](LICENSE) for details.
