<commit_before>package Bio::Phylo::PhyLoTA::Service::InferenceService::exabayes;
use Bio::Tools::Run::Phylo::ExaBayes;
use Bio::Phylo::PhyLoTA::Service::InferenceService;
use base 'Bio::Phylo::PhyLoTA::Service::InferenceService';

=head1 NAME

Bio::Phylo::PhyLoTA::Service::InferenceService::exabayes - Infers phylogenetic trees

=head1 DESCRIPTION

Provides functionality inferring trees and sets of trees by wrapping ExaBayes.

=over

=item configure

Provides the $config object to the wrapper so that settings defined in $config object can 
be applied to the wrapper.

=cut

sub configure {
    my ( $self ) = @_;
    my $tool    = $self->wrapper;
    my $config  = $self->config;
    my $logger  = $self->logger;
    my $outfile = $self->outfile;
        
    # set outfile name
    $logger->info("going to create output file $outfile");
    $tool->outfile_name($outfile);    

    # set mpirun location
    $logger->info("going to use mpirun executable ".$config->MPIRUN_BIN);
    $tool->mpirun($config->MPIRUN_BIN);
    
    # set number of nodes
    $logger->info("setting number of MPI nodes ".$config->NODES);
    $tool->nodes($config->NODES);

    # set exabayes location
    $logger->info("going to use executable ".$config->EXABAYES_BIN);
    $tool->executable($config->EXABAYES_BIN);
        
    # set parser location
    $logger->info("going to use parser executable ".$config->EXABAYES_PARSER_BIN);
    $tool->parser($config->EXABAYES_PARSER_BIN);
        
    # set number of independent runs
    $logger->info("going to perform ".$config->EXABAYES_NUMRUNS." independent runs");
    $tool->numRuns($config->EXABAYES_NUMRUNS);

    # set number of coupled chains
    $logger->info("setting coupled chains to ".$config->EXABAYES_NUMCHAINS);
    $tool->numCoupledChains($config->EXABAYES_NUMCHAINS);
        
    # set number of generations
    $logger->info("setting number of generations to ".$config->EXABAYES_NUMGENS);
    $tool->numGen($config->EXABAYES_NUMGENS);
            
    # set random seed to config value
    $tool->seed($config->RANDOM_SEED); 
    
    # set binary of consense program
    $logger->info("setting consense binary");
    $tool->consense_bin($config->EXABAYES_CONSENSE_BIN);
        
    # set burnin fraction
    $logger->info("setting burnin fraction to ".$config->BURNIN);
    $tool->burnin_fraction($config->BURNIN); 
}

=item create

Instantiates and configures the wrapper object, returns the instantiated wrapper.

=cut

sub create {
    my $self = shift;

    # instantiate helper objects and variables
    my $logger  = $self->logger;
    my $workdir = $self->workdir;
    my $tool    = Bio::Tools::Run::Phylo::ExaBayes->new;
    
    # set working directory
    $logger->info("going to use working directory $workdir");
    $tool->work_dir($workdir);        
        
    # set parsimony start
    $logger->info("setting ExaBayes to start from parsimony tree");
    $tool->parsimonyStart('true');
        
    # set run id to current process id
    $tool->run_id("infer_backbone_".$$);
        
    # set oufile format
    $logger->info("setting outfile format to 'newick'");
    $tool->outfile_format("newick");
        
    # set mode to zero (faster, but takes more memory)
    $logger->info("setting exabayes 'mode' argument to zero ");
    $tool->mode(0);             
    return $tool;
}

=item run

Runs the analysis on the provided supermatrix. Returns a tree file.

=cut

sub run {
    my ( $self, %args ) = @_;
    return $self->wrapper->run( '-phylip' => $args{'matrix'} );
}

=item is_bayesian

Indicates whether the inference service is bayesian, in which case we will
not run a bootstrapping analysis. Returns true.

=cut

sub is_bayesian { 1 }

=item cleanup

Cleans up any intermediate files.

=cut

