Metadata-Version: 2.3
Name: plasbin-hmf
Version: 0.2.0
Summary: Plasmid binning in short read assembly graph.
License: # MIT License
         
         Copyright 2026 Victor Epain
         
         Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
         
         The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
         
         THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Requires-Dist: biopython>=1.87
Requires-Dist: click>=8.4.2
Requires-Dist: gfapy>=1.2.3
Requires-Dist: gurobipy>=13.0,<14.0
Requires-Dist: loguru>=0.7.3
Requires-Dist: networkx>=3.6.1
Requires-Dist: pydantic>=2.13.4
Requires-Dist: pyyaml>=6.0.3
Requires-Dist: typer>=0.27,<0.28.0
Requires-Python: >=3.13
Description-Content-Type: text/markdown

# A multiflow global optimization approach for plasmid binning in short read assembly graph: PlasBin-HMF

[![PyPI][pypi_badge]][pypi_link]
[![Coverage report][coverage_badge]][coverage_link]
[![ty][ty_badge]][ty_link]
[![Ruff][ruff_badge]][ruff_link]
[![uv][uv_badge]][uv_link]
[![Pipeline status][pipeline_badge]][pipeline_link]
[![Documentation][docs_badge]][docs_link]
[![License][license_badge]][licence_link]

## Install

