Skip to content

Commit 466e979

Browse files
rmcar17YapengLangdependabot[bot]
committed
DEV: rebase windows-support on main (#5)
* ENH: provide piqtree_nj and nj_tree option for positive branch lengths (#165) * ENH: parameter to restrict negative value of branch length * TST: allow_negative correctly control the behavior of branch length in nj_tree * ENH: add parameter allow_negative in piqtree_nj app * MAINT: fix double return error in merge * MAINT: ruff formatting * TST: Use full fasta file for checking `nj_tree` negative branch lengths * MAINT: move from `ArrayAlignment` to `Alignment` in preparation for `new_type` default * MAINT: simplify logic for negative branch lengths * MAINT: Sort fixtures by number of taxa * MAINT: Remove unused test data file --------- Co-authored-by: Robert McArthur <[email protected]> * DEV: Update minimum required `cogent3` version (support cogent3 hooks) * TST: Remove skip for `quick_tree` `cogent3` hook as `cogent3` min version has updated * Bump ruff from 0.11.0 to 0.11.5 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.11.0 to 0.11.5. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](astral-sh/ruff@0.11.0...0.11.5) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.11.5 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <[email protected]> * Bump pypa/cibuildwheel from 2.23.1 to 2.23.2 Bumps [pypa/cibuildwheel](https://github.com/pypa/cibuildwheel) from 2.23.1 to 2.23.2. - [Release notes](https://github.com/pypa/cibuildwheel/releases) - [Changelog](https://github.com/pypa/cibuildwheel/blob/main/docs/changelog.md) - [Commits](pypa/cibuildwheel@v2.23.1...v2.23.2) --- updated-dependencies: - dependency-name: pypa/cibuildwheel dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <[email protected]> * Bump ruff from 0.11.5 to 0.11.8 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.11.5 to 0.11.8. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](astral-sh/ruff@0.11.5...0.11.8) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.11.8 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <[email protected]> * Bump pypa/cibuildwheel from 2.23.2 to 2.23.3 Bumps [pypa/cibuildwheel](https://github.com/pypa/cibuildwheel) from 2.23.2 to 2.23.3. - [Release notes](https://github.com/pypa/cibuildwheel/releases) - [Changelog](https://github.com/pypa/cibuildwheel/blob/main/docs/changelog.md) - [Commits](pypa/cibuildwheel@v2.23.2...v2.23.3) --- updated-dependencies: - dependency-name: pypa/cibuildwheel dependency-version: 2.23.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <[email protected]> * MAINT: upgrade min `cogent3` and use new `Table` API * ENH: support use of `str` for model in `build_tree` and `fit_tree` * TST: add tests for string represnetations of models * API: simplify expression of model in apps (match that used by `build_tree` and `fit_tree`) * DEV: add scriv fragment * DEV: bump version to 0.5.0 * DEV: collate scriv fragment * DOC: fix typo in release checklist * DEV: update trove classifiers for supported python versions * DEV: add minimum cmake version * Bump ruff from 0.11.8 to 0.11.9 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.11.8 to 0.11.9. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](astral-sh/ruff@0.11.8...0.11.9) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.11.9 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <[email protected]> --------- Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: Yapeng Lang <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
1 parent bc80ae0 commit 466e979

File tree

17 files changed

+162
-72
lines changed

17 files changed

+162
-72
lines changed

.github/workflows/build_wheels.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ jobs:
6161
uses: ilammy/msvc-dev-cmd@v1
6262

6363
- name: Build wheels
64-
uses: pypa/[email protected].1
64+
uses: pypa/[email protected].3
6565
env: # Can specify per os - e.g. CIBW_BEFORE_ALL_LINUX, CIBW_BEFORE_ALL_MACOS, CIBW_BEFORE_ALL_WINDOWS
6666
CIBW_BEFORE_ALL_LINUX: ./build_tools/before_all_linux.sh
6767
CIBW_BEFORE_ALL_MACOS: ./build_tools/before_all_mac.sh

.github/workflows/release.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ jobs:
6161
uses: ilammy/msvc-dev-cmd@v1
6262

6363
- name: Build wheels
64-
uses: pypa/[email protected].1
64+
uses: pypa/[email protected].3
6565
env: # Can specify per os - e.g. CIBW_BEFORE_ALL_LINUX, CIBW_BEFORE_ALL_MACOS, CIBW_BEFORE_ALL_WINDOWS
6666
CIBW_BEFORE_ALL_LINUX: ./build_tools/before_all_linux.sh
6767
CIBW_BEFORE_ALL_MACOS: ./build_tools/before_all_mac.sh

build_tools/build_iqtree.sh

+2-2
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ elif [[ "$OSTYPE" == "msys"* || "$OSTYPE" == "cygwin"* ]]; then
3030
..
3131
make -j
3232
else
33-
echo "Building for Linux."
34-
cmake -DBUILD_LIB=ON ..
33+
echo "Building for linux."
34+
cmake -DBUILD_LIB=ON -DCMAKE_POLICY_VERSION_MINIMUM=3.5 ..
3535
make -j
3636
fi
3737

changelog.md

+20
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,24 @@
11

2+
<a id='changelog-0.5.0'></a>
3+
# Changes in release "0.5.0"
4+
5+
## Contributors
6+
7+
- @GavinHuttley added `nj_tree` as a hook for `cogent3.Alignment.quick_tree`.
8+
- @YapengLang handled negative branch lengths from the rapidNJ tree.
9+
- @rmcar17, @thomaskf general maintanence on the piqtree/IQ-TREE sides including work on windows behind the scenes.
10+
11+
## ENH
12+
13+
- Add support for Python 3.13, remove support for 3.10
14+
- IQ-TREE's rapidNJ implementation can be used as a hook for `quick_tree` on `cogent3` alignment objects. Try `Alignment.quick_tree(use_hook="piqtree")`.
15+
- `nj_tree` now by default does not allow negative branch lengths. Set `allow_negative=True` if this behaviour is desired.
16+
- Allow `str` to be used for `model` in `build_tree` and `fit_tree`. The value is automatically coerced into the `Model` class.
17+
18+
## API
19+
20+
- Simplify API for `piqtree_phylo` and `piqtree_fit` apps. Both now take a single parameter for the model, matching the parameter for `model` in `build_tree` and `fit_tree`.
21+
222
<a id='changelog-0.4.0'></a>
323
# Changes in release "0.4.0"
424

docs/developers/release.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ The documentation is fetched by readthedocs from the "Build Docs" GitHub Action.
88

99
- The `piqtree` version has been correctly bumped.
1010
- The testing, linting and type checking all pass on all supported platforms.
11-
- The code has been throuroughly tested (check what's been missed in the coverage report).
11+
- The code has been thoroughly tested (check what's been missed in the coverage report).
1212
- The documentation builds and appears correct.
1313
- The documentation has been updated on readthedocs (this must be triggered from readthedocs).
1414
- The "Release" GitHub Action has correctly uploaded to Test PyPI.

pyproject.toml

+3-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "piqtree"
7-
dependencies = ["cogent3>=2024.11.29a2", "pyyaml", "requests"]
7+
dependencies = ["cogent3>=2025.5.8a2", "pyyaml", "requests"]
88
requires-python = ">=3.11, <3.14"
99

1010
authors = [{name="Gavin Huttley"}, {name="Robert McArthur"}, {name="Bui Quang Minh "}, {name="Richard Morris"}, {name="Thomas Wong"}]
@@ -28,9 +28,9 @@ classifiers = [
2828

2929
"Programming Language :: C++",
3030
"Programming Language :: Python :: 3",
31-
"Programming Language :: Python :: 3.10",
3231
"Programming Language :: Python :: 3.11",
3332
"Programming Language :: Python :: 3.12",
33+
"Programming Language :: Python :: 3.13",
3434

3535
"Typing :: Typed"
3636
]
@@ -43,7 +43,7 @@ Documentation = "https://piqtree.readthedocs.io"
4343
[project.optional-dependencies]
4444
dev = ["cibuildwheel", "pybind11", "scriv", "piqtree[test]", "piqtree[lint]", "piqtree[typing]"]
4545
test = ["pytest", "pytest-cov", "nox"]
46-
lint = ["ruff==0.11.0"]
46+
lint = ["ruff==0.11.9"]
4747
typing = ["mypy==1.15.0", "piqtree[stubs]", "piqtree[test]"]
4848
stubs = ["types-PyYAML", "types-requests"]
4949
extra = ["cogent3[extra]"]

src/piqtree/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ def _add_dll_path() -> None:
3535
make_model,
3636
)
3737

38-
__version__ = "0.4.0"
38+
__version__ = "0.5.0"
3939

4040
__all__ = [
4141
"Model",

src/piqtree/_app/__init__.py

+10-22
Original file line numberDiff line numberDiff line change
@@ -23,21 +23,13 @@ class piqtree_phylo:
2323
@extend_docstring_from(build_tree)
2424
def __init__(
2525
self,
26-
submod_type: str,
27-
freq_type: str | None = None,
28-
rate_model: str | None = None,
26+
model: Model | str,
2927
*,
30-
invariant_sites: bool = False,
3128
rand_seed: int | None = None,
3229
bootstrap_reps: int | None = None,
3330
num_threads: int | None = None,
3431
) -> None:
35-
self._model = Model(
36-
submod_type=submod_type,
37-
invariant_sites=invariant_sites,
38-
rate_model=rate_model,
39-
freq_type=freq_type,
40-
)
32+
self._model = model
4133
self._rand_seed = rand_seed
4234
self._bootstrap_reps = bootstrap_reps
4335
self._num_threads = num_threads
@@ -61,21 +53,13 @@ class piqtree_fit:
6153
def __init__(
6254
self,
6355
tree: cogent3.PhyloNode,
64-
submod_type: str,
65-
freq_type: str | None = None,
66-
rate_model: str | None = None,
56+
model: Model | str,
6757
*,
6858
rand_seed: int | None = None,
6959
num_threads: int | None = None,
70-
invariant_sites: bool = False,
7160
) -> None:
7261
self._tree = tree
73-
self._model = Model(
74-
submod_type=submod_type,
75-
invariant_sites=invariant_sites,
76-
rate_model=rate_model,
77-
freq_type=freq_type,
78-
)
62+
self._model = model
7963
self._rand_seed = rand_seed
8064
self._num_threads = num_threads
8165

@@ -124,8 +108,12 @@ def main(
124108

125109
@composable.define_app
126110
@extend_docstring_from(nj_tree)
127-
def piqtree_nj(dists: c3_types.PairwiseDistanceType) -> cogent3.PhyloNode:
128-
tree = nj_tree(dists)
111+
def piqtree_nj(
112+
dists: c3_types.PairwiseDistanceType,
113+
*,
114+
allow_negative: bool = False,
115+
) -> cogent3.PhyloNode:
116+
tree = nj_tree(dists, allow_negative=allow_negative)
129117
tree.params |= {"provenance": "piqtree"}
130118
return tree
131119

src/piqtree/iqtree/_tree.py

+24-7
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
from piqtree.exceptions import ParseIqTreeError
1313
from piqtree.iqtree._decorator import iqtree_func
14-
from piqtree.model import DnaModel, Model
14+
from piqtree.model import DnaModel, Model, make_model
1515

1616
iq_build_tree = iqtree_func(iq_build_tree, hide_files=True)
1717
iq_fit_tree = iqtree_func(iq_fit_tree, hide_files=True)
@@ -196,7 +196,7 @@ def _process_tree_yaml(
196196

197197
def build_tree(
198198
aln: c3_types.AlignedSeqsType,
199-
model: Model,
199+
model: Model | str,
200200
rand_seed: int | None = None,
201201
bootstrap_replicates: int | None = None,
202202
num_threads: int | None = None,
@@ -209,7 +209,7 @@ def build_tree(
209209
----------
210210
aln : c3_types.AlignedSeqsType
211211
The sequence alignment.
212-
model : Model
212+
model : Model | str
213213
The substitution model with base frequencies and rate heterogeneity.
214214
rand_seed : int | None, optional
215215
The random seed - 0 or None means no seed, by default None.
@@ -227,6 +227,9 @@ def build_tree(
227227
The IQ-TREE maximum likelihood tree from the given alignment.
228228
229229
"""
230+
if isinstance(model, str):
231+
model = make_model(model)
232+
230233
if rand_seed is None:
231234
rand_seed = 0 # The default rand_seed in IQ-TREE
232235

@@ -261,7 +264,7 @@ def build_tree(
261264
def fit_tree(
262265
aln: c3_types.AlignedSeqsType,
263266
tree: cogent3.PhyloNode,
264-
model: Model,
267+
model: Model | str,
265268
rand_seed: int | None = None,
266269
num_threads: int | None = None,
267270
) -> cogent3.PhyloNode:
@@ -276,7 +279,7 @@ def fit_tree(
276279
The sequence alignment.
277280
tree : cogent3.PhyloNode
278281
The topology to fit branch lengths to.
279-
model : Model
282+
model : Model | str
280283
The substitution model with base frequencies and rate heterogeneity.
281284
rand_seed : int | None, optional
282285
The random seed - 0 or None means no seed, by default None.
@@ -290,6 +293,9 @@ def fit_tree(
290293
A phylogenetic tree with same given topology fitted with branch lengths.
291294
292295
"""
296+
if isinstance(model, str):
297+
model = make_model(model)
298+
293299
if rand_seed is None:
294300
rand_seed = 0 # The default rand_seed in IQ-TREE
295301

@@ -312,7 +318,11 @@ def fit_tree(
312318
return tree
313319

314320

315-
def nj_tree(pairwise_distances: c3_types.PairwiseDistanceType) -> cogent3.PhyloNode:
321+
def nj_tree(
322+
pairwise_distances: c3_types.PairwiseDistanceType,
323+
*,
324+
allow_negative: bool = False,
325+
) -> cogent3.PhyloNode:
316326
"""Construct a neighbour joining tree from a pairwise distance matrix.
317327
318328
Parameters
@@ -334,4 +344,11 @@ def nj_tree(pairwise_distances: c3_types.PairwiseDistanceType) -> cogent3.PhyloN
334344
pairwise_distances.keys(),
335345
np.array(pairwise_distances).flatten(),
336346
)
337-
return make_tree(newick_tree)
347+
348+
tree = make_tree(newick_tree)
349+
350+
if not allow_negative:
351+
for node in tree.traverse(include_self=False):
352+
node.length = max(node.length, 0)
353+
354+
return tree

src/piqtree/model/_options.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import functools
44
from typing import Literal
55

6-
from cogent3 import _Table, make_table
6+
from cogent3.core.table import Table, make_table
77

88
from piqtree.model._freq_type import FreqType
99
from piqtree.model._rate_type import ALL_BASE_RATE_TYPES, get_description
@@ -40,7 +40,7 @@ def available_models(
4040
model_type: Literal["dna", "protein"] | None = None,
4141
*,
4242
show_all: bool = True,
43-
) -> _Table:
43+
) -> Table:
4444
"""Return a table showing available substitution models.
4545
4646
Parameters
@@ -52,7 +52,7 @@ def available_models(
5252
5353
Returns
5454
-------
55-
_Table
55+
Table
5656
Table with all available models.
5757
5858
"""
@@ -78,7 +78,7 @@ def available_models(
7878
return table
7979

8080

81-
def available_freq_type() -> _Table:
81+
def available_freq_type() -> Table:
8282
"""Return a table showing available freq type options."""
8383
data: dict[str, list[str]] = {"Freq Type": [], "Description": []}
8484

@@ -89,7 +89,7 @@ def available_freq_type() -> _Table:
8989
return make_table(data=data, title="Available frequency types")
9090

9191

92-
def available_rate_type() -> _Table:
92+
def available_rate_type() -> Table:
9393
"""Return a table showing available rate type options."""
9494
data: dict[str, list[str]] = {"Rate Type": [], "Description": []}
9595

tests/conftest.py

+10-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import pathlib
22

33
import pytest
4-
from cogent3 import ArrayAlignment, load_aligned_seqs
4+
from cogent3 import Alignment, load_aligned_seqs
55

66

77
@pytest.fixture(scope="session")
@@ -10,21 +10,27 @@ def DATA_DIR() -> pathlib.Path:
1010

1111

1212
@pytest.fixture
13-
def three_otu(DATA_DIR: pathlib.Path) -> ArrayAlignment:
13+
def three_otu(DATA_DIR: pathlib.Path) -> Alignment:
1414
aln = load_aligned_seqs(DATA_DIR / "example.fasta", moltype="dna", new_type=True)
1515
aln = aln.take_seqs(["Human", "Rhesus", "Mouse"])
1616
return aln.omit_gap_pos(allowed_gap_frac=0)
1717

1818

1919
@pytest.fixture
20-
def four_otu(DATA_DIR: pathlib.Path) -> ArrayAlignment:
20+
def four_otu(DATA_DIR: pathlib.Path) -> Alignment:
2121
aln = load_aligned_seqs(DATA_DIR / "example.fasta", moltype="dna", new_type=True)
2222
aln = aln.take_seqs(["Human", "Chimpanzee", "Rhesus", "Mouse"])
2323
return aln.omit_gap_pos(allowed_gap_frac=0)
2424

2525

2626
@pytest.fixture
27-
def five_otu(DATA_DIR: pathlib.Path) -> ArrayAlignment:
27+
def five_otu(DATA_DIR: pathlib.Path) -> Alignment:
2828
aln = load_aligned_seqs(DATA_DIR / "example.fasta", moltype="dna", new_type=True)
2929
aln = aln.take_seqs(["Human", "Chimpanzee", "Rhesus", "Manatee", "Dugong"])
3030
return aln.omit_gap_pos(allowed_gap_frac=0)
31+
32+
33+
@pytest.fixture
34+
def all_otu(DATA_DIR: pathlib.Path) -> Alignment:
35+
aln = load_aligned_seqs(DATA_DIR / "example.fasta", moltype="dna", new_type=True)
36+
return aln.omit_gap_pos(allowed_gap_frac=0)

tests/test_app/test_app.py

+4-6
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,20 @@
11
import pytest
2-
from cogent3 import __version__ as cogent3_vers
32
from cogent3 import get_app, make_tree
43
from cogent3.core.new_alignment import Alignment
54

65
import piqtree
7-
from piqtree import jc_distances
6+
from piqtree import jc_distances, make_model
87

98

109
def test_piqtree_phylo(four_otu: Alignment) -> None:
1110
expected = make_tree("(Human,Chimpanzee,(Rhesus,Mouse));")
12-
app = get_app("piqtree_phylo", submod_type="JC")
11+
app = get_app("piqtree_phylo", model="JC")
1312
got = app(four_otu)
1413
assert expected.same_topology(got)
1514

1615

1716
def test_piqtree_phylo_support(four_otu: Alignment) -> None:
18-
app = get_app("piqtree_phylo", submod_type="JC", bootstrap_reps=1000)
17+
app = get_app("piqtree_phylo", model=make_model("JC"), bootstrap_reps=1000)
1918
got = app(four_otu)
2019
supports = [
2120
node.params.get("support", None)
@@ -29,7 +28,7 @@ def test_piqtree_fit(three_otu: Alignment) -> None:
2928
tree = make_tree(tip_names=three_otu.names)
3029
app = get_app("model", "JC69", tree=tree)
3130
expected = app(three_otu)
32-
piphylo = get_app("piqtree_fit", tree=tree, submod_type="JC")
31+
piphylo = get_app("piqtree_fit", tree=tree, model="JC")
3332
got = piphylo(three_otu)
3433
assert got.params["lnL"] == pytest.approx(expected.lnL)
3534

@@ -112,7 +111,6 @@ def test_mfinder_result_roundtrip(five_otu: Alignment) -> None:
112111
assert str(got.best_aicc) == str(inflated.best_aicc)
113112

114113

115-
@pytest.mark.skipif(cogent3_vers < "2025.3.1", reason="requires cogent3 >= 2025.3.1")
116114
@pytest.mark.parametrize("use_hook", [None, "piqtree"])
117115
def test_quick_tree_hook(four_otu: Alignment, use_hook: str | None) -> None:
118116
tree = four_otu.quick_tree(use_hook=use_hook)

0 commit comments

Comments
 (0)