sub cleanup {
    my $self = shift;
    my $runid = $self->wrapper->run_id;
    my $dir = $self->workdir;    

    # remove the input matrices
    unlink "${dir}/${runid}-dat.binary";
    unlink "${dir}/${runid}.nex";

    # remove single intermediate files
    for my $prefix ( qw(diagnostics checkpoint info prevCheckpointBackup ConsensusExtendedMajorityRuleNexus) ) {
        unlink "${dir}/ExaBayes_${prefix}.${runid}";
    }

    # remove files from chains
    opendir my $dh, $dir or die $!;
    while( my $entry = readdir $dh ) {
        if ( $entry =~ /ExaBayes_parameters.${runid}.\d+/ ) {
            unlink "${dir}/${entry}";
        }
    }
}

=back 

=cut

1;
<commit_msg>now interdigitates trees from different chains [skip ci]<commit_after>package Bio::Phylo::PhyLoTA::Service::InferenceService::exabayes;
use Bio::Tools::Run::Phylo::ExaBayes;
use Bio::Phylo::PhyLoTA::Service::InferenceService;
use base 'Bio::Phylo::PhyLoTA::Service::InferenceService';

=head1 NAME

Bio::Phylo::PhyLoTA::Service::InferenceService::exabayes - Infers phylogenetic trees

=head1 DESCRIPTION

Provides functionality inferring trees and sets of trees by wrapping ExaBayes.

=over

=item configure

Provides the $config object to the wrapper so that settings defined in $config object can 
be applied to the wrapper.

=cut

sub configure {
    my ( $self ) = @_;
    my $tool    = $self->wrapper;
    my $config  = $self->config;
    my $logger  = $self->logger;
    my $outfile = $self->outfile;
        
    # set outfile name
    $logger->info("going to create output file $outfile");
    $tool->outfile_name($outfile);    

    # set mpirun location
    $logger->info("going to use mpirun executable ".$config->MPIRUN_BIN);
    $tool->mpirun($config->MPIRUN_BIN);
    
    # set number of nodes
    $logger->info("setting number of MPI nodes ".$config->NODES);
    $tool->nodes($config->NODES);

    # set exabayes location
    $logger->info("going to use executable ".$config->EXABAYES_BIN);
    $tool->executable($config->EXABAYES_BIN);
        
    # set parser location
    $logger->info("going to use parser executable ".$config->EXABAYES_PARSER_BIN);
    $tool->parser($config->EXABAYES_PARSER_BIN);
        
    # set number of independent runs
    $logger->info("going to perform ".$config->EXABAYES_NUMRUNS." independent runs");
    $tool->numRuns($config->EXABAYES_NUMRUNS);

    # set number of coupled chains
    $logger->info("setting coupled chains to ".$config->EXABAYES_NUMCHAINS);
    $tool->numCoupledChains($config->EXABAYES_NUMCHAINS);
        
    # set number of generations
    $logger->info("setting number of generations to ".$config->EXABAYES_NUMGENS);
    $tool->numGen($config->EXABAYES_NUMGENS);
            
    # set random seed to config value
    $tool->seed($config->RANDOM_SEED); 
    
    # set binary of consense program
    $logger->info("setting consense binary");
    $tool->consense_bin($config->EXABAYES_CONSENSE_BIN);
        
    # set burnin fraction
    $logger->info("setting burnin fraction to ".$config->BURNIN);
    $tool->burnin_fraction($config->BURNIN); 
}

=item create

Instantiates and configures the wrapper object, returns the instantiated wrapper.

=cut

sub create {
    my $self = shift;

    # instantiate helper objects and variables
    my $logger  = $self->logger;
    my $workdir = $self->workdir;
    my $tool    = Bio::Tools::Run::Phylo::ExaBayes->new;
    
    # set working directory
    $logger->info("going to use working directory $workdir");
    $tool->work_dir($workdir);        
        
    # set parsimony start
    $logger->info("setting ExaBayes to start from parsimony tree");
    $tool->parsimonyStart('true');
        
    # set run id to current process id
    $tool->run_id("infer_backbone_".$$);
        
    # set oufile format
    $logger->info("setting outfile format to 'newick'");
    $tool->outfile_format("newick");
        
    # set mode to zero (faster, but takes more memory)
    $logger->info("setting exabayes 'mode' argument to zero ");
    $tool->mode(0);             
    return $tool;
}