> [!IMPORTANT]
> PlasBin-HMF relies on the commercial [Gurobi MILP solver](https://www.gurobi.com/).
> If you are an academic, you can get a free license, see <https://www.gurobi.com/academics#licenses>.

In a virtual environment:

```sh
pip install plasbin-hmf
```

Or if you want to install it via [uv]:

```sh
# As a dependency of your project:
uv add plasbin-hmf
# As an uv tool:
uv tool install plasbin-hmf
```

## Usage

In your virtual environment:

```sh
plasbin-hmf --help
```

You can also use [uv] tools:

```sh
uvx plasbin-hmf --help
```

To get the plasmid bins:

```sh
uvx plasbin-hmf run --help
```

### Options

Algorithm parameters can be given via CLI or you can use `--config config.yaml`:

```yaml
bins:
    circular:
        max_with_seeds: <int> | auto    # default: auto
        max_optional_seeds: <int>       # default: 2
    partially_circular:
        max_with_seeds: <int> | auto    # default: auto
        max_optional_seeds: <int>       # default: 0
constraints:
    min_bin_size: <int>                             # default: 1000
    min_coverage: <(0, inf)>                        # default: 0.3
    plasmidness_coefficient: <[0, 1]>               # default: 0
    max_coverage_ratio: <(1, inf]> | null           # default: 10
    partially_circular_s_t_constraints: <bool>      # default: false
input:
    input_format: PlasBin-HMF | PlasBin-flow    # default: PlasBin-HMF
    gfa_origin: Unicycler  | SKESA              # default: Unicycler
    default_plasmidness: <[-1, 1]>              # default: 0
    contig_length_threshold: <int>              # default: 100
output:
    keep_optimization_traces: <bool>    # default: false
    plasbin_flow_output: <bool>         # default: false
```

Gurobi can also be configured, via CLI of via `--gurobi-config gurobi_cfg.yaml`:

```yaml
mip_gap: <float>            # default: 0.0
time_limit: <float> | null  # default: null
threads: <int> | all        # default: all
```

## Input

PlasBin-HMF takes as input:

* An assembly graph (GFA v1 file produced by Unicycler or SKESA, see `--gfa-origin` option), can be compressed.
* A file listing the seed contigs
* A file associating to each contig a plasmidness score

Seed contigs (e.g. `seeds.tsv`, tabulation-separated values):

| Column ID   | Type  | Description                                                      |
| ----------- | ----- | ---------------------------------------------------------------- |
| `contig_id` | `str` | Contig ID (corresponds to the ID of the segment line in the GFA) |

Plasmidness scores (e.g. `plasmidness.tsv`, tabulation-separated values):

| Column ID           | Type    | Description                                                      |
| ------------------- | ------- | ---------------------------------------------------------------- |
| `contig_id`         | `str`   | Contig ID (corresponds to the ID of the segment line in the GFA) |
| `plasmidness_score` | `float` | Plasmidness score                                                |

> [!TIP]
> If you use [PlasBin-flow](https://github.com/cchauve/PlasBin-flow) (another plasmid binning tool), you can use the same seed and plasmidness files via the option `--input-format PlasBin-flow`.
> Otherwise (`--input-format PlasBin-HMF`), PlasBin-HMF expects the following files.

## Output

Suppose `--output-dir/-o output_dir` is provided.

PlasBin-HMF keeps your configuration for future reproducibility:

```sh
📂 output_dir
├── ℹ️ configs.yaml
└── ℹ️ gurobi_cfg.yaml
```

### Solution

```sh
📂 output_dir
├── ℹ️ solution_metadata.yaml
├── 📄 bins.tsv
└── 📂 subgraphs
    ├── 📄 link_arcs.tsv
    ├── 📄 source_arcs.tsv
    └── 📄 sink_arcs.tsv
```

> [!TIP]
> You can also get the extracted bins under the [PlasBin-flow] format with the option `--plasbin-flow-output`.
> In that case, `📂 output_dir` will also contain `📄 plasbin_flow_bins.tsv` file in the [PlasBin-flow] format

#### Predicted bins

The predicted bins are stored in the `📄 bins.tsv` file (tabulation separated columns):

| Column ID         | Type  | Description                                                                 |
| ----------------- | ----- | --------------------------------------------------------------------------- |
| `bin_id`          | `str` | Bin ID                                                                      |
| `contig_id`       | `str` | Contig ID in the bin (corresponds to the ID of the segment line in the GFA) |
| `contig_norm_cov` | `int` | Normalized coverage of the contig                                           |

> [!NOTE]
> A contig can belong to several bins.
> In that case there are multiple rows with the same `contig_id` value but different `bin_id`.

#### Solution networks

PlasBin-HMF uses the assembly graph topology to produce the bins.
Folder `📂 subgraphs` contains the arcs defining the subnetworks associated with the bins.

For each subnetwork (bin), the set of arcs is the union of:

* a set of link-arcs (arcs between two oriented contigs);
* a set of source-arcs (arcs from the source to the oriented contigs);
* a set of sink-arcs (arcs from the oriented contigs to the sink).

File `📄 link_arcs.tsv` contains the solution link arcs for all the subnetworks.
A link is an arc that corresponds to a link in the assembly graph (between two oriented contigs).

| Column ID | Type         | Description                                |
| --------- | ------------ | ------------------------------------------ |
| `bin_id`  | `str`        | Bin ID                                     |
| `pred_id` | `str`        | Predecessor oriented contig ID             |
| `pred_or` | `{'+', '-'}` | Predecessor oriented contig orientation    |
| `succ_id` | `str`        | Successor oriented contig ID               |
| `succ_or` | `{'+', '-'}` | Successor oriented contig orientation      |
| `flow`    | `float`      | Flow on the arc (always strictly positive) |

File `📄 source_arcs.tsv` contains the solution source arcs for all the subnetworks.

| Column ID | Type         | Description                           |
| --------- | ------------ | ------------------------------------- |
| `bin_id`  | `str`        | Bin ID                                |
| `succ_id` | `str`        | Successor oriented contig ID          |
| `succ_or` | `{'+', '-'}` | Successor oriented contig orientation |
| `flow`    | `float`      | Flow on the arc (can be 0)            |

File `📄 sink_arcs.tsv` contains the solution sink arcs for all the subnetworks

| Column ID | Type         | Description                             |
| --------- | ------------ | --------------------------------------- |
| `bin_id`  | `str`        | Bin ID                                  |
| `pred_id` | `str`        | Predecessor oriented contig ID          |
| `pred_or` | `{'+', '-'}` | Predecessor oriented contig orientation |
| `flow`    | `float`      | Flow on the arc (can be 0)              |

#### Solution metadata

File `ℹ️ solution_metadata.yaml` is a YAML stream.
Each document (separated by `---` at the top) describes one predicted bin:

```yaml
---
bin_id: <str> # e.g. Bin_1
property:
    topology: circular|partially_circular
    seed_constraint: required|optional
stats:
    cumulative_contig_length: <int>     # Do not account for the multiplicity of a contig
    plasmidness_score: <float>          # A good bin has a plasmidness score greater than 0
    minimum_positive_arc_flow: <float>  # This can be used as a coverage normalization factor
    maximum_contig_coverage: <float>    # Maximum contig coverage in the bin
origin:                 # only if --keep-optimization-traces
    instance_id: <str>  # e.g. instance_1
    multiflow_id: <str> # e.g. multiflow_2
    flow_id: <str>      # e.g. flow_1
```

#### If there is no solution

In case PlasBin-HMF was not able to find any bin under the parametrized constraints, the progam produces:

```sh
📂 output_dir
└── ℹ️ no_solution.yaml
```

Where `no_solution.yaml` contains:

```yaml
status: no_solution
```

### Optimization traces

If you provide `--keep-optimization-traces`, the optimization traces will be kept in the output directory.

```sh
📂 output_dir
└── 📂 optimization_traces
    ├── ℹ️ traces_metadata.yaml
    ├── 🗄️ instance_1.tar.xz # or .zip
    ├── 🗄️ instance_2.tar.xz # or .zip
    └── ...
```

> [!NOTE]
> The archive format depends on the user choice via `--compression-format` or on the first supported compression backend available on the system (by default `auto`).

#### Compressed instance results

Directory `📂 optimization_traces` keeps trace for each instance of the steps to find the bins corresponding to that instance.
An instance is defined by:

* an ID (prefixed with `instance_` and the same ID given in the `instance_id` key in `solution_metadata.yaml`);
* a network such that removing the source and the sink vertices still gives a connected subgraph;
* a topology (circular or partially circular);
* a seed constraint (required or optional);

> [!TIP]
> The instance IDs reflect the tree of instances:
> `instance_2` is associated with the second connected component, `instance_2_0` is associated with the first component after solving the parent instance `instance_2`.
>
> ```sh
> Full network
> ├── instance_1
> │   ├── instance_1_1
> │   │   └── ...
> │   └── ...
> └── ...
> ```

One compressed file corresponds to one instance.
Decompressing it gives one directory (for example `instance_1.<archive>`):

```sh
📂 instance_1  # Instance ID prefixed with `instance_`
├── ℹ️ metadata.yaml
├── 📂 multiflow_1  # The 1-multiflow attempt was succesful
│   ├── 📃 gurobi.log
│   ├── 📄 gurobi.sol
│   └── ℹ️ solution_metadata.yaml
└── 📂 multiflow_2  # The 2-multiflow attempt failed
    ├── 📃 gurobi.log
    └── ℹ️ infeasible.yaml

```

File `ℹ️ metadata.yaml` describes the instance:

```yaml
instance_id: <str>
multiflow_size_upper_bound: <int>
required_bin_property:
    topology: circular|partially_circular
    seed_constraint: required|optional
input_stats:
    number_of_contigs: <int>
    number_of_links: <int>
    number_of_seeds: <int>
    null_plasmidness: <bool>  # True if every contig has null plasmidness. The multiflow score function depends on this boolean.
parent_instance: <str> | null # Instance ID, if any. Otherwise null
```

For each instance, there is at least one optimization attempt.
An attempt `multiflow_n` means an attempt to find the best \(n\)-multiflow with the instance properties.

In a multiflow attempt (`multiflow_1` means a multiflow of size 1), file `ℹ️ solution_metadata.yaml` contains:

```yaml
multiflow_id: <str>
multiflow_size: <int>
status: <str>  # Gurobi status, see https://docs.gurobi.com/projects/optimizer/en/current/reference/numericcodes/statuscodes.html
multiflow_score: <float>
flows:
    flow_ID:  # Flow ID e.g. flow_1
        total_flow: <float>             # Should be 0 in the circular context
        plasmidness_score: <float>      # A good bin has a plasmidness score greater than 0
        cumulative_contig_length: <int> # Do not account for the multiplicity of a contig
        min_positive_arc_flow: <float>  # Corresponds to the minimum positive arc flow
        max_positive_arc_flow: <float>  # Corresponds to the maximum positive arc flow
        min_positive_contig_inflow: <float>    # Corresponds to the minimum positive contig inflow (forward + reverse orientations)
        max_positive_contig_inflow: <float>    # Corresponds to the maximum positive contig inflow (forward + reverse orientations)
```

In case PlasBin-HMF was not able to find any bin under the parametrized constraints, the program produces file `ℹ️ infeasible.yaml`:

```yaml
multiflow_id: <str>
multiflow_size: <int>
status: <str>  # Gurobi status, see https://docs.gurobi.com/projects/optimizer/en/current/reference/numericcodes/statuscodes.html
```

#### Traces metadata

File `ℹ️ traces_metadata.yaml` is a YAML stream of all the `instance_id/metadata.yaml`, see [instance metadata](#compressed-instance-results).

Each document (separated by `---` at the top) describes one optimization instance.

```yaml
---
instance_id: <str>
multiflow_size_upper_bound: <int>
required_bin_property:
    topology: circular|partially_circular
    seed_constraint: required|optional
input_stats:
    number_of_contigs: <int>
    number_of_links: <int>
    number_of_seeds: <int>
parent_instance: <str> | null # Instance ID, if any. Otherwise null
# ---
# ... Another instance
```

<!-- Links -->
[uv]: https://docs.astral.sh/uv/
[PlasBin-flow]: https://github.com/cchauve/PlasBin-flow

<!-- Badges -->

<!--
Changes:
* PyPI project name `plasbin-hmf`
* Git project name `plasbin-hmf`
* GitLab project ID `85213159`
-->

[pypi_badge]: https://img.shields.io/pypi/v/plasbin-hmf?style=for-the-badge&logo=python&color=blue "Package badge"
[pypi_link]: https://pypi.org/project/plasbin-hmf/ "Package link"

[coverage_badge]: https://img.shields.io/gitlab/pipeline-coverage/vepain%2Fplasbin-hmf?branch=main&style=for-the-badge&logo=gitlab&label=Coverage "Coverage badge"
[coverage_link]: https://gitlab.com/vepain/plasbin-hmf/-/commits/main "Coverage link"

[ruff_badge]: https://img.shields.io/endpoint?url=https%3A%2F%2Fgitlab.com%2Fapi%2Fv4%2Fprojects%2F85213159%2Fjobs%2Fartifacts%2Fmain%2Fraw%2Fruff%2Fbadge.json%3Fjob%3Druff&style=for-the-badge&logo=ruff&label=Ruff "Ruff badge"
[ruff_link]: https://gitlab.com/vepain/plasbin-hmf/-/commits/main "Ruff link"

<!-- https://gitlab.com/api/v4/projects/85213159/jobs/artifacts/main/raw/ruff/badge.json?job=ruff -->

[ty_badge]: https://img.shields.io/endpoint?url=https%3A%2F%2Fgitlab.com%2Fapi%2Fv4%2Fprojects%2F85213159%2Fjobs%2Fartifacts%2Fmain%2Fraw%2Fty%2Fbadge.json%3Fjob%3Dty&style=for-the-badge&logo=ty&label=Ty "ty badge"
[ty_link]: https://gitlab.com/vepain/plasbin-hmf/-/commits/main "ty link"

[uv_badge]: https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2Fastral-sh%2Fuv%2Fmain%2Fassets%2Fbadge%2Fv0.json&style=for-the-badge "uv badge"
[uv_link]: https://docs.astral.sh/uv/ "uv link"

[pipeline_badge]: https://img.shields.io/gitlab/pipeline-status/vepain%2Fplasbin-hmf?branch=main&style=for-the-badge&logo=circleci "Pipeline badge"
[pipeline_link]: https://gitlab.com/vepain/plasbin-hmf/-/commits/main "Pipeline link"

[docs_badge]: https://img.shields.io/readthedocs/plasbin-hmf?style=for-the-badge&logo=readthedocs "Documentation badge"
[docs_link]: https://plasbin-hmf.readthedocs.io/en/latest/ "Documentation link"

[license_badge]: https://img.shields.io/gitlab/license/vepain%2Fplasbin-hmf?style=for-the-badge&logo=readdotcv&color=green "Licence badge"
[licence_link]: https://gitlab.com/vepain/plasbin-hmf "Licence link"
