Skip to content
Merged

Dev #473

Show file tree
Hide file tree
Changes from all 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
64 changes: 57 additions & 7 deletions .github/workflows/packaging.yml
Original file line number Diff line number Diff line change
Expand Up @@ -126,24 +126,54 @@ jobs:
retention-days: 7

install:
name: Install the wheel on Python ${{ matrix.python-version }}
name: Install the wheel on Python ${{ matrix.python-version }} (${{ matrix.os }})
needs: build
runs-on: ubuntu-latest
timeout-minutes: 20
runs-on: ${{ matrix.os }}
# Every platform only downloads wheels here: assembly-theory, the one
# dependency that would have to be compiled, is not installed on Windows.
timeout-minutes: 40

defaults:
run:
# The steps below glob a wheel path and use a heredoc, neither of which
# PowerShell provides. Git Bash is on every runner image.
shell: bash

env:
# A fixed, platform-independent location for the cache step below to
# restore into. pip's own default differs per operating system.
PIP_CACHE_DIR: ${{ github.workspace }}/.pip-cache

strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
# Oldest and newest supported interpreters. A dependency with no wheel
# yet on the newest one fails here rather than on a user's
# `pip install assemblytheorytools`.
python-version: ["3.12", "3.14"]
include:
# The published wheel is pure Python, so what a second platform
# checks is the dependency set: that every runtime import resolves
# off PyPI alone, without a compiler or a system library.
- os: windows-latest
python-version: "3.13"

steps:
- uses: actions/setup-python@v7
with:
python-version: ${{ matrix.python-version }}

- name: Restore pip's wheel store
# The dependency set is large (rdkit, scipy, matplotlib), and this job
# installs it from scratch on three runners. There is no checkout here
# to hash, so the key names the distributions instead.
uses: actions/cache@v6
with:
path: ${{ env.PIP_CACHE_DIR }}
key: pip-${{ runner.os }}-${{ matrix.python-version }}-${{ needs.build.outputs.version }}
restore-keys: pip-${{ runner.os }}-${{ matrix.python-version }}-

- name: Retrieve the built distributions
uses: actions/download-artifact@v8
with:
Expand All @@ -159,16 +189,20 @@ jobs:
- name: Smoke-test the installed package
# There is deliberately no checkout, so the import below can only
# resolve to the installed wheel and not to a source tree beside it.
# The calculation uses the Rust backend, which arrives as a declared
# dependency, so this job proves the wheel installs and computes
# without needing a C++ toolchain. The C++ backend is covered by
# Off Windows the calculation uses the Rust backend, which arrives as a
# declared dependency, so this job proves the wheel installs and
# computes without needing a C++ toolchain. On Windows that dependency
# is excluded by a marker, so the same call has to fail with the
# ImportError that names it: this is the only job that exercises that
# path on the platform it exists for. The C++ backend is covered by
# tests.yml, which builds it from parallelassemblycpp.
env:
MPLBACKEND: Agg
EXPECTED_VERSION: ${{ needs.build.outputs.version }}
run: |
python - <<'PY'
import os
import sys

from rdkit import Chem

Expand All @@ -177,7 +211,23 @@ jobs:
expected = os.environ["EXPECTED_VERSION"]
assert att.__version__ == expected, f"{att.__version__} != {expected}"

index = att.calculate_assembly_index_rust(Chem.MolFromSmiles("CCO"))
ethanol = Chem.MolFromSmiles("CCO")

if sys.platform == "win32":
try:
att.calculate_assembly_index_rust(ethanol)
except ImportError as error:
assert "assembly-theory" in str(error), error
else:
raise AssertionError(
"assembly-theory is installed on Windows, where its sdist "
"cannot build; the dependency marker has stopped working."
)
print(f"assemblytheorytools {att.__version__} installs; "
"the Rust backend reports itself absent, as it should")
sys.exit(0)

index = att.calculate_assembly_index_rust(ethanol)
assert isinstance(index, int) and index >= 0, index

