File size: 4,320 Bytes
907b200
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
117
118
119
120
121
122
123
124
<?php

namespace App\Jobs;

use App\Models\DockingJob;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Process;

class RunDockingJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public $dockingJob;

    public $params;

    /**
     * The number of seconds the job can run before timing out.
     *
     * @var int
     */
    public $timeout = 1800;

    /**
     * Create a new job instance.
     */
    public function __construct(DockingJob $dockingJob, array $params)
    {
        $this->dockingJob = $dockingJob;
        $this->params = $params;
    }

    /**
     * Execute the job.
     */
    public function handle(): void
    {
        // Mark as processing
        $this->dockingJob->update(['status' => 'processing']);

        try {
            $scriptPath = base_path('scripts/vina_docking.py');
            $pythonPath = env('DOCKING_PYTHON_PATH', base_path('vina_env/bin/python'));

            // Build command
            $cmd = sprintf(
                '%s %s %s %s %f %f %f %f %f %f %d %d',
                escapeshellarg($pythonPath),
                escapeshellarg($scriptPath),
                escapeshellarg($this->dockingJob->protein_path),
                escapeshellarg($this->dockingJob->ligand_path),
                $this->params['center_x'],
                $this->params['center_y'],
                $this->params['center_z'],
                $this->params['box_size_x'],
                $this->params['box_size_y'],
                $this->params['box_size_z'],
                $this->params['exhaustiveness'] ?? 8,
                $this->params['n_poses'] ?? 5
            );

            // Run process
            $result = Process::timeout($this->timeout)->run($cmd);

            // Using regex to reliably find our JSON object in the mixed python Vina logs output
            $outputData = null;
            $fullOutput = $result->output();

            if (preg_match('/\{"status":\s*"(success|error)".*\}/s', $fullOutput, $matches)) {
                $outputData = json_decode($matches[0], true);
            }

            // Also check standard error in case Python printed an error payload there
            if (! $outputData && preg_match('/\{"status":\s*"(success|error)".*\}/s', $result->errorOutput(), $matches)) {
                $outputData = json_decode($matches[0], true);
            }

            if (! $result->successful()) {
                $errorMessage = $outputData['message'] ?? $result->errorOutput();
                if (empty(trim($errorMessage))) {
                    $errorMessage = trim($fullOutput) ?: 'Unknown Error / Empty Output';
                }
                throw new \Exception(trim($errorMessage));
            }

            if (isset($outputData['status']) && $outputData['status'] === 'error') {
                throw new \Exception($outputData['message'] ?? 'Unknown script error');
            }

            $vinaScores = [];
            if ($outputData && isset($outputData['energies'])) {
                foreach ($outputData['energies'] as $pose) {
                    $vinaScores[] = [
                        'affinity' => (float) ($pose[0] ?? 0.0),
                        'inter' => (float) ($pose[1] ?? 0.0),
                        'intra' => (float) ($pose[2] ?? 0.0),
                        'torsions' => (float) ($pose[3] ?? 0.0),
                        'unbound' => (float) ($pose[4] ?? 0.0),
                    ];
                }
                unset($outputData['energies']);
            }

            $this->dockingJob->update([
                'status' => 'completed',
                'result_data' => $outputData ?: ['raw_output' => $fullOutput],
                'vina_scores' => $vinaScores,
            ]);

        } catch (\Exception $e) {
            Log::error('Docking Job Failed', ['error' => $e->getMessage(), 'job_id' => $this->dockingJob->id]);
            $this->dockingJob->update([
                'status' => 'failed',
                'result_data' => ['error' => $e->getMessage()],
            ]);
        }
    }
}