File size: 5,505 Bytes
a3f3d91
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# -*- 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.")