print(f"assemblytheorytools {att.__version__} installs and runs; MA(ethanol) = {index}")
Expand Down
28 changes: 22 additions & 6 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,17 @@ jobs:
test:
name: Python ${{ matrix.python-version }} on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
timeout-minutes: 30
# Generous for Windows, which builds the C++ calculator from source with
# MSVC on a cold cache. The other platforms finish in a few minutes.
timeout-minutes: 60

defaults:
run:
# Windows would otherwise run these in PowerShell, which only reports
# the last command's exit status, so a failing install would be noticed
# one step too late. Git Bash is on the image and keeps every job's
# steps identical.
shell: bash

strategy:
# One failing interpreter should not hide the result on the others.
Expand All @@ -70,12 +80,17 @@ jobs:
os: [ubuntu-latest]
python-version: ["3.12", "3.13", "3.14"]
include:
# Smoke-test the other supported platform. macOS runners are arm64,
# which is the only macOS wheel assembly-theory publishes. There is
# no Windows job: the package is POSIX-only (see the classifiers in
# pyproject.toml) and assembly-theory ships no Windows wheel.
# Smoke-test the other supported platforms. macOS runners are arm64,
# which is the only macOS wheel assembly-theory publishes.
- os: macos-latest
python-version: "3.13"
# Windows is the slow one: the C++ calculator is built by MSVC
# through CMake's Visual Studio generator rather than Ninja. It
# lands in a cache, so only a cold run pays full price. The Rust
# backend is not installed there (see pyproject.toml), so the tests
# that need it skip themselves.
- os: windows-latest
python-version: "3.13"

