Spaces:
No application file
No application file
# Copyright (C) 2011 by Brandon Invergo ([email protected]) | |
# | |
# This file is part of the Biopython distribution and governed by your | |
# choice of the "Biopython License Agreement" or the "BSD 3-Clause License". | |
# Please see the LICENSE file that should have been included as part of this | |
# package. | |
"""Base class for the support of PAML, Phylogenetic Analysis by Maximum Likelihood.""" | |
import os | |
import subprocess | |
class PamlError(EnvironmentError): | |
"""paml has failed. | |
Run with verbose=True to view the error message. | |
""" | |
class Paml: | |
"""Base class for wrapping PAML commands.""" | |
def __init__(self, alignment=None, working_dir=None, out_file=None): | |
"""Initialize the class.""" | |
if working_dir is None: | |
self.working_dir = os.getcwd() | |
else: | |
self.working_dir = working_dir | |
if alignment is not None: | |
if not os.path.exists(alignment): | |
raise FileNotFoundError("The specified alignment file does not exist.") | |
self.alignment = alignment | |
self.out_file = out_file | |
self._options = {} # will be set in subclasses | |
def write_ctl_file(self): | |
"""Write control file.""" | |
pass | |
def read_ctl_file(self): | |
"""Read control file.""" | |
pass | |
def print_options(self): | |
"""Print out all of the options and their current settings.""" | |
for option in self._options.items(): | |
print(f"{option[0]} = {option[1]}") | |
def set_options(self, **kwargs): | |
"""Set the value of an option. | |
This function abstracts the options dict to prevent the user from | |
adding options that do not exist or misspelling options. | |
""" | |
for option, value in kwargs.items(): | |
if option not in self._options: | |
raise KeyError("Invalid option: " + option) | |
else: | |
self._options[option] = value | |
def get_option(self, option): | |
"""Return the value of an option.""" | |
if option not in self._options: | |
raise KeyError("Invalid option: " + option) | |
else: | |
return self._options.get(option) | |
def get_all_options(self): | |
"""Return the values of all the options.""" | |
return list(self._options.items()) | |
def _set_rel_paths(self): | |
"""Convert all file/directory locations to paths relative to the current working directory (PRIVATE). | |
paml requires that all paths specified in the control file be | |
relative to the directory from which it is called rather than | |
absolute paths. | |
""" | |
if self.working_dir is not None: | |
self._rel_working_dir = os.path.relpath(self.working_dir) | |
if self.alignment is not None: | |
self._rel_alignment = os.path.relpath(self.alignment, self.working_dir) | |
if self.out_file is not None: | |
self._rel_out_file = os.path.relpath(self.out_file, self.working_dir) | |
def run(self, ctl_file, verbose, command): | |
"""Run a paml program using the current configuration. | |
Check that the class attributes exist and raise an error | |
if not. Then run the command and check if it succeeds with | |
a return code of 0, otherwise raise an error. | |
The arguments may be passed as either absolute or relative | |
paths, despite the fact that paml requires relative paths. | |
""" | |
if self.alignment is None: | |
raise ValueError("Alignment file not specified.") | |
if not os.path.exists(self.alignment): | |
raise FileNotFoundError("The specified alignment file does not exist.") | |
if self.out_file is None: | |
raise ValueError("Output file not specified.") | |
if self.working_dir is None: | |
raise ValueError("Working directory not specified.") | |
# Get the current working directory | |
cwd = os.getcwd() | |
# Move to the desired working directory | |
if not os.path.exists(self.working_dir): | |
os.mkdir(self.working_dir) | |
os.chdir(self.working_dir) | |
# If no external control file was specified... | |
if ctl_file is None: | |
# Dynamically build a control file | |
self.write_ctl_file() | |
ctl_file = self.ctl_file | |
else: | |
if not os.path.exists(ctl_file): | |
raise FileNotFoundError("The specified control file does not exist.") | |
if verbose: | |
result_code = subprocess.call([command, ctl_file]) | |
else: | |
with open(os.devnull) as dn: | |
result_code = subprocess.call([command, ctl_file], stdout=dn, stderr=dn) | |
os.chdir(cwd) | |
if result_code > 0: | |
# If the program fails for any reason | |
raise PamlError( | |
"%s has failed (return code %i). Run with verbose = True to view error message" | |
% (command, result_code) | |
) | |
if result_code < 0: | |
# If the paml process is killed by a signal somehow | |
raise OSError( | |
"The %s process was killed (return code %i)." % (command, result_code) | |
) | |