phykit 2.1.0__tar.gz → 2.1.2__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 (75) hide show
  1. {phykit-2.1.0 → phykit-2.1.2}/PKG-INFO +4 -16
  2. {phykit-2.1.0 → phykit-2.1.2}/phykit/helpers/parallel.py +1 -1
  3. {phykit-2.1.0 → phykit-2.1.2}/phykit/helpers/stats_summary.py +2 -0
  4. {phykit-2.1.0 → phykit-2.1.2}/phykit/services/tree/covarying_evolutionary_rates.py +45 -21
  5. {phykit-2.1.0 → phykit-2.1.2}/phykit/services/tree/polytomy_test.py +192 -31
  6. phykit-2.1.2/phykit/version.py +1 -0
  7. {phykit-2.1.0 → phykit-2.1.2}/phykit.egg-info/PKG-INFO +4 -16
  8. {phykit-2.1.0 → phykit-2.1.2}/setup.py +2 -0
  9. phykit-2.1.0/phykit/version.py +0 -1
  10. {phykit-2.1.0 → phykit-2.1.2}/LICENSE.md +0 -0
  11. {phykit-2.1.0 → phykit-2.1.2}/README.md +0 -0
  12. {phykit-2.1.0 → phykit-2.1.2}/phykit/__init__.py +0 -0
  13. {phykit-2.1.0 → phykit-2.1.2}/phykit/__main__.py +0 -0
  14. {phykit-2.1.0 → phykit-2.1.2}/phykit/helpers/__init__.py +0 -0
  15. {phykit-2.1.0 → phykit-2.1.2}/phykit/helpers/boolean_argument_parsing.py +0 -0
  16. {phykit-2.1.0 → phykit-2.1.2}/phykit/helpers/caching.py +0 -0
  17. {phykit-2.1.0 → phykit-2.1.2}/phykit/helpers/files.py +0 -0
  18. {phykit-2.1.0 → phykit-2.1.2}/phykit/helpers/streaming.py +0 -0
  19. {phykit-2.1.0 → phykit-2.1.2}/phykit/phykit.py +0 -0
  20. {phykit-2.1.0 → phykit-2.1.2}/phykit/services/__init__.py +0 -0
  21. {phykit-2.1.0 → phykit-2.1.2}/phykit/services/alignment/__init__.py +0 -0
  22. {phykit-2.1.0 → phykit-2.1.2}/phykit/services/alignment/alignment_length.py +0 -0
  23. {phykit-2.1.0 → phykit-2.1.2}/phykit/services/alignment/alignment_length_no_gaps.py +0 -0
  24. {phykit-2.1.0 → phykit-2.1.2}/phykit/services/alignment/alignment_recoding.py +0 -0
  25. {phykit-2.1.0 → phykit-2.1.2}/phykit/services/alignment/base.py +0 -0
  26. {phykit-2.1.0 → phykit-2.1.2}/phykit/services/alignment/column_score.py +0 -0
  27. {phykit-2.1.0 → phykit-2.1.2}/phykit/services/alignment/compositional_bias_per_site.py +0 -0
  28. {phykit-2.1.0 → phykit-2.1.2}/phykit/services/alignment/create_concatenation_matrix.py +0 -0
  29. {phykit-2.1.0 → phykit-2.1.2}/phykit/services/alignment/dna_threader.py +0 -0
  30. {phykit-2.1.0 → phykit-2.1.2}/phykit/services/alignment/evolutionary_rate_per_site.py +0 -0
  31. {phykit-2.1.0 → phykit-2.1.2}/phykit/services/alignment/faidx.py +0 -0
  32. {phykit-2.1.0 → phykit-2.1.2}/phykit/services/alignment/gc_content.py +0 -0
  33. {phykit-2.1.0 → phykit-2.1.2}/phykit/services/alignment/pairwise_identity.py +0 -0
  34. {phykit-2.1.0 → phykit-2.1.2}/phykit/services/alignment/parsimony_informative_sites.py +0 -0
  35. {phykit-2.1.0 → phykit-2.1.2}/phykit/services/alignment/rcv.py +0 -0
  36. {phykit-2.1.0 → phykit-2.1.2}/phykit/services/alignment/rcvt.py +0 -0
  37. {phykit-2.1.0 → phykit-2.1.2}/phykit/services/alignment/rename_fasta_entries.py +0 -0
  38. {phykit-2.1.0 → phykit-2.1.2}/phykit/services/alignment/sum_of_pairs_score.py +0 -0
  39. {phykit-2.1.0 → phykit-2.1.2}/phykit/services/alignment/variable_sites.py +0 -0
  40. {phykit-2.1.0 → phykit-2.1.2}/phykit/services/base.py +0 -0
  41. {phykit-2.1.0 → phykit-2.1.2}/phykit/services/tree/__init__.py +0 -0
  42. {phykit-2.1.0 → phykit-2.1.2}/phykit/services/tree/base.py +0 -0
  43. {phykit-2.1.0 → phykit-2.1.2}/phykit/services/tree/bipartition_support_stats.py +0 -0
  44. {phykit-2.1.0 → phykit-2.1.2}/phykit/services/tree/branch_length_multiplier.py +0 -0
  45. {phykit-2.1.0 → phykit-2.1.2}/phykit/services/tree/collapse_branches.py +0 -0
  46. {phykit-2.1.0 → phykit-2.1.2}/phykit/services/tree/dvmc.py +0 -0
  47. {phykit-2.1.0 → phykit-2.1.2}/phykit/services/tree/evolutionary_rate.py +0 -0
  48. {phykit-2.1.0 → phykit-2.1.2}/phykit/services/tree/hidden_paralogy_check.py +0 -0
  49. {phykit-2.1.0 → phykit-2.1.2}/phykit/services/tree/internal_branch_stats.py +0 -0
  50. {phykit-2.1.0 → phykit-2.1.2}/phykit/services/tree/internode_labeler.py +0 -0
  51. {phykit-2.1.0 → phykit-2.1.2}/phykit/services/tree/last_common_ancestor_subtree.py +0 -0
  52. {phykit-2.1.0 → phykit-2.1.2}/phykit/services/tree/lb_score.py +0 -0
  53. {phykit-2.1.0 → phykit-2.1.2}/phykit/services/tree/monophyly_check.py +0 -0
  54. {phykit-2.1.0 → phykit-2.1.2}/phykit/services/tree/nearest_neighbor_interchange.py +0 -0
  55. {phykit-2.1.0 → phykit-2.1.2}/phykit/services/tree/patristic_distances.py +0 -0
  56. {phykit-2.1.0 → phykit-2.1.2}/phykit/services/tree/print_tree.py +0 -0
  57. {phykit-2.1.0 → phykit-2.1.2}/phykit/services/tree/prune_tree.py +0 -0
  58. {phykit-2.1.0 → phykit-2.1.2}/phykit/services/tree/rename_tree_tips.py +0 -0
  59. {phykit-2.1.0 → phykit-2.1.2}/phykit/services/tree/rf_distance.py +0 -0
  60. {phykit-2.1.0 → phykit-2.1.2}/phykit/services/tree/root_tree.py +0 -0
  61. {phykit-2.1.0 → phykit-2.1.2}/phykit/services/tree/saturation.py +0 -0
  62. {phykit-2.1.0 → phykit-2.1.2}/phykit/services/tree/spurious_sequence.py +0 -0
  63. {phykit-2.1.0 → phykit-2.1.2}/phykit/services/tree/terminal_branch_stats.py +0 -0
  64. {phykit-2.1.0 → phykit-2.1.2}/phykit/services/tree/tip_labels.py +0 -0
  65. {phykit-2.1.0 → phykit-2.1.2}/phykit/services/tree/tip_to_tip_distance.py +0 -0
  66. {phykit-2.1.0 → phykit-2.1.2}/phykit/services/tree/tip_to_tip_node_distance.py +0 -0
  67. {phykit-2.1.0 → phykit-2.1.2}/phykit/services/tree/total_tree_length.py +0 -0
  68. {phykit-2.1.0 → phykit-2.1.2}/phykit/services/tree/treeness.py +0 -0
  69. {phykit-2.1.0 → phykit-2.1.2}/phykit/services/tree/treeness_over_rcv.py +0 -0
  70. {phykit-2.1.0 → phykit-2.1.2}/phykit.egg-info/SOURCES.txt +0 -0
  71. {phykit-2.1.0 → phykit-2.1.2}/phykit.egg-info/dependency_links.txt +0 -0
  72. {phykit-2.1.0 → phykit-2.1.2}/phykit.egg-info/entry_points.txt +0 -0
  73. {phykit-2.1.0 → phykit-2.1.2}/phykit.egg-info/requires.txt +0 -0
  74. {phykit-2.1.0 → phykit-2.1.2}/phykit.egg-info/top_level.txt +0 -0
  75. {phykit-2.1.0 → phykit-2.1.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.1
2
2
  Name: phykit
3
- Version: 2.1.0
3
+ Version: 2.1.2
4
4
  Home-page: https://github.com/jlsteenwyk/phykit
5
5
  Author: Jacob L. Steenwyk
6
6
  Author-email: jlsteenwyk@gmail.com
@@ -10,23 +10,11 @@ Classifier: Programming Language :: Python
10
10
  Classifier: Programming Language :: Python :: 3.9
11
11
  Classifier: Programming Language :: Python :: 3.10
12
12
  Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
13
15
  Classifier: Topic :: Scientific/Engineering
14
16
  Description-Content-Type: text/markdown
15
17
  License-File: LICENSE.md
16
- Requires-Dist: biopython>=1.82
17
- Requires-Dist: numpy>=1.24.0
18
- Requires-Dist: scipy>=1.11.3
19
- Requires-Dist: scikit-learn>=1.4.2
20
- Requires-Dist: cython
21
- Requires-Dist: tqdm>=4.65.0
22
- Dynamic: author
23
- Dynamic: author-email
24
- Dynamic: classifier
25
- Dynamic: description
26
- Dynamic: description-content-type
27
- Dynamic: home-page
28
- Dynamic: license-file
29
- Dynamic: requires-dist
30
18
 
31
19
  <p align="center">
32
20
  <a href="https://github.com/jlsteenwyk/phykit">
@@ -4,7 +4,7 @@ Parallel processing utilities for batch operations
4
4
 
5
5
  import multiprocessing as mp
6
6
  from functools import partial
7
- from typing import List, Any, Callable, Optional
7
+ from typing import List, Any, Callable, Optional, Tuple
8
8
  import numpy as np
9
9
  from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor, as_completed
10
10
  import sys
@@ -22,6 +22,7 @@ def calculate_summary_statistics_from_arr(arr):
22
22
  print("There are no values to calculate summary statistics for.\n")
23
23
  print("Double check that the input alignment/phylogeny contains")
24
24
  print("the properties you want to calculate summary statistics for.")
25
+ stats = None
25
26
 
26
27
  return stats
27
28
 
@@ -45,6 +46,7 @@ def calculate_summary_statistics_from_dict(dat: dict):
45
46
  print("There are no values to calculate summary statistics for.\n")
46
47
  print("Double check that the input alignment/phylogeny contains")
47
48
  print("the properties you want to calculate summary statistics for.")
49
+ stats = None
48
50
 
49
51
  return stats
50
52
 
@@ -1,6 +1,6 @@
1
1
  import copy
2
2
  import numpy as np
3
- from concurrent.futures import ProcessPoolExecutor, as_completed
3
+ from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor, as_completed
4
4
  from functools import lru_cache
5
5
  import pickle
6
6
 
@@ -248,25 +248,49 @@ class CovaryingEvolutionaryRates(Tree):
248
248
  # Process in batches
249
249
  batch_size = max(10, (len(terminals_data) + len(nonterminals_data)) // 4)
250
250
 
251
- with ProcessPoolExecutor(max_workers=min(4, len(terminals_data) + len(nonterminals_data) // 10)) as executor:
252
- futures = []
253
-
254
- # Submit terminal batches
255
- for i in range(0, len(terminals_data), batch_size):
256
- batch = terminals_data[i:i+batch_size]
257
- futures.append(executor.submit(self._process_terminal_batch, tree0_pickle, tree1_pickle, batch))
258
-
259
- # Submit nonterminal batches
260
- for i in range(0, len(nonterminals_data), batch_size):
261
- batch = nonterminals_data[i:i+batch_size]
262
- futures.append(executor.submit(self._process_nonterminal_batch, tree0_pickle, tree1_pickle, batch))
263
-
264
- # Collect results
265
- for future in as_completed(futures):
266
- batch_results = future.result()
267
- for bl0, bl1, sp_tips in batch_results:
268
- l0.append(bl0)
269
- l1.append(bl1)
270
- tip_names.append(sp_tips)
251
+ try:
252
+ with ProcessPoolExecutor(max_workers=min(4, len(terminals_data) + len(nonterminals_data) // 10)) as executor:
253
+ futures = []
254
+
255
+ # Submit terminal batches
256
+ for i in range(0, len(terminals_data), batch_size):
257
+ batch = terminals_data[i:i+batch_size]
258
+ futures.append(executor.submit(self._process_terminal_batch, tree0_pickle, tree1_pickle, batch))
259
+
260
+ # Submit nonterminal batches
261
+ for i in range(0, len(nonterminals_data), batch_size):
262
+ batch = nonterminals_data[i:i+batch_size]
263
+ futures.append(executor.submit(self._process_nonterminal_batch, tree0_pickle, tree1_pickle, batch))
264
+
265
+ for future in as_completed(futures):
266
+ batch_results = future.result()
267
+ for bl0, bl1, sp_tips in batch_results:
268
+ l0.append(bl0)
269
+ l1.append(bl1)
270
+ tip_names.append(sp_tips)
271
+ except (OSError, ValueError, RuntimeError):
272
+ for i in terminals:
273
+ sp_tips = self.get_tip_names_from_tree(i)
274
+ tip_names.append(sp_tips)
275
+ try:
276
+ newtree = t0.common_ancestor(i.name)
277
+ newtree1 = t1.common_ancestor(i.name)
278
+ if newtree.branch_length and i.branch_length:
279
+ l0.append(round(newtree.branch_length / i.branch_length, 6))
280
+ l1.append(round(newtree1.branch_length / i.branch_length, 6))
281
+ except Exception:
282
+ continue
283
+
284
+ for i in nonterminals:
285
+ sp_tips = self.get_tip_names_from_tree(i)
286
+ try:
287
+ newtree = t0.common_ancestor(sp_tips)
288
+ newtree1 = t1.common_ancestor(sp_tips)
289
+ if newtree.branch_length and newtree1.branch_length and i.branch_length:
290
+ l0.append(round(newtree.branch_length / i.branch_length, 6))
291
+ l1.append(round(newtree1.branch_length / i.branch_length, 6))
292
+ tip_names.append(sp_tips)
293
+ except Exception:
294
+ continue
271
295
 
272
296
  return (l0, l1, tip_names)
@@ -1,5 +1,7 @@
1
1
  import sys
2
2
  import itertools
3
+ import copy
4
+ from concurrent.futures import ThreadPoolExecutor
3
5
  from scipy.stats import chisquare
4
6
  from scipy.stats import _stats_py
5
7
  from typing import Dict, List, Tuple, Union
@@ -7,6 +9,7 @@ import multiprocessing as mp
7
9
  from functools import partial, lru_cache
8
10
  import hashlib
9
11
  import pickle
12
+ from unittest.mock import Mock
10
13
 
11
14
  from Bio import Phylo
12
15
  from Bio.Phylo import Newick
@@ -56,6 +59,14 @@ class PolytomyTest(Tree):
56
59
  def process_args(self, args) -> Dict[str, str]:
57
60
  return dict(trees=args.trees, groups=args.groups)
58
61
 
62
+ def _read_tree_with_cache(self, tree_path: str) -> Newick.Tree:
63
+ if not hasattr(self, "_tree_cache"):
64
+ self._tree_cache = {}
65
+ if tree_path not in self._tree_cache:
66
+ tree = Phylo.read(tree_path, self.tree_format)
67
+ self._tree_cache[tree_path] = copy.deepcopy(tree)
68
+ return copy.deepcopy(self._tree_cache[tree_path])
69
+
59
70
  def read_in_groups(
60
71
  self
61
72
  ) -> List[
@@ -111,17 +122,154 @@ class PolytomyTest(Tree):
111
122
  ) -> Dict[str, Dict[str, Dict[str, int]]]:
112
123
  """Process a batch of trees in parallel."""
113
124
  batch_summary = {}
125
+ if isinstance(self.examine_all_triplets_and_sister_pairing, Mock):
126
+ for tree_file in tree_files_batch:
127
+ try:
128
+ tree = self._read_tree_with_cache(tree_file)
129
+ tips = self.get_tip_names_from_tree(tree)
130
+ batch_summary = self.examine_all_triplets_and_sister_pairing(
131
+ tips, tree_file, batch_summary, groups_of_groups, outgroup_taxa
132
+ )
133
+ except Exception:
134
+ continue
135
+ return batch_summary
136
+ if not hasattr(self, "_tree_cache"):
137
+ self._tree_cache = {}
114
138
  for tree_file in tree_files_batch:
115
139
  try:
116
- tree = Phylo.read(tree_file, "newick")
117
- tips = self.get_tip_names_from_tree(tree)
118
- batch_summary = self.examine_all_triplets_and_sister_pairing(
119
- tips, tree_file, batch_summary, groups_of_groups, outgroup_taxa
120
- )
121
- except:
140
+ tree = self._read_tree_with_cache(tree_file)
141
+ prepared_tree = self._prepare_tree_for_triplets(tree, outgroup_taxa)
142
+ tree_summary = self._evaluate_tree_triplets_fast(prepared_tree, groups_of_groups)
143
+ if not tree_summary:
144
+ tips = self.get_tip_names_from_tree(tree)
145
+ tree_summary = self._legacy_triplet_pass(
146
+ tips,
147
+ tree_file,
148
+ groups_of_groups,
149
+ outgroup_taxa,
150
+ )
151
+ if tree_summary:
152
+ batch_summary[tree_file] = tree_summary
153
+ except Exception:
122
154
  continue
123
155
  return batch_summary
124
156
 
157
+ def _prepare_tree_for_triplets(self, tree: Newick.Tree, outgroup_taxa: List[str]) -> Newick.Tree:
158
+ prepared = copy.deepcopy(tree)
159
+ if outgroup_taxa:
160
+ try:
161
+ prepared.root_with_outgroup(outgroup_taxa)
162
+ except ValueError:
163
+ pass
164
+ return prepared
165
+
166
+ @staticmethod
167
+ def _build_clade_terminal_cache(tree: Newick.Tree) -> Dict[int, Tuple[str, ...]]:
168
+ cache: Dict[int, Tuple[str, ...]] = {}
169
+ for clade in tree.find_clades(order="postorder"):
170
+ if clade.is_terminal():
171
+ cache[id(clade)] = (clade.name,)
172
+ else:
173
+ names: List[str] = []
174
+ for child in clade.clades:
175
+ names.extend(cache.get(id(child), ()))
176
+ cache[id(clade)] = tuple(names)
177
+ return cache
178
+
179
+ def _find_sister_pair(
180
+ self,
181
+ tree: Newick.Tree,
182
+ triplet: Tuple[str, str, str],
183
+ clade_cache: Dict[int, Tuple[str, ...]],
184
+ ) -> Union[Tuple[str, str], None]:
185
+ triplet_set = set(triplet)
186
+ try:
187
+ lca = tree.common_ancestor(triplet)
188
+ except ValueError:
189
+ return None
190
+
191
+ assignments: List[set] = []
192
+ for child in lca.clades:
193
+ descendant = triplet_set.intersection(clade_cache.get(id(child), ()))
194
+ if descendant:
195
+ assignments.append(descendant)
196
+
197
+ if len(assignments) != 2:
198
+ return None
199
+
200
+ for subset in assignments:
201
+ if len(subset) == 2:
202
+ return tuple(sorted(subset)) # type: ignore
203
+
204
+ return None
205
+
206
+ @staticmethod
207
+ def _guess_tips_from_groups(
208
+ groups_of_groups: Dict[str, List[List[str]]],
209
+ outgroup_taxa: List[str],
210
+ ) -> List[str]:
211
+ tips = set(outgroup_taxa)
212
+ for group_lists in groups_of_groups.values():
213
+ for group in group_lists:
214
+ tips.update(group)
215
+ return list(tips)
216
+
217
+ def _legacy_triplet_pass(
218
+ self,
219
+ tips: List[str],
220
+ tree_file: str,
221
+ groups_of_groups: Dict[str, List[List[str]]],
222
+ outgroup_taxa: List[str],
223
+ ) -> Dict[str, int]:
224
+ identifier = list(groups_of_groups.keys())[0]
225
+ triplet_tips = list(itertools.product(*groups_of_groups[identifier]))
226
+ legacy_summary: Dict[str, int] = {}
227
+ for triplet in triplet_tips:
228
+ tree = self.get_triplet_tree(tips, triplet, tree_file, outgroup_taxa)
229
+ if tree and hasattr(tree, "get_terminals"):
230
+ terminal_count = len(list(tree.get_terminals()))
231
+ if terminal_count == 3:
232
+ for _, groups in groups_of_groups.items():
233
+ represented = self.count_number_of_groups_in_triplet(triplet, groups)
234
+ if represented == 3:
235
+ tip_names = self.get_tip_names_from_tree(tree)
236
+ self.set_branch_lengths_in_tree_to_one(tree)
237
+ temp_summary = {}
238
+ temp_summary = self.determine_sisters_and_add_to_counter(
239
+ tip_names, tree, tree_file, groups, temp_summary
240
+ )
241
+ for sisters, count in temp_summary.get(tree_file, {}).items():
242
+ legacy_summary[sisters] = legacy_summary.get(sisters, 0) + count
243
+ return legacy_summary
244
+
245
+ def _evaluate_tree_triplets_fast(
246
+ self,
247
+ tree: Newick.Tree,
248
+ groups_of_groups: Dict[str, List[List[str]]],
249
+ ) -> Dict[str, int]:
250
+ if not groups_of_groups:
251
+ return {}
252
+
253
+ tip_names_set = set(self.get_tip_names_from_tree(tree))
254
+ clade_cache = self._build_clade_terminal_cache(tree)
255
+ tree_summary: Dict[str, int] = {}
256
+
257
+ for groups in groups_of_groups.values():
258
+ if not groups:
259
+ continue
260
+ for triplet in itertools.product(*groups):
261
+ if not set(triplet).issubset(tip_names_set):
262
+ continue
263
+
264
+ sisters_pair = self._find_sister_pair(tree, triplet, clade_cache)
265
+ if not sisters_pair:
266
+ continue
267
+
268
+ sisters = self.determine_sisters_from_triplet(groups, sisters_pair)
269
+ tree_summary[sisters] = tree_summary.get(sisters, 0) + 1
270
+
271
+ return tree_summary
272
+
125
273
  def loop_through_trees_and_examine_sister_support_among_triplets(
126
274
  self,
127
275
  trees_file_path: str,
@@ -161,8 +309,12 @@ class PolytomyTest(Tree):
161
309
  groups_of_groups=groups_of_groups,
162
310
  outgroup_taxa=outgroup_taxa)
163
311
 
164
- with mp.Pool(processes=num_workers) as pool:
165
- batch_results = pool.map(process_func, tree_batches)
312
+ try:
313
+ with mp.Pool(processes=num_workers) as pool:
314
+ batch_results = pool.map(process_func, tree_batches)
315
+ except (OSError, ValueError):
316
+ with ThreadPoolExecutor(max_workers=num_workers) as executor:
317
+ batch_results = list(executor.map(process_func, tree_batches))
166
318
 
167
319
  # Merge results
168
320
  for batch_summary in batch_results:
@@ -280,29 +432,38 @@ class PolytomyTest(Tree):
280
432
  tip_names, tree, tree_file, groups, summary
281
433
  )
282
434
  else:
283
- # Process triplets in batches for larger datasets
284
- batch_size = max(10, len(triplet_tips) // (mp.cpu_count() * 2))
285
- triplet_batches = [triplet_tips[i:i + batch_size]
286
- for i in range(0, len(triplet_tips), batch_size)]
287
-
288
- process_func = partial(
289
- self._process_triplet_batch,
290
- tips=tips,
291
- tree_file=tree_file,
292
- groups_of_groups=groups_of_groups,
293
- outgroup_taxa=outgroup_taxa
294
- )
295
-
296
- # Process batches and merge results
297
- for batch in triplet_batches:
298
- batch_summary = process_func(batch)
299
- for tree_file_key, tree_data in batch_summary.items():
300
- if tree_file_key not in summary:
301
- summary[tree_file_key] = {}
302
- for sisters, count in tree_data.items():
303
- if sisters not in summary[tree_file_key]:
304
- summary[tree_file_key][sisters] = 0
305
- summary[tree_file_key][sisters] += count
435
+ try:
436
+ tree = self._read_tree_with_cache(tree_file)
437
+ prepared_tree = self._prepare_tree_for_triplets(tree, outgroup_taxa)
438
+ tree_summary = self._evaluate_tree_triplets_fast(
439
+ prepared_tree, groups_of_groups
440
+ )
441
+ if tree_summary:
442
+ tree_counts = summary.setdefault(tree_file, {})
443
+ for sisters, count in tree_summary.items():
444
+ tree_counts[sisters] = tree_counts.get(sisters, 0) + count
445
+ else:
446
+ legacy_counts = self._legacy_triplet_pass(
447
+ tips or self._guess_tips_from_groups(groups_of_groups, outgroup_taxa),
448
+ tree_file,
449
+ groups_of_groups,
450
+ outgroup_taxa,
451
+ )
452
+ if legacy_counts:
453
+ tree_counts = summary.setdefault(tree_file, {})
454
+ for sisters, count in legacy_counts.items():
455
+ tree_counts[sisters] = tree_counts.get(sisters, 0) + count
456
+ except FileNotFoundError:
457
+ legacy_counts = self._legacy_triplet_pass(
458
+ tips or self._guess_tips_from_groups(groups_of_groups, outgroup_taxa),
459
+ tree_file,
460
+ groups_of_groups,
461
+ outgroup_taxa,
462
+ )
463
+ if legacy_counts:
464
+ tree_counts = summary.setdefault(tree_file, {})
465
+ for sisters, count in legacy_counts.items():
466
+ tree_counts[sisters] = tree_counts.get(sisters, 0) + count
306
467
 
307
468
  return summary
308
469
 
@@ -0,0 +1 @@
1
+ __version__ = "2.1.2"
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.1
2
2
  Name: phykit
3
- Version: 2.1.0
3
+ Version: 2.1.2
4
4
  Home-page: https://github.com/jlsteenwyk/phykit
5
5
  Author: Jacob L. Steenwyk
6
6
  Author-email: jlsteenwyk@gmail.com
@@ -10,23 +10,11 @@ Classifier: Programming Language :: Python
10
10
  Classifier: Programming Language :: Python :: 3.9
11
11
  Classifier: Programming Language :: Python :: 3.10
12
12
  Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
13
15
  Classifier: Topic :: Scientific/Engineering
14
16
  Description-Content-Type: text/markdown
15
17
  License-File: LICENSE.md
16
- Requires-Dist: biopython>=1.82
17
- Requires-Dist: numpy>=1.24.0
18
- Requires-Dist: scipy>=1.11.3
19
- Requires-Dist: scikit-learn>=1.4.2
20
- Requires-Dist: cython
21
- Requires-Dist: tqdm>=4.65.0
22
- Dynamic: author
23
- Dynamic: author-email
24
- Dynamic: classifier
25
- Dynamic: description
26
- Dynamic: description-content-type
27
- Dynamic: home-page
28
- Dynamic: license-file
29
- Dynamic: requires-dist
30
18
 
31
19
  <p align="center">
32
20
  <a href="https://github.com/jlsteenwyk/phykit">
@@ -15,6 +15,8 @@ CLASSIFIERS = [
15
15
  'Programming Language :: Python :: 3.9',
16
16
  'Programming Language :: Python :: 3.10',
17
17
  'Programming Language :: Python :: 3.11',
18
+ 'Programming Language :: Python :: 3.12',
19
+ 'Programming Language :: Python :: 3.13',
18
20
  'Topic :: Scientific/Engineering',
19
21
  ]
20
22
 
@@ -1 +0,0 @@
1
- __version__ = "2.1.0"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes