PythiaLabelGenerator 1.0.0__tar.gz → 1.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/.github/workflows/test-label-generator.yml +1 -1
  2. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/PKG-INFO +18 -15
  3. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/README.md +17 -14
  4. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/labelgenerator/label.py +7 -5
  5. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/labelgenerator/logger.py +17 -1
  6. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/labelgenerator/main.py +48 -19
  7. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/pyproject.toml +1 -1
  8. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/test_label.py +3 -0
  9. pythialabelgenerator-1.1.1/tests/test_main.py +127 -0
  10. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/.github/actions/setup-iqtree/action.yml +0 -0
  11. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/.github/actions/setup-raxmlng/action.yml +0 -0
  12. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/.github/actions/test-label-cli/action.yml +0 -0
  13. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/.gitignore +0 -0
  14. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/.pre-commit-config.yaml +0 -0
  15. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/LICENSE +0 -0
  16. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/__init__.py +0 -0
  17. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/etc/environment.yml +0 -0
  18. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/examples/example.phy +0 -0
  19. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/labelgenerator/__init__.py +0 -0
  20. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/labelgenerator/iqtree.py +0 -0
  21. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/labelgenerator/iqtree_parser.py +0 -0
  22. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/labelgenerator/raxmlng.py +0 -0
  23. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/setup.py +0 -0
  24. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/__init__.py +0 -0
  25. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/conftest.py +0 -0
  26. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/AA.phy +0 -0
  27. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/DNA.phy +0 -0
  28. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/MORPH.phy +0 -0
  29. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/logs/AA.iqtree.iqtree +0 -0
  30. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/logs/AA.iqtree.log +0 -0
  31. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/logs/DNA.iqtree.iqtree +0 -0
  32. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/logs/DNA.iqtree.log +0 -0
  33. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/logs/DNA.raxml.log +0 -0
  34. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/logs/MORPH.iqtree.iqtree +0 -0
  35. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/logs/MORPH.iqtree.log +0 -0
  36. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/logs/test.raxml.bestModel +0 -0
  37. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/logs/test.raxml.bestTree +0 -0
  38. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/logs/test.raxml.log +0 -0
  39. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/logs/test.raxml.mlTrees +0 -0
  40. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/logs/test.raxml.rba +0 -0
  41. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/logs/test.raxml.startTree +0 -0
  42. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/logs/test.rfdist.raxml.log +0 -0
  43. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/logs/test.rfdist.raxml.rfDistances +0 -0
  44. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/mltrees/AA.raxml.bestModel +0 -0
  45. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/mltrees/AA.raxml.bestTree +0 -0
  46. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/mltrees/AA.raxml.log +0 -0
  47. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/mltrees/AA.raxml.mlTrees +0 -0
  48. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/mltrees/AA.raxml.rba +0 -0
  49. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/mltrees/AA.raxml.startTree +0 -0
  50. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/mltrees/DNA.raxml.bestModel +0 -0
  51. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/mltrees/DNA.raxml.bestTree +0 -0
  52. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/mltrees/DNA.raxml.bestTreeCollapsed +0 -0
  53. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/mltrees/DNA.raxml.log +0 -0
  54. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/mltrees/DNA.raxml.mlTrees +0 -0
  55. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/mltrees/DNA.raxml.rba +0 -0
  56. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/mltrees/DNA.raxml.startTree +0 -0
  57. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/mltrees/MORPH.raxml.bestModel +0 -0
  58. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/mltrees/MORPH.raxml.bestTree +0 -0
  59. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/mltrees/MORPH.raxml.bestTreeCollapsed +0 -0
  60. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/mltrees/MORPH.raxml.log +0 -0
  61. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/mltrees/MORPH.raxml.mlTrees +0 -0
  62. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/mltrees/MORPH.raxml.rba +0 -0
  63. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/mltrees/MORPH.raxml.startTree +0 -0
  64. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/test_config.py +0 -0
  65. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/test_iqtree.py +0 -0
  66. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/test_iqtree_parser.py +0 -0
  67. {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/test_raxmlng.py +0 -0
@@ -39,7 +39,7 @@ jobs:
39
39
  # uses: mxschmitt/action-tmate@v3
40
40
  - name: Run LabelGenerator tests
41
41
  run: |
42
- PYTHONPATH=. pytest -svx
42
+ PYTHONPATH=. pytest -svx --color=yes
43
43
  shell: bash -el {0}
44
44
 
45
45
  Install-using-conda:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PythiaLabelGenerator
3
- Version: 1.0.0
3
+ Version: 1.1.1
4
4
  Summary: Command line tool to generate the ground-truth phylogenetic difficulty of MSAs
5
5
  Project-URL: Homepage, https://github.com/tschuelia/PythiaLabelGenerator
6
6
  Author-email: Julia Haag <info@juliaschmid.com>
@@ -18,11 +18,11 @@ Provides-Extra: test
18
18
  Requires-Dist: pytest; extra == 'test'
19
19
  Description-Content-Type: text/markdown
20
20
 
21
- # Pythia Difficulty Label Generator
21
+ # Pythia Difficulty Label Generator (PyDLG)
22
22
 
23
23
  ![Label Generator GH actions CI](https://github.com/tschuelia/PythiaLabelGenerator/actions/workflows/test-label-generator.yml/badge.svg)
24
24
 
25
- The Pythia Difficulty Label Generator generates the ground-truth phylogenetic difficulty label for an MSA and
25
+ PyDLG generates the ground-truth phylogenetic difficulty label for an MSA and
26
26
  corresponds to the prediction target of our difficulty prediction tool [Pythia](https://github.com/tschuelia/PyPythia).
27
27
 
28
28
  > [!CAUTION]
@@ -70,7 +70,7 @@ comprising [SARS-CoV-2 sequences](https://doi.org/10.1093/molbev/msaa314) (appro
70
70
  12 hours on a large compute cluster. Using Pythia instead, we can predict the same MSA to be very difficult in about 2.5
71
71
  minutes on a standard MacBook.
72
72
 
73
- Only use this tool if you need the ground-truth difficulty for a specific MSA and you are sure that Pythia is unable to
73
+ Only use PyDLG if you need the ground-truth difficulty for a specific MSA and you are sure that Pythia is unable to
74
74
  predict the difficulty accurately.
75
75
  The only case where we observed Pythia to fail is for language MSAs, so if you are working with DNA, Protein, or
76
76
  biological morphological data, Pythia should work just fine 😉
@@ -79,7 +79,7 @@ biological morphological data, Pythia should work just fine 😉
79
79
 
80
80
  #### Requirements
81
81
 
82
- To use this labelling tool, you need to install
82
+ To use PyDLG, you need to install
83
83
 
84
84
  - RAxML-NG: See [the RAxML-NG GitHub repository](https://github.com/amkozlov/raxml-ng) for installation instructions.
85
85
  Please make sure that you install a RAxML-NG version < 2.
@@ -88,7 +88,11 @@ To use this labelling tool, you need to install
88
88
 
89
89
  #### Install via conda (recommended)
90
90
 
91
- This package will soon be available on conda-forge :)
91
+ You can install the package using conda:
92
+
93
+ ```bash
94
+ conda install pythialabelgenerator -c conda-forge
95
+ ```
92
96
 
93
97
  #### Install using pip
94
98
 
@@ -100,7 +104,7 @@ pip install pythialabelgenerator
100
104
 
101
105
  ## Usage
102
106
 
103
- This label-generator is primarily a command line tool. You can call it using the `label` command, for instance, to
107
+ PyDLG is primarily a command line tool. You can call it using the `label` command, for instance, to
104
108
  compute the difficulty for the example MSA provided in the `examples` directory run
105
109
 
106
110
  ```bash
@@ -112,7 +116,7 @@ to the console.
112
116
  The output will look something like this:
113
117
 
114
118
  ```text
115
- Difficulty LabelGenerator version 1.0.0 released by The Exelixis Lab
119
+ PyDLG version 1.0.1 released by The Exelixis Lab
116
120
  Developed by: Julia Haag
117
121
  Latest version: https://github.com/tschuelia/LabelGenerator
118
122
  Questions/problems/suggestions? Please open an issue on GitHub.
@@ -147,7 +151,7 @@ Note that this `examply.phy` MSA is not the same exemplary MSA as we provide in
147
151
  For a full list of command line options, run `label -h`:
148
152
 
149
153
  ```text
150
- Difficulty LabelGenerator version 1.0.0 released by The Exelixis Lab
154
+ PyDLG version 1.0.1 released by The Exelixis Lab
151
155
  Developed by: Julia Haag
152
156
  Latest version: https://github.com/tschuelia/LabelGenerator
153
157
  Questions/problems/suggestions? Please open an issue on GitHub.
@@ -183,19 +187,19 @@ difficulties, as the difficulty is based on the average pairwise RF distance bet
183
187
 
184
188
  ### Result Files
185
189
 
186
- Running this labelling tool will result in the following files:
190
+ Running PyDLG will result in the following files:
187
191
 
188
192
  - `{prefix}.raxml.*`: RAxML-NG log and result files.
189
193
  - `{prefix}.iqtree.*`: IQ-TREE log and result files.
190
194
  - `{prefix}.labelGen.log`: Log file containing the output of the label generator. This is the same output as printed to
191
195
  the terminal.
196
+ - `{prefix}.csv`: The computed ground-truth difficulty and all features required for Pythia for the given MSA.
192
197
 
193
198
  You can set the prefix of these files using the `-p` option. By default, the prefix is the name of the MSA file.
194
199
  Note that RAxML-NG and IQ-TREE refuse to overwrite existing files. If you want to redo the computations, you can use the
195
200
  `--redo` option.
196
201
  Please also specify the `--redo` option if you want to change the number of trees to infer using the `--ntrees` option
197
- for the same prefix. Otherwise,
198
- the label generator will exit with an error message.
202
+ for the same prefix. Otherwise, PyDLG will exit with an error message.
199
203
 
200
204
  ### Input Data
201
205
 
@@ -209,7 +213,6 @@ you can do so using the `--model` option.
209
213
 
210
214
 
211
215
  ## Citation
212
- We will soon publish a pre-print on bioRxiv with updates on our Pythia difficulty prediction tool that will also include a
213
- brief description of this new labelling tool. Please cite this pre-print if you use this tool in your research.
216
+ Please cite the following preprint if you use this tool in your research.
214
217
 
215
- The link to the paper will be added soon 🙂
218
+ Haag, J. & Stamatakis, A. (2025). **Pythia 2.0: New Data, New Prediction Model, New Features.** *BioRxiv*. [https://doi.org/10.1101/2025.03.25.645182](https://doi.org/10.1101/2025.03.25.645182)
@@ -1,8 +1,8 @@
1
- # Pythia Difficulty Label Generator
1
+ # Pythia Difficulty Label Generator (PyDLG)
2
2
 
3
3
  ![Label Generator GH actions CI](https://github.com/tschuelia/PythiaLabelGenerator/actions/workflows/test-label-generator.yml/badge.svg)
4
4
 
5
- The Pythia Difficulty Label Generator generates the ground-truth phylogenetic difficulty label for an MSA and
5
+ PyDLG generates the ground-truth phylogenetic difficulty label for an MSA and
6
6
  corresponds to the prediction target of our difficulty prediction tool [Pythia](https://github.com/tschuelia/PyPythia).
7
7
 
8
8
  > [!CAUTION]
@@ -50,7 +50,7 @@ comprising [SARS-CoV-2 sequences](https://doi.org/10.1093/molbev/msaa314) (appro
50
50
  12 hours on a large compute cluster. Using Pythia instead, we can predict the same MSA to be very difficult in about 2.5
51
51
  minutes on a standard MacBook.
52
52
 
53
- Only use this tool if you need the ground-truth difficulty for a specific MSA and you are sure that Pythia is unable to
53
+ Only use PyDLG if you need the ground-truth difficulty for a specific MSA and you are sure that Pythia is unable to
54
54
  predict the difficulty accurately.
55
55
  The only case where we observed Pythia to fail is for language MSAs, so if you are working with DNA, Protein, or
56
56
  biological morphological data, Pythia should work just fine 😉
@@ -59,7 +59,7 @@ biological morphological data, Pythia should work just fine 😉
59
59
 
60
60
  #### Requirements
61
61
 
62
- To use this labelling tool, you need to install
62
+ To use PyDLG, you need to install
63
63
 
64
64
  - RAxML-NG: See [the RAxML-NG GitHub repository](https://github.com/amkozlov/raxml-ng) for installation instructions.
65
65
  Please make sure that you install a RAxML-NG version < 2.
@@ -68,7 +68,11 @@ To use this labelling tool, you need to install
68
68
 
69
69
  #### Install via conda (recommended)
70
70
 
71
- This package will soon be available on conda-forge :)
71
+ You can install the package using conda:
72
+
73
+ ```bash
74
+ conda install pythialabelgenerator -c conda-forge
75
+ ```
72
76
 
73
77
  #### Install using pip
74
78
 
@@ -80,7 +84,7 @@ pip install pythialabelgenerator
80
84
 
81
85
  ## Usage
82
86
 
83
- This label-generator is primarily a command line tool. You can call it using the `label` command, for instance, to
87
+ PyDLG is primarily a command line tool. You can call it using the `label` command, for instance, to
84
88
  compute the difficulty for the example MSA provided in the `examples` directory run
85
89
 
86
90
  ```bash
@@ -92,7 +96,7 @@ to the console.
92
96
  The output will look something like this:
93
97
 
94
98
  ```text
95
- Difficulty LabelGenerator version 1.0.0 released by The Exelixis Lab
99
+ PyDLG version 1.0.1 released by The Exelixis Lab
96
100
  Developed by: Julia Haag
97
101
  Latest version: https://github.com/tschuelia/LabelGenerator
98
102
  Questions/problems/suggestions? Please open an issue on GitHub.
@@ -127,7 +131,7 @@ Note that this `examply.phy` MSA is not the same exemplary MSA as we provide in
127
131
  For a full list of command line options, run `label -h`:
128
132
 
129
133
  ```text
130
- Difficulty LabelGenerator version 1.0.0 released by The Exelixis Lab
134
+ PyDLG version 1.0.1 released by The Exelixis Lab
131
135
  Developed by: Julia Haag
132
136
  Latest version: https://github.com/tschuelia/LabelGenerator
133
137
  Questions/problems/suggestions? Please open an issue on GitHub.
@@ -163,19 +167,19 @@ difficulties, as the difficulty is based on the average pairwise RF distance bet
163
167
 
164
168
  ### Result Files
165
169
 
166
- Running this labelling tool will result in the following files:
170
+ Running PyDLG will result in the following files:
167
171
 
168
172
  - `{prefix}.raxml.*`: RAxML-NG log and result files.
169
173
  - `{prefix}.iqtree.*`: IQ-TREE log and result files.
170
174
  - `{prefix}.labelGen.log`: Log file containing the output of the label generator. This is the same output as printed to
171
175
  the terminal.
176
+ - `{prefix}.csv`: The computed ground-truth difficulty and all features required for Pythia for the given MSA.
172
177
 
173
178
  You can set the prefix of these files using the `-p` option. By default, the prefix is the name of the MSA file.
174
179
  Note that RAxML-NG and IQ-TREE refuse to overwrite existing files. If you want to redo the computations, you can use the
175
180
  `--redo` option.
176
181
  Please also specify the `--redo` option if you want to change the number of trees to infer using the `--ntrees` option
177
- for the same prefix. Otherwise,
178
- the label generator will exit with an error message.
182
+ for the same prefix. Otherwise, PyDLG will exit with an error message.
179
183
 
180
184
  ### Input Data
181
185
 
@@ -189,7 +193,6 @@ you can do so using the `--model` option.
189
193
 
190
194
 
191
195
  ## Citation
192
- We will soon publish a pre-print on bioRxiv with updates on our Pythia difficulty prediction tool that will also include a
193
- brief description of this new labelling tool. Please cite this pre-print if you use this tool in your research.
196
+ Please cite the following preprint if you use this tool in your research.
194
197
 
195
- The link to the paper will be added soon 🙂
198
+ Haag, J. & Stamatakis, A. (2025). **Pythia 2.0: New Data, New Prediction Model, New Features.** *BioRxiv*. [https://doi.org/10.1101/2025.03.25.645182](https://doi.org/10.1101/2025.03.25.645182)
@@ -1,7 +1,7 @@
1
1
  import pathlib
2
2
  from typing import Optional
3
3
 
4
- from pypythia.msa import parse_msa
4
+ from pypythia.msa import DataType, MSA
5
5
 
6
6
  from labelgenerator.iqtree import (
7
7
  filter_plausible_trees,
@@ -92,14 +92,15 @@ def get_label(
92
92
  label = total / 5
93
93
 
94
94
  eps = 1e-9
95
- assert -eps <= label <= 1 + eps, (
96
- f"Label {label} is not between 0 and 1. Check the input values."
97
- )
95
+ assert (
96
+ -eps <= label <= 1 + eps
97
+ ), f"Label {label} is not between 0 and 1. Check the input values."
98
98
 
99
99
  return label
100
100
 
101
101
 
102
102
  def compute_label(
103
+ msa_obj: MSA,
103
104
  msa_file: pathlib.Path,
104
105
  raxmlng: pathlib.Path,
105
106
  iqtree: pathlib.Path,
@@ -117,6 +118,7 @@ def compute_label(
117
118
 
118
119
 
119
120
  Args:
121
+ msa_obj (MSA): MSA object containing the input data.
120
122
  msa_file (pathlib.Path): Path to the MSA file to compute the label for. Can be either in FASTA or PHYLIP format.
121
123
  raxmlng (pathlib.Path): Path to the RAxML-NG executable.
122
124
  iqtree (pathlib.Path): Path to the IQ-TREE executable.
@@ -134,7 +136,6 @@ def compute_label(
134
136
  float: The ground truth difficulty label for the given MSA.
135
137
 
136
138
  """
137
- msa_obj = parse_msa(msa_file)
138
139
  model = model or msa_obj.get_raxmlng_model()
139
140
 
140
141
  # 1. Infer 100 ML trees for the given MSA using RAxML-NG
@@ -182,6 +183,7 @@ def compute_label(
182
183
  seed=seed,
183
184
  threads=threads,
184
185
  redo=redo,
186
+ is_morph=msa_obj.data_type == DataType.MORPH,
185
187
  )
186
188
 
187
189
  # 4. Filter the plausible trees
@@ -15,7 +15,7 @@ logger.add(sys.stderr, format="{message}")
15
15
 
16
16
  def get_header():
17
17
  return textwrap.dedent(
18
- f"Difficulty LabelGenerator version {__version__} released by The Exelixis Lab\n"
18
+ f"PyDLG version {__version__} released by The Exelixis Lab\n"
19
19
  f"Developed by: Julia Haag\n"
20
20
  f"Latest version: https://github.com/tschuelia/LabelGenerator\n"
21
21
  f"Questions/problems/suggestions? Please open an issue on GitHub.\n",
@@ -30,3 +30,19 @@ def log_runtime_information(message, log_runtime=True):
30
30
  else:
31
31
  time_string = ""
32
32
  logger.info(f"{time_string}{message}")
33
+
34
+
35
+ def log_runtime(total_runtime: int, runtime_name: str):
36
+ hours, remainder = divmod(total_runtime, 3600)
37
+ minutes, seconds = divmod(remainder, 60)
38
+
39
+ if hours > 0:
40
+ logger.info(
41
+ f"{runtime_name}: {int(hours):02d}:{int(minutes):02d}:{seconds:02d} hours ({round(total_runtime)} seconds)."
42
+ )
43
+ elif minutes > 0:
44
+ logger.info(
45
+ f"{runtime_name}: {int(minutes):02d}:{int(seconds):02d} minutes ({round(total_runtime)} seconds)."
46
+ )
47
+ else:
48
+ logger.info(f"{runtime_name}: {seconds:.2f} seconds.")
@@ -3,15 +3,20 @@ import pathlib
3
3
  import shutil
4
4
  import sys
5
5
  import time
6
+ from typing import Optional
6
7
 
7
8
  from labelgenerator import __version__
8
9
  from labelgenerator.label import compute_label
9
10
  from labelgenerator.logger import (
10
11
  SCRIPT_START,
11
12
  get_header,
13
+ log_runtime,
12
14
  log_runtime_information,
13
15
  logger,
14
16
  )
17
+ from pypythia.msa import parse_msa
18
+ from pypythia.prediction import collect_features
19
+ from pypythia.raxmlng import RAxMLNG
15
20
 
16
21
  DEFAULT_RAXMLNG_EXE = (
17
22
  pathlib.Path(shutil.which("raxml-ng")) if shutil.which("raxml-ng") else None
@@ -22,7 +27,7 @@ DEFAULT_IQTREE_EXE = (
22
27
  )
23
28
 
24
29
 
25
- def _parse_cli():
30
+ def _parse_cli(arg_list: Optional[list[str]] = None):
26
31
  parser = argparse.ArgumentParser(
27
32
  description="Generate the ground truth difficulty for the given MSA."
28
33
  )
@@ -110,29 +115,49 @@ def _parse_cli():
110
115
  help="Print the version number and exit.",
111
116
  )
112
117
 
113
- return parser.parse_args()
118
+ return parser.parse_args(arg_list)
114
119
 
115
120
 
116
- def main():
121
+ def main(arg_list: Optional[list[str]] = None):
117
122
  logger.info(get_header())
118
- args = _parse_cli()
123
+ args = _parse_cli(arg_list)
119
124
 
120
125
  msa_file = pathlib.Path(args.msa)
121
126
  prefix = pathlib.Path(args.prefix) if args.prefix else msa_file
122
127
 
123
128
  log_file = pathlib.Path(f"{prefix}.labelGen.log")
129
+ features_file = pathlib.Path(f"{prefix}.csv")
130
+
131
+ # If the log file and the features file already exist, remove them if the --redo flag is set
132
+ if args.redo:
133
+ log_file.unlink(missing_ok=True)
134
+ features_file.unlink(missing_ok=True)
135
+
124
136
  logger.add(log_file, format="{message}")
125
137
  log_file.write_text(get_header() + "\n")
126
138
 
127
139
  logger.info(
128
- f"LabelGenerator was called at {time.strftime('%d-%b-%Y %H:%M:%S')} as follows:\n"
140
+ f"PyDLG was called at {time.strftime('%d-%b-%Y %H:%M:%S')} as follows:\n"
129
141
  )
130
142
  logger.info(" ".join(sys.argv))
131
143
  logger.info("")
132
144
 
133
145
  log_runtime_information("Starting label computation.")
134
146
 
147
+ msa_obj = parse_msa(msa_file)
148
+
149
+ if msa_obj.contains_duplicate_sequences():
150
+ logger.info(
151
+ "WARNING: The input MSA contains duplicate sequences. Consider deduplicating the MSA before running the label generator."
152
+ )
153
+
154
+ if msa_obj.contains_full_gap_sequences():
155
+ logger.info(
156
+ "WARNING: The input MSA contains sequences that are only gaps. Consider removing these sequences before running the label generator."
157
+ )
158
+
135
159
  difficulty = compute_label(
160
+ msa_obj=msa_obj,
136
161
  msa_file=msa_file,
137
162
  raxmlng=pathlib.Path(args.raxmlng),
138
163
  iqtree=pathlib.Path(args.iqtree),
@@ -145,6 +170,22 @@ def main():
145
170
  log_info=True,
146
171
  )
147
172
 
173
+ label_end = time.perf_counter()
174
+
175
+ log_runtime_information("Computing corresponding Pythia features.")
176
+
177
+ features = collect_features(
178
+ msa=msa_obj,
179
+ msa_file=msa_file,
180
+ raxmlng=RAxMLNG(pathlib.Path(args.raxmlng)),
181
+ log_info=False,
182
+ threads=args.threads,
183
+ seed=args.seed,
184
+ )
185
+
186
+ features["difficulty"] = difficulty
187
+ features.to_csv(features_file, index=False)
188
+
148
189
  script_end = time.perf_counter()
149
190
 
150
191
  logger.info("")
@@ -156,20 +197,8 @@ def main():
156
197
  )
157
198
 
158
199
  logger.info("")
159
- total_runtime = script_end - SCRIPT_START
160
- hours, remainder = divmod(total_runtime, 3600)
161
- minutes, seconds = divmod(remainder, 60)
162
-
163
- if hours > 0:
164
- logger.info(
165
- f"Total runtime: {int(hours):02d}:{int(minutes):02d}:{seconds:02d} hours ({round(total_runtime)} seconds)."
166
- )
167
- elif minutes > 0:
168
- logger.info(
169
- f"Total runtime: {int(minutes):02d}:{int(seconds):02d} minutes ({round(total_runtime)} seconds)."
170
- )
171
- else:
172
- logger.info(f"Total runtime: {seconds:.2f} seconds.")
200
+ log_runtime(label_end - SCRIPT_START, "Label computation runtime")
201
+ log_runtime(script_end - SCRIPT_START, "Total runtime")
173
202
 
174
203
 
175
204
  if __name__ == "__main__":
@@ -3,7 +3,7 @@ name = "PythiaLabelGenerator"
3
3
  description = "Command line tool to generate the ground-truth phylogenetic difficulty of MSAs"
4
4
  readme = {file = "README.md", content-type = "text/markdown"}
5
5
  authors = [{name = "Julia Haag", email = "info@juliaschmid.com"}]
6
- version = "1.0.0"
6
+ version = "1.1.1"
7
7
  license = "GPL-3.0-or-later"
8
8
  classifiers = [
9
9
  "Programming Language :: Python :: 3.9",
@@ -6,6 +6,7 @@ import pytest
6
6
  from labelgenerator.label import get_label, compute_label
7
7
  from labelgenerator.logger import logger
8
8
  from pypythia.custom_types import DataType
9
+ from pypythia.msa import parse_msa
9
10
 
10
11
 
11
12
  def test_get_label_fails_for_invalid_input():
@@ -99,6 +100,7 @@ def test_compute_label(
99
100
  prefix = pathlib.Path(tmpdir) / "test"
100
101
 
101
102
  label = compute_label(
103
+ msa_obj=parse_msa(msa_file),
102
104
  msa_file=msa_file,
103
105
  raxmlng=raxmlng_command,
104
106
  iqtree=iqtree_command,
@@ -123,6 +125,7 @@ def test_compute_label_with_logging(raxmlng_command, iqtree_command, data_dir):
123
125
  logger.add(logfile, format="{message}")
124
126
 
125
127
  label = compute_label(
128
+ msa_obj=parse_msa(msa_file),
126
129
  msa_file=msa_file,
127
130
  raxmlng=raxmlng_command,
128
131
  iqtree=iqtree_command,
@@ -0,0 +1,127 @@
1
+ import tempfile
2
+ import pytest
3
+
4
+ import pandas as pd
5
+
6
+ from labelgenerator.main import main
7
+ from labelgenerator import __version__
8
+ import pathlib
9
+
10
+ from pypythia.custom_types import DataType
11
+ from pypythia.msa import parse_msa
12
+
13
+
14
+ @pytest.mark.parametrize(
15
+ "data_type, expected_label",
16
+ [(DataType.DNA, 0.772), (DataType.AA, 0.04), (DataType.MORPH, 0.173)],
17
+ )
18
+ def test_main(data_type, expected_label, data_dir, raxmlng_command, iqtree_command):
19
+ with tempfile.TemporaryDirectory() as tmpdir:
20
+ # Create a temporary directory for the test
21
+ prefix = pathlib.Path(tmpdir) / "test"
22
+ seed = 42
23
+ n_trees = 10
24
+
25
+ logfile = prefix.with_suffix(".labelGen.log")
26
+ features_file = prefix.with_suffix(".csv")
27
+
28
+ msa_file = data_dir / f"{data_type.name}.phy"
29
+ msa_obj = parse_msa(msa_file)
30
+
31
+ args = [
32
+ "--msa",
33
+ str(msa_file),
34
+ "--raxmlng",
35
+ str(raxmlng_command),
36
+ "--iqtree",
37
+ str(iqtree_command),
38
+ "--seed",
39
+ str(seed),
40
+ "--prefix",
41
+ str(prefix),
42
+ "--ntrees",
43
+ str(n_trees),
44
+ "--redo",
45
+ ]
46
+
47
+ main(args)
48
+
49
+ # Check if all output files exist
50
+ expected_files = [
51
+ logfile,
52
+ features_file,
53
+ # RAxML-NG output files
54
+ prefix.with_suffix(".raxml.bestTree"),
55
+ prefix.with_suffix(".raxml.log"),
56
+ prefix.with_suffix(".raxml.mlTrees"),
57
+ prefix.with_suffix(".raxml.plausibleTrees"),
58
+ # IQ-TREE output files
59
+ prefix.with_suffix(".iqtree.iqtree"),
60
+ prefix.with_suffix(".iqtree.log"),
61
+ ]
62
+
63
+ for file in expected_files:
64
+ assert file.exists(), f"Expected file {file} does not exist."
65
+
66
+ # Check if the features csv file is correct
67
+ features_content = pd.read_csv(features_file)
68
+ assert features_content.shape[0] == 1, "Expected one row in the features file."
69
+ pd.testing.assert_index_equal(
70
+ features_content.columns,
71
+ pd.Index(
72
+ [
73
+ "num_taxa",
74
+ "num_sites",
75
+ "num_patterns",
76
+ "num_patterns/num_taxa",
77
+ "num_sites/num_taxa",
78
+ "num_patterns/num_sites",
79
+ "proportion_gaps",
80
+ "proportion_invariant",
81
+ "entropy",
82
+ "bollback",
83
+ "pattern_entropy",
84
+ "avg_rfdist_parsimony",
85
+ "proportion_unique_topos_parsimony",
86
+ "difficulty",
87
+ ]
88
+ ),
89
+ )
90
+
91
+ # Check if the label is correct
92
+ label = features_content["difficulty"].values[0]
93
+ assert label == pytest.approx(expected_label, abs=0.01)
94
+
95
+ # Check if the log file is correct and contains the expected output
96
+ expected_lines = [
97
+ f"PyDLG version {__version__} released by The Exelixis Lab",
98
+ "PyDLG was called at",
99
+ "Starting label computation.",
100
+ "Computing RF-Distance between ML trees.",
101
+ "Inferring 10 ML trees using RAxML-NG.",
102
+ "RF-Distance ML trees:",
103
+ "Unique topologies ML trees:",
104
+ "Running IQ-TREE statistical tests.",
105
+ "Computing RF-Distance between plausible ML trees.",
106
+ "RF-Distance plausible trees:",
107
+ "Unique topologies plausible trees:",
108
+ "Computing corresponding Pythia features.",
109
+ f"Ground Truth Difficulty for {msa_file}:",
110
+ "WARNING: The number of inferred ML trees is less than 100. The computed label may be less reliable.",
111
+ "Label computation runtime",
112
+ "Total runtime",
113
+ ]
114
+
115
+ if msa_obj.contains_duplicate_sequences():
116
+ expected_lines.append(
117
+ "WARNING: The input MSA contains duplicate sequences. Consider deduplicating the MSA before running the label generator."
118
+ )
119
+ if msa_obj.contains_full_gap_sequences():
120
+ expected_lines.append(
121
+ "WARNING: The input MSA contains sequences that are only gaps. Consider removing these sequences before running the label generator."
122
+ )
123
+
124
+ log_content = logfile.read_text()
125
+
126
+ for line in expected_lines:
127
+ assert line in log_content