steps:
- uses: actions/checkout@v7
Expand All @@ -93,7 +108,8 @@ jobs:
# does not supply that library. The Linux images already ship it; on
# macOS Homebrew installs outside dyld's default search path, so the
# fallback path has to name it or the import fails with OSError and the
# metro-plot test skips itself.
# metro-plot test skips itself. Windows has no comparable one-line
# install, so there the metro-plot test takes that skip.
if: runner.os == 'macOS'
run: |
brew install cairo
Expand Down
13 changes: 12 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,16 @@ New work should not reduce existing coverage. See the
[test-suite guide](https://github.com/ELIFE-ASU/assemblytheorytools/blob/main/tests/README.md)
for marker and plotting details.

## Linting

The `lint` group installs [Ruff](https://docs.astral.sh/ruff/). Run it from the
repository root before opening a pull request; CI runs the same rule set,
configured in `pyproject.toml`:

```console
ruff check .
```

## Documentation

The documentation lives in `docs/` and is published at
Expand Down Expand Up @@ -137,13 +147,14 @@ python -m twine check --strict dist/*
```

The build uses an isolated environment with the backend requirements declared
in `pyproject.toml`. The artifacts are written to `dist/`.
in `pyproject.toml`. The artefacts are written to `dist/`.

## Pull request checklist

- [ ] The change is focused and excludes unrelated refactoring.
- [ ] New behaviour and bug fixes have tests.
- [ ] The relevant default, integration, or slow test groups pass.
- [ ] `ruff check .` passes.
- [ ] Coverage does not decrease.
- [ ] Public APIs have type hints and NumPy-style docstrings.
- [ ] User-facing behaviour is documented.
Expand Down
40 changes: 29 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ python -m pip install assemblytheorytools

> **Platform note:** The C++ calculator is not distributed as a binary. The first calculation that needs it builds
> [parallelassemblycpp](https://github.com/ELIFE-ASU/parallelassemblycpp) from source into `~/.cache/assemblytheorytools`, which
> takes a few minutes and needs a C++20 compiler. To use a build you already have, set `ASS_PATH` instead; see
> takes a few minutes and needs `git` and a C++20 compiler. To use a build you already have, set `ASS_PATH` instead; see
> **Use your own parallelassemblycpp build** below.

Calculate and plot the assembly pathway for caffeine:
Expand Down Expand Up @@ -86,7 +86,8 @@ Assembly index: 9
- `ai` is the assembly index.
- `virtual_objects` contains reusable intermediates found along the pathway. The collection is unordered; do not rely on
positional order.
- `pathway` is a NetworkX `DiGraph`: its nodes are virtual objects and its directed edges are joining operations.
- `pathway` is a NetworkX `DiGraph` whose nodes are the virtual objects and the joining steps. Each node carries its
object in a `vo` attribute; the node ids themselves are labels such as `virtual_object_3` and `step_5`.

Convert the virtual-object graphs back to SMILES with:

Expand Down Expand Up @@ -119,14 +120,18 @@ quantity ATT computes, with its inputs, its outputs, and what it is used for.
| Assembly `A` | `calculate_assembly` | Graphs and their copy numbers | Ensemble assembly value |
| Assembly depth | `calculate_assembly_depth_rust` | NetworkX graph or RDKit `Mol` | Minimum depth under concurrent joins |
| Bounds | `calculate_assembly_index_upper_bound`, `calculate_assembly_index_lower_bound` | NetworkX graph or RDKit `Mol` | Instant bounds for screening |
| Many indices at once | `calculate_assembly_index_parallel` | List of graphs | Indices, virtual objects, pathways |
| Assembly index from tandem MS | `MAEstimator` | Fragmentation tree and molecular weight | Monte Carlo samples of MA |
| Assembly index from IR peaks | `estimate_ai_from_ir_peaks` | Peak counts and reference indices | Fitted model and predicted indices |
| Many indices at once | `calculate_assembly_index_parallel` | List of graphs plus a settings dictionary (required; pass `None` for the defaults) | Indices, virtual objects, pathways |
| Assembly index *estimated* from tandem MS | `MAEstimator` | Fragmentation tree and molecular weight | Monte Carlo samples of MA |
| Assembly index *estimated* from IR peaks | `estimate_ai_from_ir_peaks` | Peak counts, reference indices, a model function and a starting parameter guess | Fitted model and predicted indices |

The last two rows are heuristic estimates from measured spectra, not exact calculations; report their spread.
| Other complexity scores | `bertz_complexity`, `bottcher`, `wiener_index`, and more | RDKit `Mol` | Score, for comparison against the index |

## What ATT includes

- Exact assembly-index calculations for molecules, arbitrary labelled graphs, and directed or undirected strings.
The search is exponential in the worst case, so a default 100-second timeout applies; on a timeout the calculation
returns the best upper bound it reached, and `exact=True` makes it return `-1` instead.
- Default C++ and alternative Rust search interfaces, plus fast graph bounds and CFG-based string approximations.
- Joint assembly, parallel execution, pathway parsing, pathway visualisation, and alternative-path enumeration.
- Molecular complexity metrics, structure conversion, reassembly, crystal-cell, spectroscopy, and mass-spectrometry
Expand All @@ -139,13 +144,19 @@ quantity ATT computes, with its inputs, its outputs, and what it is used for.
| --- | --- | --- | --- |
| parallelassemblycpp (C++) | `calculate_assembly_index` | Default molecule and graph calculations | Index, virtual objects, and pathway |
| assembly-theory (Rust) | `calculate_assembly_index_rust` | Fast molecular index calculations | Index |
| assembly-theory search (Rust) | `calculate_assembly_index_rust_search` | Search statistics, options, and supported pathway reconstruction | Structured search result |
| Graph bounds | `calculate_assembly_index_upper_bound` and `calculate_assembly_index_lower_bound` | Fast size-based estimates | Upper or lower bound |
| assemblycfg | String calculations with `mode="cfg"` | Fast approximate string calculations | Upper bound and pathway |
| assembly-theory search (Rust) | `calculate_assembly_index_rust_search` | Search statistics, options, and pathway reconstruction | Structured search result |
| assemblycfg | `calculate_string_assembly_index(..., mode="cfg")` | Fast approximate string calculations | Upper bound and pathway |

The analytic bounds `calculate_assembly_index_upper_bound` and
`calculate_assembly_index_lower_bound` are not a backend: they are pure Python
formulas that invoke no calculator at all.

The Rust backend always strips hydrogens. For a meaningful comparison, compare it with
`calculate_assembly_index(..., strip_hydrogen=True)`.

The Rust backend is unavailable on Windows, where `assembly-theory` cannot be installed; its four functions raise
`ImportError` there, and every other backend works.

The PyPI distribution ships no parallelassemblycpp binary. ATT checks `ASS_PATH`, then looks for `ParallelAssemblyCpp`
(or the older `AssemblyCpp`) on `PATH`, then in its own cache, and builds one from source if it finds none. A single executable covers molecules,
graphs and strings. See [configuration](https://assemblytheorytools.readthedocs.io/en/latest/configuration.html) for
Expand All @@ -159,6 +170,12 @@ The one-line PyPI install above resolves ATT's runtime dependencies. The authori
versions live in
[`pyproject.toml`](https://github.com/ELIFE-ASU/assemblytheorytools/blob/main/pyproject.toml).

Two dependencies need more than pip can supply on its own: `assembly-theory` is not installed on Windows at all,
because it has no wheel there and its Rust source distribution does not build with MSVC (Intel macOS does build it,
and needs a Rust toolchain), and `cairosvg` needs the Cairo system library. The
[installation guide](https://assemblytheorytools.readthedocs.io/en/latest/install.html) covers both, along with conda,
HPC and build-from-source instructions.

<details>
<summary><strong>Install from source for development</strong></summary>

Expand Down Expand Up @@ -291,6 +308,7 @@ The second line prints `1`, the assembly index of hydrogen-stripped ethanol.

| Resource | Description |
| --- | --- |
| [Installation](https://assemblytheorytools.readthedocs.io/en/latest/install.html) | Platform notes, conda, HPC and building the C++ calculator |
| [Route map](https://assemblytheorytools.readthedocs.io/en/latest/route_map.html) | Every ATT quantity with its inputs, outputs, and applications |
| [Concepts](https://assemblytheorytools.readthedocs.io/en/latest/concepts.html) | Assembly indices, virtual objects, pathways, joint assembly, and backends |
| [User guide](https://assemblytheorytools.readthedocs.io/en/latest/guide/index.html) | Molecules, strings, graphs, pathways, parallel runs, complexity, and mass spectrometry |
Expand Down Expand Up @@ -343,9 +361,9 @@ Python dependencies.
1. Sharma, A., Czégel, D., Lachmann, M., Kempes, C. P., Walker, S. I., & Cronin, L. (2023). Assembly theory explains
and quantifies selection and evolution. *Nature*, 622(7982), 321–328.
[doi:10.1038/s41586-023-06600-9](https://doi.org/10.1038/s41586-023-06600-9)
2. Seet, I., Patarroyo, K. Y., Siebert, G., Walker, S. I., & Cronin, L. (2024). Rapid computation of the assembly index
of molecular graphs. *arXiv preprint*, arXiv:2410.09100.
[doi:10.48550/arXiv.2410.09100](https://doi.org/10.48550/arXiv.2410.09100)
2. Seet, I., Patarroyo, K. Y., Siebert, G., Walker, S. I., & Cronin, L. (2025). Rapid exploration of the assembly
chemical space of molecular graphs. *Journal of Chemical Information and Modeling*, 65(24), 13203–13214.
[doi:10.1021/acs.jcim.5c01964](https://doi.org/10.1021/acs.jcim.5c01964)
3. Vimal, D., Parzych, G., Smith, O. M., Parkar, D., Bergen, H., Daymude, J. J., & Mathis, C. (2026).
assembly-theory: Open, reproducible calculation of assembly indices. *Journal of Open Source Software*, 11(117),
9318. [doi:10.21105/joss.09318](https://doi.org/10.21105/joss.09318)
Expand Down
12 changes: 10 additions & 2 deletions assemblytheorytools/README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,10 @@
This is the main directory for the Assembly Theory Tools project.
Here you will find all the source code and documentation related to the project.
# `assemblytheorytools`

The package source. Each module is documented in the
[API reference](https://assemblytheorytools.readthedocs.io/en/latest/modules.html);
the narrative documentation lives in `docs/`, and runnable examples in
`examples/`.

`data/` holds the two files shipped with the package: the precomputed
integer-chain lookup table behind `calculate_integer_chain`, and the reference
molecule set the test suite checks against.
Loading
Loading