phykit 2.1.92__tar.gz → 2.1.93__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.
- {phykit-2.1.92 → phykit-2.1.93}/PKG-INFO +1 -1
- {phykit-2.1.92 → phykit-2.1.93}/phykit/phykit.py +53 -9
- phykit-2.1.93/phykit/services/tree/nearest_neighbor_interchange.py +313 -0
- phykit-2.1.93/phykit/version.py +1 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit.egg-info/PKG-INFO +1 -1
- phykit-2.1.92/phykit/services/tree/nearest_neighbor_interchange.py +0 -155
- phykit-2.1.92/phykit/version.py +0 -1
- {phykit-2.1.92 → phykit-2.1.93}/LICENSE.md +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/README.md +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/__init__.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/__main__.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/cli_registry.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/errors.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/helpers/__init__.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/helpers/boolean_argument_parsing.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/helpers/caching.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/helpers/circular_layout.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/helpers/color_annotations.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/helpers/discrete_models.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/helpers/files.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/helpers/geological_timescale.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/helpers/json_output.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/helpers/parallel.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/helpers/parsimony_utils.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/helpers/pgls_utils.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/helpers/plot_config.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/helpers/quartet_utils.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/helpers/stats_summary.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/helpers/streaming.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/helpers/trait_parsing.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/service_factories.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/__init__.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/alignment/__init__.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/alignment/alignment_entropy.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/alignment/alignment_length.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/alignment/alignment_length_no_gaps.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/alignment/alignment_outlier_taxa.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/alignment/alignment_recoding.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/alignment/alignment_subsample.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/alignment/base.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/alignment/column_score.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/alignment/composition_per_taxon.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/alignment/compositional_bias_per_site.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/alignment/create_concatenation_matrix.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/alignment/dfoil.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/alignment/dna_threader.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/alignment/dstatistic.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/alignment/evolutionary_rate_per_site.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/alignment/faidx.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/alignment/gc_content.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/alignment/identity_matrix.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/alignment/mask_alignment.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/alignment/occupancy_filter.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/alignment/occupancy_per_taxon.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/alignment/pairwise_identity.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/alignment/parsimony_informative_sites.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/alignment/phylo_gwas.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/alignment/plot_alignment_qc.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/alignment/rcv.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/alignment/rcvt.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/alignment/rename_fasta_entries.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/alignment/sum_of_pairs_score.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/alignment/taxon_groups.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/alignment/variable_sites.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/base.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/__init__.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/ancestral_reconstruction.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/base.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/bipartition_support_stats.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/branch_length_multiplier.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/character_map.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/chronogram.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/collapse_branches.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/concordance_asr.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/consensus_network.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/consensus_tree.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/cont_map.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/cophylo.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/covarying_evolutionary_rates.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/density_map.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/discordance_asymmetry.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/dtt.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/dvmc.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/evo_tempo_map.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/evolutionary_rate.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/faiths_pd.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/fit_continuous.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/fit_discrete.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/hidden_paralogy_check.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/hybridization.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/independent_contrasts.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/internal_branch_stats.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/internode_labeler.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/kf_distance.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/last_common_ancestor_subtree.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/lb_score.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/ltt.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/monophyly_check.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/neighbor_net.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/network_signal.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/ou_shift_detection.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/ouwie.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/parsimony_score.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/patristic_distances.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/phenogram.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/phylo_anova.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/phylo_heatmap.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/phylo_impute.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/phylo_logistic.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/phylo_path.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/phylogenetic_glm.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/phylogenetic_ordination.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/phylogenetic_regression.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/phylogenetic_signal.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/phylomorphospace.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/polytomy_test.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/print_tree.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/prune_tree.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/quartet_network.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/quartet_pie.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/rate_heterogeneity.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/relative_rate_test.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/rename_tree_tips.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/rf_distance.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/root_tree.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/saturation.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/simmap_summary.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/spectral_discordance.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/spr.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/spurious_sequence.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/stochastic_character_map.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/terminal_branch_stats.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/threshold_model.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/tip_labels.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/tip_to_tip_distance.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/tip_to_tip_node_distance.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/total_tree_length.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/trait_correlation.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/trait_rate_map.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/transfer_annotations.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/tree_space.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/treeness.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/treeness_over_rcv.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit/services/tree/vcv_utils.py +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit.egg-info/SOURCES.txt +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit.egg-info/dependency_links.txt +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit.egg-info/entry_points.txt +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit.egg-info/requires.txt +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/phykit.egg-info/top_level.txt +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/setup.cfg +0 -0
- {phykit-2.1.92 → phykit-2.1.93}/setup.py +0 -0
|
@@ -3742,10 +3742,19 @@ class Phykit:
|
|
|
3742
3742
|
f"""\
|
|
3743
3743
|
{help_header}
|
|
3744
3744
|
|
|
3745
|
-
Generate
|
|
3746
|
-
rooted tree.
|
|
3745
|
+
Generate nearest neighbor interchange (NNI) moves for a
|
|
3746
|
+
binary rooted tree.
|
|
3747
3747
|
|
|
3748
|
-
|
|
3748
|
+
By default, all NNI rearrangements are emitted. Pass
|
|
3749
|
+
--branch (a comma-separated pair of taxa) or --branches
|
|
3750
|
+
(a file with one pair per line) to restrict output to the
|
|
3751
|
+
two NNI rearrangements around a specific internal branch.
|
|
3752
|
+
The branch is identified as the edge leading to the MRCA
|
|
3753
|
+
of the supplied taxa. Useful for branch-by-branch
|
|
3754
|
+
likelihood comparison in IQ-TREE / RAxML.
|
|
3755
|
+
|
|
3756
|
+
The output file includes the input phylogeny at the top
|
|
3757
|
+
unless --no-input-tree is supplied.
|
|
3749
3758
|
|
|
3750
3759
|
Aliases:
|
|
3751
3760
|
nearest_neighbor_interchange, nni
|
|
@@ -3753,22 +3762,49 @@ class Phykit:
|
|
|
3753
3762
|
pk_nearest_neighbor_interchange, pk_nni
|
|
3754
3763
|
|
|
3755
3764
|
Usage:
|
|
3756
|
-
phykit nearest_neighbor_interchange <tree>
|
|
3765
|
+
phykit nearest_neighbor_interchange <tree>
|
|
3766
|
+
[-o/--output <output_file>] [--branch A,B]
|
|
3767
|
+
[--branches <file>] [--no-input-tree] [--json]
|
|
3757
3768
|
|
|
3758
3769
|
Options
|
|
3759
3770
|
=====================================================
|
|
3760
|
-
<tree> first argument after
|
|
3771
|
+
<tree> first argument after
|
|
3761
3772
|
function name should be
|
|
3762
3773
|
a tree file
|
|
3763
3774
|
|
|
3764
3775
|
-o/--output name of output file that will
|
|
3765
3776
|
contain all trees with the
|
|
3766
3777
|
nearest neighbor interchange
|
|
3767
|
-
moves.
|
|
3768
|
-
Default output will have
|
|
3778
|
+
moves.
|
|
3779
|
+
Default output will have
|
|
3769
3780
|
the same name as the input
|
|
3770
|
-
file but with the suffix
|
|
3771
|
-
".
|
|
3781
|
+
file but with the suffix
|
|
3782
|
+
".nnis"
|
|
3783
|
+
|
|
3784
|
+
--branch optional argument. Two taxa
|
|
3785
|
+
separated by a comma
|
|
3786
|
+
(e.g., --branch A,B) whose
|
|
3787
|
+
MRCA defines the internal
|
|
3788
|
+
branch to rearrange. Emits
|
|
3789
|
+
the two alternative NNI
|
|
3790
|
+
topologies for that branch.
|
|
3791
|
+
|
|
3792
|
+
--branches optional argument. Path to a
|
|
3793
|
+
file with one taxon pair per
|
|
3794
|
+
line (comma- or tab-
|
|
3795
|
+
separated). Lines may
|
|
3796
|
+
optionally start with a
|
|
3797
|
+
label followed by the two
|
|
3798
|
+
taxa, in which case the
|
|
3799
|
+
label is echoed in the
|
|
3800
|
+
--json report. Lines that
|
|
3801
|
+
are blank or start with #
|
|
3802
|
+
are ignored. Each pair
|
|
3803
|
+
yields two NNI trees.
|
|
3804
|
+
|
|
3805
|
+
--no-input-tree optional argument. Omit the
|
|
3806
|
+
input phylogeny from the
|
|
3807
|
+
top of the output file.
|
|
3772
3808
|
|
|
3773
3809
|
--json optional argument to output
|
|
3774
3810
|
results as JSON
|
|
@@ -3777,6 +3813,14 @@ class Phykit:
|
|
|
3777
3813
|
)
|
|
3778
3814
|
parser.add_argument("tree", type=str, help=SUPPRESS)
|
|
3779
3815
|
parser.add_argument("-o", "--output", type=str, required=False, help=SUPPRESS)
|
|
3816
|
+
parser.add_argument("--branch", type=str, default=None, help=SUPPRESS)
|
|
3817
|
+
parser.add_argument("--branches", type=str, default=None, help=SUPPRESS)
|
|
3818
|
+
parser.add_argument(
|
|
3819
|
+
"--no-input-tree",
|
|
3820
|
+
action="store_true",
|
|
3821
|
+
default=False,
|
|
3822
|
+
help=SUPPRESS,
|
|
3823
|
+
)
|
|
3780
3824
|
_add_json_argument(parser)
|
|
3781
3825
|
_run_service(parser, argv, NearestNeighborInterchange)
|
|
3782
3826
|
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from typing import Dict, List, Optional, Tuple
|
|
3
|
+
import pickle
|
|
4
|
+
|
|
5
|
+
from Bio import Phylo
|
|
6
|
+
from Bio.Phylo import Newick
|
|
7
|
+
|
|
8
|
+
from .base import Tree
|
|
9
|
+
from ...errors import PhykitUserError
|
|
10
|
+
from ...helpers.json_output import print_json
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
BranchSpec = Tuple[str, List[str]]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class NearestNeighborInterchange(Tree):
|
|
17
|
+
def __init__(self, args) -> None:
|
|
18
|
+
parsed = self.process_args(args)
|
|
19
|
+
super().__init__(
|
|
20
|
+
tree_file_path=parsed["tree_file_path"],
|
|
21
|
+
output_file_path=parsed["output_file_path"],
|
|
22
|
+
)
|
|
23
|
+
self.json_output = parsed["json_output"]
|
|
24
|
+
self.branch = parsed["branch"]
|
|
25
|
+
self.branches_file = parsed["branches_file"]
|
|
26
|
+
self.no_input_tree = parsed["no_input_tree"]
|
|
27
|
+
|
|
28
|
+
def run(self) -> None:
|
|
29
|
+
tree = self.read_tree_file()
|
|
30
|
+
|
|
31
|
+
branch_specs = self._resolve_branch_specs()
|
|
32
|
+
|
|
33
|
+
if branch_specs:
|
|
34
|
+
output_trees, branch_report = self._generate_targeted_nnis(
|
|
35
|
+
tree, branch_specs
|
|
36
|
+
)
|
|
37
|
+
else:
|
|
38
|
+
output_trees = self.get_neighbors(tree)
|
|
39
|
+
branch_report = None
|
|
40
|
+
|
|
41
|
+
trees_to_write = output_trees if self.no_input_tree else [tree, *output_trees]
|
|
42
|
+
Phylo.write(trees_to_write, self.output_file_path, "newick")
|
|
43
|
+
|
|
44
|
+
if self.json_output:
|
|
45
|
+
payload = dict(
|
|
46
|
+
input_tree=self.tree_file_path,
|
|
47
|
+
total_trees=len(trees_to_write),
|
|
48
|
+
nni_neighbors=len(output_trees),
|
|
49
|
+
output_file=self.output_file_path,
|
|
50
|
+
)
|
|
51
|
+
if branch_report is not None:
|
|
52
|
+
payload["branches"] = branch_report
|
|
53
|
+
print_json(payload)
|
|
54
|
+
|
|
55
|
+
def process_args(self, args) -> Dict[str, str]:
|
|
56
|
+
tree_file_path = args.tree
|
|
57
|
+
output_file_path = \
|
|
58
|
+
f"{args.output}" if args.output else f"{tree_file_path}.nnis"
|
|
59
|
+
|
|
60
|
+
branch = None
|
|
61
|
+
raw_branch = getattr(args, "branch", None)
|
|
62
|
+
if raw_branch:
|
|
63
|
+
parts = [p.strip() for p in re.split(r"[,\t]", raw_branch) if p.strip()]
|
|
64
|
+
if len(parts) != 2:
|
|
65
|
+
raise PhykitUserError(
|
|
66
|
+
[
|
|
67
|
+
"--branch must specify exactly two taxa separated by a "
|
|
68
|
+
"comma (e.g., --branch A,B)"
|
|
69
|
+
],
|
|
70
|
+
code=2,
|
|
71
|
+
)
|
|
72
|
+
branch = tuple(parts)
|
|
73
|
+
|
|
74
|
+
return dict(
|
|
75
|
+
tree_file_path=tree_file_path,
|
|
76
|
+
output_file_path=output_file_path,
|
|
77
|
+
branch=branch,
|
|
78
|
+
branches_file=getattr(args, "branches", None),
|
|
79
|
+
no_input_tree=getattr(args, "no_input_tree", False),
|
|
80
|
+
json_output=getattr(args, "json", False),
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
def _resolve_branch_specs(self) -> List[BranchSpec]:
|
|
84
|
+
specs: List[BranchSpec] = []
|
|
85
|
+
if self.branch:
|
|
86
|
+
t1, t2 = self.branch
|
|
87
|
+
specs.append((f"{t1}|{t2}", [t1, t2]))
|
|
88
|
+
if self.branches_file:
|
|
89
|
+
with open(self.branches_file) as fh:
|
|
90
|
+
for i, raw in enumerate(fh, start=1):
|
|
91
|
+
line = raw.strip()
|
|
92
|
+
if not line or line.startswith("#"):
|
|
93
|
+
continue
|
|
94
|
+
parts = [p.strip() for p in re.split(r"[,\t]", line) if p.strip()]
|
|
95
|
+
if len(parts) < 2:
|
|
96
|
+
raise PhykitUserError(
|
|
97
|
+
[
|
|
98
|
+
f"--branches file line {i}: need at least two "
|
|
99
|
+
"taxa per line (comma- or tab-separated)"
|
|
100
|
+
],
|
|
101
|
+
code=2,
|
|
102
|
+
)
|
|
103
|
+
if len(parts) >= 3:
|
|
104
|
+
label, t1, t2 = parts[0], parts[1], parts[2]
|
|
105
|
+
else:
|
|
106
|
+
t1, t2 = parts[0], parts[1]
|
|
107
|
+
label = f"{t1}|{t2}"
|
|
108
|
+
specs.append((label, [t1, t2]))
|
|
109
|
+
return specs
|
|
110
|
+
|
|
111
|
+
def _generate_targeted_nnis(
|
|
112
|
+
self,
|
|
113
|
+
tree: Newick.Tree,
|
|
114
|
+
branch_specs: List[BranchSpec],
|
|
115
|
+
) -> Tuple[List[Newick.Tree], List[Dict]]:
|
|
116
|
+
tip_names = {term.name for term in tree.get_terminals()}
|
|
117
|
+
output_trees: List[Newick.Tree] = []
|
|
118
|
+
report: List[Dict] = []
|
|
119
|
+
|
|
120
|
+
for label, taxa in branch_specs:
|
|
121
|
+
missing = [t for t in taxa if t not in tip_names]
|
|
122
|
+
if missing:
|
|
123
|
+
raise PhykitUserError(
|
|
124
|
+
[
|
|
125
|
+
f"branch '{label}': taxa not found in tree: "
|
|
126
|
+
+ ", ".join(missing)
|
|
127
|
+
],
|
|
128
|
+
code=2,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
working = self._fast_tree_copy(tree)
|
|
132
|
+
parents = self._build_parent_map(working)
|
|
133
|
+
target = working.common_ancestor(taxa)
|
|
134
|
+
|
|
135
|
+
if target is working.root:
|
|
136
|
+
raise PhykitUserError(
|
|
137
|
+
[
|
|
138
|
+
f"branch '{label}': MRCA of {taxa[0]} and {taxa[1]} is "
|
|
139
|
+
"the root; no internal branch above to rearrange"
|
|
140
|
+
],
|
|
141
|
+
code=2,
|
|
142
|
+
)
|
|
143
|
+
if target.is_terminal():
|
|
144
|
+
raise PhykitUserError(
|
|
145
|
+
[
|
|
146
|
+
f"branch '{label}': MRCA of {taxa[0]} and {taxa[1]} is "
|
|
147
|
+
"a terminal; NNI requires an internal branch"
|
|
148
|
+
],
|
|
149
|
+
code=2,
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
nnis = self._nnis_around_branch(working, target, parents)
|
|
153
|
+
output_trees.extend(nnis)
|
|
154
|
+
report.append(
|
|
155
|
+
dict(label=label, taxa=list(taxa), n_nnis=len(nnis))
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
return output_trees, report
|
|
159
|
+
|
|
160
|
+
def _fast_tree_copy(self, tree: Newick.Tree) -> Newick.Tree:
|
|
161
|
+
"""Fast tree copying using pickle instead of deep copy."""
|
|
162
|
+
return pickle.loads(pickle.dumps(tree, protocol=pickle.HIGHEST_PROTOCOL))
|
|
163
|
+
|
|
164
|
+
def _build_parent_map(self, tree: Newick.Tree) -> Dict:
|
|
165
|
+
parents = {}
|
|
166
|
+
for clade in tree.find_clades():
|
|
167
|
+
if clade != tree.root:
|
|
168
|
+
node_path = tree.get_path(clade)
|
|
169
|
+
if len(node_path) == 1:
|
|
170
|
+
parents[clade] = tree.root
|
|
171
|
+
else:
|
|
172
|
+
parents[clade] = node_path[-2]
|
|
173
|
+
return parents
|
|
174
|
+
|
|
175
|
+
def _nnis_around_branch(
|
|
176
|
+
self,
|
|
177
|
+
tree: Newick.Tree,
|
|
178
|
+
clade,
|
|
179
|
+
parents: Dict,
|
|
180
|
+
) -> List[Newick.Tree]:
|
|
181
|
+
"""Generate the 2 NNI rearrangements around the branch leading to clade.
|
|
182
|
+
|
|
183
|
+
Mutates the tree in place but restores the original topology before
|
|
184
|
+
returning so the caller can request additional rearrangements on the
|
|
185
|
+
same tree.
|
|
186
|
+
"""
|
|
187
|
+
if clade.is_terminal():
|
|
188
|
+
return []
|
|
189
|
+
|
|
190
|
+
root_children = tree.root.clades
|
|
191
|
+
is_root_child = clade in root_children
|
|
192
|
+
if is_root_child:
|
|
193
|
+
other = (
|
|
194
|
+
root_children[1] if clade is root_children[0] else root_children[0]
|
|
195
|
+
)
|
|
196
|
+
if other.is_terminal():
|
|
197
|
+
# Implicit root edge can only be rearranged if both root
|
|
198
|
+
# children are internal — otherwise there is no subtree on
|
|
199
|
+
# the sister side to swap.
|
|
200
|
+
return []
|
|
201
|
+
return self._nnis_root_edge(tree)
|
|
202
|
+
|
|
203
|
+
parent = parents.get(clade)
|
|
204
|
+
if parent is None:
|
|
205
|
+
return []
|
|
206
|
+
return self._nnis_internal_edge(tree, clade, parent)
|
|
207
|
+
|
|
208
|
+
def _nnis_root_edge(self, tree: Newick.Tree) -> List[Newick.Tree]:
|
|
209
|
+
neighbors: List[Newick.Tree] = []
|
|
210
|
+
left = tree.root.clades[0]
|
|
211
|
+
right = tree.root.clades[1]
|
|
212
|
+
left_right = left.clades[1]
|
|
213
|
+
right_left = right.clades[0]
|
|
214
|
+
right_right = right.clades[1]
|
|
215
|
+
|
|
216
|
+
# neighbor 1: swap left.clades[1] with right.clades[1]
|
|
217
|
+
del left.clades[1]
|
|
218
|
+
del right.clades[1]
|
|
219
|
+
left.clades.append(right_right)
|
|
220
|
+
right.clades.append(left_right)
|
|
221
|
+
neighbors.append(self._fast_tree_copy(tree))
|
|
222
|
+
|
|
223
|
+
# neighbor 2: swap left.clades[1] with right.clades[0]
|
|
224
|
+
del left.clades[1]
|
|
225
|
+
del right.clades[0]
|
|
226
|
+
left.clades.append(right_left)
|
|
227
|
+
right.clades.append(right_right)
|
|
228
|
+
neighbors.append(self._fast_tree_copy(tree))
|
|
229
|
+
|
|
230
|
+
# restore original
|
|
231
|
+
del left.clades[1]
|
|
232
|
+
del right.clades[0]
|
|
233
|
+
left.clades.append(left_right)
|
|
234
|
+
right.clades.insert(0, right_left)
|
|
235
|
+
return neighbors
|
|
236
|
+
|
|
237
|
+
def _nnis_internal_edge(
|
|
238
|
+
self,
|
|
239
|
+
tree: Newick.Tree,
|
|
240
|
+
clade,
|
|
241
|
+
parent,
|
|
242
|
+
) -> List[Newick.Tree]:
|
|
243
|
+
neighbors: List[Newick.Tree] = []
|
|
244
|
+
left = clade.clades[0]
|
|
245
|
+
right = clade.clades[1]
|
|
246
|
+
|
|
247
|
+
if clade is parent.clades[0]:
|
|
248
|
+
sister = parent.clades[1]
|
|
249
|
+
# neighbor 1: swap clade.clades[1] with sister
|
|
250
|
+
del parent.clades[1]
|
|
251
|
+
del clade.clades[1]
|
|
252
|
+
parent.clades.append(right)
|
|
253
|
+
clade.clades.append(sister)
|
|
254
|
+
neighbors.append(self._fast_tree_copy(tree))
|
|
255
|
+
# neighbor 2: swap clade.clades[0] with sister (current parent.clades[1] is `right`)
|
|
256
|
+
del parent.clades[1]
|
|
257
|
+
del clade.clades[0]
|
|
258
|
+
parent.clades.append(left)
|
|
259
|
+
clade.clades.append(right)
|
|
260
|
+
neighbors.append(self._fast_tree_copy(tree))
|
|
261
|
+
# restore
|
|
262
|
+
del parent.clades[1]
|
|
263
|
+
del clade.clades[0]
|
|
264
|
+
parent.clades.append(sister)
|
|
265
|
+
clade.clades.insert(0, left)
|
|
266
|
+
else:
|
|
267
|
+
sister = parent.clades[0]
|
|
268
|
+
# neighbor 1
|
|
269
|
+
del parent.clades[0]
|
|
270
|
+
del clade.clades[1]
|
|
271
|
+
parent.clades.insert(0, right)
|
|
272
|
+
clade.clades.append(sister)
|
|
273
|
+
neighbors.append(self._fast_tree_copy(tree))
|
|
274
|
+
# neighbor 2
|
|
275
|
+
del parent.clades[0]
|
|
276
|
+
del clade.clades[0]
|
|
277
|
+
parent.clades.insert(0, left)
|
|
278
|
+
clade.clades.append(right)
|
|
279
|
+
neighbors.append(self._fast_tree_copy(tree))
|
|
280
|
+
# restore
|
|
281
|
+
del parent.clades[0]
|
|
282
|
+
del clade.clades[0]
|
|
283
|
+
parent.clades.insert(0, sister)
|
|
284
|
+
clade.clades.insert(0, left)
|
|
285
|
+
|
|
286
|
+
return neighbors
|
|
287
|
+
|
|
288
|
+
def get_neighbors(
|
|
289
|
+
self,
|
|
290
|
+
tree: Newick.Tree
|
|
291
|
+
) -> List[Newick.Tree]:
|
|
292
|
+
### This code is from BioPython (so is this comment)
|
|
293
|
+
# Get all neighbor trees of the given tree (PRIVATE).
|
|
294
|
+
# Currently only for binary rooted trees.
|
|
295
|
+
###
|
|
296
|
+
parents = self._build_parent_map(tree)
|
|
297
|
+
neighbors: List[Newick.Tree] = []
|
|
298
|
+
root_childs = []
|
|
299
|
+
for clade in tree.get_nonterminals(order="level"):
|
|
300
|
+
if clade == tree.root:
|
|
301
|
+
left = clade.clades[0]
|
|
302
|
+
right = clade.clades[1]
|
|
303
|
+
root_childs.append(left)
|
|
304
|
+
root_childs.append(right)
|
|
305
|
+
if not left.is_terminal() and not right.is_terminal():
|
|
306
|
+
neighbors.extend(self._nnis_root_edge(tree))
|
|
307
|
+
elif clade in root_childs:
|
|
308
|
+
continue
|
|
309
|
+
else:
|
|
310
|
+
parent = parents[clade]
|
|
311
|
+
neighbors.extend(self._nnis_internal_edge(tree, clade, parent))
|
|
312
|
+
|
|
313
|
+
return neighbors
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.1.93"
|
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
from typing import Dict, List
|
|
2
|
-
import pickle
|
|
3
|
-
|
|
4
|
-
from Bio import Phylo
|
|
5
|
-
from Bio.Phylo import Newick
|
|
6
|
-
|
|
7
|
-
from .base import Tree
|
|
8
|
-
from ...helpers.json_output import print_json
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class NearestNeighborInterchange(Tree):
|
|
12
|
-
def __init__(self, args) -> None:
|
|
13
|
-
parsed = self.process_args(args)
|
|
14
|
-
super().__init__(
|
|
15
|
-
tree_file_path=parsed["tree_file_path"],
|
|
16
|
-
output_file_path=parsed["output_file_path"],
|
|
17
|
-
)
|
|
18
|
-
self.json_output = parsed["json_output"]
|
|
19
|
-
|
|
20
|
-
def run(self) -> None:
|
|
21
|
-
tree = self.read_tree_file()
|
|
22
|
-
|
|
23
|
-
# Use standard neighbor generation with optimized copying
|
|
24
|
-
all_nnis = [tree]
|
|
25
|
-
all_nnis.extend(self.get_neighbors(tree))
|
|
26
|
-
Phylo.write(all_nnis, self.output_file_path, "newick")
|
|
27
|
-
if self.json_output:
|
|
28
|
-
print_json(
|
|
29
|
-
dict(
|
|
30
|
-
input_tree=self.tree_file_path,
|
|
31
|
-
total_trees=len(all_nnis),
|
|
32
|
-
nni_neighbors=max(0, len(all_nnis) - 1),
|
|
33
|
-
output_file=self.output_file_path,
|
|
34
|
-
)
|
|
35
|
-
)
|
|
36
|
-
|
|
37
|
-
def process_args(self, args) -> Dict[str, str]:
|
|
38
|
-
tree_file_path = args.tree
|
|
39
|
-
output_file_path = \
|
|
40
|
-
f"{args.output}" if args.output else f"{tree_file_path}.nnis"
|
|
41
|
-
|
|
42
|
-
return dict(
|
|
43
|
-
tree_file_path=tree_file_path,
|
|
44
|
-
output_file_path=output_file_path,
|
|
45
|
-
json_output=getattr(args, "json", False),
|
|
46
|
-
)
|
|
47
|
-
|
|
48
|
-
def _fast_tree_copy(self, tree: Newick.Tree) -> Newick.Tree:
|
|
49
|
-
"""Fast tree copying using pickle instead of deep copy."""
|
|
50
|
-
return pickle.loads(pickle.dumps(tree, protocol=pickle.HIGHEST_PROTOCOL))
|
|
51
|
-
|
|
52
|
-
def get_neighbors(
|
|
53
|
-
self,
|
|
54
|
-
tree: Newick.Tree
|
|
55
|
-
) -> List[Newick.Tree]:
|
|
56
|
-
### This code is from BioPython (so is this comment)
|
|
57
|
-
# Get all neighbor trees of the given tree (PRIVATE).
|
|
58
|
-
# Currently only for binary rooted trees.
|
|
59
|
-
###
|
|
60
|
-
# make child to parent dict
|
|
61
|
-
parents = {}
|
|
62
|
-
for clade in tree.find_clades():
|
|
63
|
-
if clade != tree.root:
|
|
64
|
-
node_path = tree.get_path(clade)
|
|
65
|
-
# cannot get the parent if the parent is root. Bug?
|
|
66
|
-
if len(node_path) == 1:
|
|
67
|
-
parents[clade] = tree.root
|
|
68
|
-
else:
|
|
69
|
-
parents[clade] = node_path[-2]
|
|
70
|
-
neighbors = []
|
|
71
|
-
root_childs = []
|
|
72
|
-
for clade in tree.get_nonterminals(order="level"):
|
|
73
|
-
if clade == tree.root:
|
|
74
|
-
left = clade.clades[0]
|
|
75
|
-
right = clade.clades[1]
|
|
76
|
-
root_childs.append(left)
|
|
77
|
-
root_childs.append(right)
|
|
78
|
-
if not left.is_terminal() and not right.is_terminal():
|
|
79
|
-
# make changes around the left_left clade
|
|
80
|
-
# left_left = left.clades[0]
|
|
81
|
-
left_right = left.clades[1]
|
|
82
|
-
right_left = right.clades[0]
|
|
83
|
-
right_right = right.clades[1]
|
|
84
|
-
# neighbor 1 (left_left + right_right)
|
|
85
|
-
del left.clades[1]
|
|
86
|
-
del right.clades[1]
|
|
87
|
-
left.clades.append(right_right)
|
|
88
|
-
right.clades.append(left_right)
|
|
89
|
-
temp_tree = self._fast_tree_copy(tree)
|
|
90
|
-
neighbors.append(temp_tree)
|
|
91
|
-
# neighbor 2 (left_left + right_left)
|
|
92
|
-
del left.clades[1]
|
|
93
|
-
del right.clades[0]
|
|
94
|
-
left.clades.append(right_left)
|
|
95
|
-
right.clades.append(right_right)
|
|
96
|
-
temp_tree = self._fast_tree_copy(tree)
|
|
97
|
-
neighbors.append(temp_tree)
|
|
98
|
-
# change back (left_left + left_right)
|
|
99
|
-
del left.clades[1]
|
|
100
|
-
del right.clades[0]
|
|
101
|
-
left.clades.append(left_right)
|
|
102
|
-
right.clades.insert(0, right_left)
|
|
103
|
-
elif clade in root_childs:
|
|
104
|
-
# skip root child
|
|
105
|
-
continue
|
|
106
|
-
else:
|
|
107
|
-
# method for other clades
|
|
108
|
-
# make changes around the parent clade
|
|
109
|
-
left = clade.clades[0]
|
|
110
|
-
right = clade.clades[1]
|
|
111
|
-
parent = parents[clade]
|
|
112
|
-
if clade == parent.clades[0]:
|
|
113
|
-
sister = parent.clades[1]
|
|
114
|
-
# neighbor 1 (parent + right)
|
|
115
|
-
del parent.clades[1]
|
|
116
|
-
del clade.clades[1]
|
|
117
|
-
parent.clades.append(right)
|
|
118
|
-
clade.clades.append(sister)
|
|
119
|
-
temp_tree = self._fast_tree_copy(tree)
|
|
120
|
-
neighbors.append(temp_tree)
|
|
121
|
-
# neighbor 2 (parent + left)
|
|
122
|
-
del parent.clades[1]
|
|
123
|
-
del clade.clades[0]
|
|
124
|
-
parent.clades.append(left)
|
|
125
|
-
clade.clades.append(right)
|
|
126
|
-
temp_tree = self._fast_tree_copy(tree)
|
|
127
|
-
neighbors.append(temp_tree)
|
|
128
|
-
# change back (parent + sister)
|
|
129
|
-
del parent.clades[1]
|
|
130
|
-
del clade.clades[0]
|
|
131
|
-
parent.clades.append(sister)
|
|
132
|
-
clade.clades.insert(0, left)
|
|
133
|
-
else:
|
|
134
|
-
sister = parent.clades[0]
|
|
135
|
-
# neighbor 1 (parent + right)
|
|
136
|
-
del parent.clades[0]
|
|
137
|
-
del clade.clades[1]
|
|
138
|
-
parent.clades.insert(0, right)
|
|
139
|
-
clade.clades.append(sister)
|
|
140
|
-
temp_tree = self._fast_tree_copy(tree)
|
|
141
|
-
neighbors.append(temp_tree)
|
|
142
|
-
# neighbor 2 (parent + left)
|
|
143
|
-
del parent.clades[0]
|
|
144
|
-
del clade.clades[0]
|
|
145
|
-
parent.clades.insert(0, left)
|
|
146
|
-
clade.clades.append(right)
|
|
147
|
-
temp_tree = self._fast_tree_copy(tree)
|
|
148
|
-
neighbors.append(temp_tree)
|
|
149
|
-
# change back (parent + sister)
|
|
150
|
-
del parent.clades[0]
|
|
151
|
-
del clade.clades[0]
|
|
152
|
-
parent.clades.insert(0, sister)
|
|
153
|
-
clade.clades.insert(0, left)
|
|
154
|
-
|
|
155
|
-
return neighbors
|
phykit-2.1.92/phykit/version.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "2.1.92"
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|