=item run

Runs the analysis on the provided supermatrix. Returns a tree file.

=cut

sub run {
    my ( $self, %args ) = @_;
    my $consensus = $self->wrapper->run( '-phylip' => $args{'matrix'} );
    return $self->interdigitate;
}

=item interdigitate

Combines the set of topology (nexus) files into a single newick file such 
that the trees from the different chains are interdigitated so that the
correct burnin is discarded should a consense operation be applied to them.

=cut

sub interdigitate {
	my $self = shift;
    my $runid = $self->wrapper->run_id;
    my $dir = $self->workdir;    
    
    # assemble set of topology files
	my @files;
	opendir my $dh, $dir or die $!;
	while( my $entry = readdir $dh ) {
		if ( $entry =~ /ExaBayes_topologies.${runid}.\d+/ ) {
			push @files, "${dir}/${entry}";
		}
	}
	
	# iterate over files
	my @tree_sets;	
	for my $file ( @files ) {
		my %translate;
		my @trees;
		my $tflag = 0;		
		open my $fh, '<', $file or die $!;
		LINE: while(<$fh>) {
			chomp;
			
			# capture translation table
			$tflag++ and next LINE if /translate/;
			if ( $tflag and /^\s+(\d+)\s+(\d+)([,;])/ ) {
				my ( $id, $name, $token ) = ( $1, $2, $3 );
				$translate{$id} = $name;
				$tflag-- if $token eq ';';
			}
			
			# capture trees
			if ( /tree \S+ = \[&U\] = (.+;)/ ) {
				my $tree = $1;
				
				# capture the starting positions of all IDs in the tree string
				my %pos;
				while( $tree =~ /(\d+):/ ) {
					my $id = $1;
					$pos{$id} = pos($tree) - ( length($id) + 1 );
				}
				
				# replace IDs with names, working from right to left
				for my $id ( sort { $pos{$b} <=> $pos{$a} } keys %pos ) {
					$tree = substr $tree, $pos{$id}, length($id), $translate{$id};
				}
				push @trees, $tree;
			}
		}
		push @tree_sets, \@trees;	
	}
	
	# merge
	my @interdigitated;		
	my $i = 0;
	TREE: while(1) {
		my $have_trees = 0;
		for my $set ( @tree_sets ) {
			if ( $set->[$i] ) {
				$have_trees++;
				push @interdigitated, $set->[$i];
			}
		}
		last TREE unless $have_trees;
	}
	
	# write to file
	open my $out, '>', $self->outfile or die $!;
	print $out join "\n", @interdigitated;
	return $self->outfile;
}

=item is_bayesian

Indicates whether the inference service is bayesian, in which case we will
not run a bootstrapping analysis. Returns true.

=cut

sub is_bayesian { 1 }

=item cleanup

Cleans up any intermediate files.

=cut

sub cleanup {
    my $self = shift;
    my $runid = $self->wrapper->run_id;
    my $dir = $self->workdir;    

    # remove the input matrices
    unlink "${dir}/${runid}-dat.binary";
    unlink "${dir}/${runid}.nex";

    # remove single intermediate files
    for my $prefix ( qw(diagnostics checkpoint info prevCheckpointBackup ConsensusExtendedMajorityRuleNexus) ) {
        unlink "${dir}/ExaBayes_${prefix}.${runid}";
    }

    # remove files from chains
    opendir my $dh, $dir or die $!;
    while( my $entry = readdir $dh ) {
        if ( $entry =~ /ExaBayes_parameters.${runid}.\d+/ ) {
            unlink "${dir}/${entry}";
        }
    }
}

=back 

=cut

1;

{'fraction_non_alphanumeric': 0.14067222273101931, 'fraction_numerical': 0.0018316695668101474, 'mean_word_length': 3.1021788129226144, 'pattern_counts': {'":': 0, '<': 6, '<?xml version=': 0, '>': 136, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '254292', 'n_tokens_mistral': 3845, 'n_tokens_neox': 3632, 'n_words': 992}