jfaustin's picture
secretion-scores (#4)
a3f3d91 verified
# -*- coding: utf-8 -*-
import os
import shutil
from . import logger
from subprocess import PIPE, Popen
from os.path import join, isfile
import platform
_name = "FoldX"
if platform.system() == "Windows":
_bin_name = "foldx.exe"
else:
_bin_name = "foldx"
# TODO maybe exploit the new foldx options for a cleaner pipeline of the pdb files etc
class FoldxWrap:
"""
Expects a path to folder with FoldX binary and rotabase.txt files and a script dir with repairPDB.txt file
If the file is not present in scripts dir, the class will attempt to create it
This class can be used to run energy minimization and mutant creation with foldx
"""
def __init__(self, foldx_dir="", work_dir="", skip_minimization=False, ph=None):
self.bin_dir = foldx_dir
self.agg_work_dir = work_dir
self.skip = skip_minimization
self.ph = ph if ph else 7.0
self.repair_cmd = [
"--pdb", "input.pdb",
"--command", "RepairPDB",
"--water", "-CRYSTAL",
"--pH", "%.2f" % self.ph]
self.build_mutant_cmd = [
"--pdb", "input_Repair.pdb",
"--command", "BuildModel",
"--mutant-file", "individual_list.txt",
"--pH", "%.2f" % self.ph]
try:
self._check_files()
except logger.FoldXError:
raise
def minimize_energy(self, working_dir="", cleanup=True, skip_min=False):
try:
os.chdir(working_dir)
except OSError:
raise logger.FoldXError("FoldX run in a non-existing directory %s" % working_dir)
if not isfile(join(working_dir, "input.pdb")):
raise logger.FoldXError("input.pdb file not present if FoldX working directory %s" % working_dir)
if not isfile(join(working_dir, "rotabase.txt")):
# In case of multiple minimizations te code below could raise a same file error
try:
os.symlink(join(self.bin_dir, "rotabase.txt"), join(working_dir, "rotabase.txt"))
except (OSError, AttributeError): # Windows will not necessarily allow symlink creation
shutil.copyfile(join(self.bin_dir, "rotabase.txt"), join(working_dir, "rotabase.txt"))
fold_cmd = [join(self.bin_dir, _bin_name)] + self.repair_cmd
if not skip_min:
self._run_foldx(cmd=fold_cmd, msg="Starting FoldX energy minimalization", outfile="finalRepair.out")
else:
# since the job is ran on output from parent this "input" should be the output of the parent process
shutil.move("input.pdb", "input_Repair.pdb") # Pretend it was ran
if cleanup:
try:
shutil.move("input_Repair.fxout", "RepairPDB.fxout")
shutil.move("input_Repair.pdb", "folded.pdb")
except IOError:
raise logger.FoldXError("FoldX didn't produce expected repair files. "
"Can't continue without it. This is unexpected and could indicate FoldX issues.")
def build_mutant(self, working_dir="", mutation_list=""):
os.chdir(working_dir)
logger.to_file(filename="individual_list.txt", content=mutation_list)
self.minimize_energy(working_dir=working_dir, cleanup=False, skip_min=self.skip)
_cmd = [join(self.bin_dir, _bin_name)] + self.build_mutant_cmd
self._run_foldx(cmd=_cmd, msg="Building mutant model", outfile="buildMutant.out")
try:
shutil.move("input_Repair_1.pdb", "input.pdb")
except IOError:
raise logger.FoldXError("FoldX didn't produce expected mutant file. "
"Can't continue without it. This is unexpected and could indicate FoldX issues.")
self._cleanup()
@staticmethod
def _run_foldx(cmd, msg='', outfile=''):
"""Assumes the directory it's run from contains """
logger.info(module_name="FoldX", msg=msg)
logger.debug(module_name="FoldX", msg="Starting FoldX with %s" % " ".join(cmd))
out, err = Popen(cmd, stdout=PIPE, stderr=PIPE).communicate()
if err:
logger.warning(module_name="FoldX", msg=err)
logger.to_file(filename=outfile, content=out, allow_err=False)
# TODO proper cleanup handling this is a mess + is this even necessary?
def _cleanup(self):
with open("Dif_input_Repair.fxout", 'r') as enelog:
d = enelog.readlines()[-1]
d = d.split()[1] + " kcal/mol"
logger.to_file(filename=join(self.agg_work_dir, "MutantEnergyDiff"), content=d)
try:
os.mkdir(join(self.agg_work_dir, "foldxOutput"))
except OSError:
logger.log_file(module_name=_name, msg="Couldn't create foldxOutput folder (probably already exists)")
shutil.move("Raw_input_Repair.fxout",
join(self.agg_work_dir, "foldxOutput/Stability_RepairPDB_input.fxout"))
shutil.move("Dif_input_Repair.fxout",
join(self.agg_work_dir, "foldxOutput/Dif_BuildModel_RepairPDB_input.fxout"))
def _check_files(self):
if not isfile(join(self.bin_dir,"rotabase.txt")) or not isfile(join(self.bin_dir,_bin_name)):
raise logger.FoldXError("Provided FoldX directory (%s) misses either %s or %s " %
(self.bin_dir, "rotabase.txt", _bin_name) +
"files which are required for the program to run.")