|
1 | 1 | import os |
2 | | -from unigbsa.settings import GMXEXE |
| 2 | +import uuid |
| 3 | +import shutil |
| 4 | +from pathlib import Path |
| 5 | +from unigbsa.settings import GMXEXE, logging |
| 6 | + |
| 7 | + |
3 | 8 | def convert_format(inputfile, filetype, outfile=None, outtype='mol'): |
4 | 9 | """ |
5 | 10 | Convert a file of type filetype to mol2 format |
@@ -335,3 +340,159 @@ def obtain_net_charge(sdfile): |
335 | 340 | # Calculate the net charge of the molecule |
336 | 341 | net_charge = mol.GetTotalCharge() |
337 | 342 | return net_charge |
| 343 | + |
| 344 | + |
| 345 | +def check_forcefield(sdfile): |
| 346 | + sqm_key = "grms_tol=0.005,qm_theory='AM1',scfconv=1.d-5,ndiis_attempts=700,maxcyc=0" |
| 347 | + tmpdir = Path('/tmp') / str(uuid.uuid4()) |
| 348 | + sdfile = Path(sdfile).absolute() |
| 349 | + net_charge = obtain_net_charge(str(sdfile)) |
| 350 | + tmpdir.mkdir(exist_ok=True) |
| 351 | + cwd = os.getcwd() |
| 352 | + os.chdir(tmpdir) |
| 353 | + cmd = f'export OMP_NUM_THREADS=1;acpype -i {sdfile} -b MOL -a gaff -c bcc -n {net_charge} -k "{sqm_key}" >acpype.log 2>&1 ' |
| 354 | + rc = os.system(cmd) |
| 355 | + os.chdir(cwd) |
| 356 | + shutil.rmtree(tmpdir) |
| 357 | + if rc != 0: |
| 358 | + return False |
| 359 | + else: |
| 360 | + return True |
| 361 | + |
| 362 | + |
| 363 | +def check_element(sdfile): |
| 364 | + from openbabel import openbabel |
| 365 | + ext = Path(sdfile).suffix[1:] |
| 366 | + obConversion = openbabel.OBConversion() |
| 367 | + obConversion.SetInAndOutFormats(ext, ext) |
| 368 | + mol = openbabel.OBMol() |
| 369 | + if not obConversion.ReadFile(mol, str(sdfile)): |
| 370 | + return -1 |
| 371 | + |
| 372 | + acceptable_elements = {6, 7, 8, 16, 15, 1, 9, 17, 35, 53} |
| 373 | + for atom in openbabel.OBMolAtomIter(mol): |
| 374 | + element = atom.GetAtomicNum() |
| 375 | + if element not in acceptable_elements: |
| 376 | + return 1 |
| 377 | + return 0 |
| 378 | + |
| 379 | + |
| 380 | +def get_total_valence_electrons(sdfile): |
| 381 | + from openbabel import openbabel |
| 382 | + extin = Path(sdfile).suffix[1:] |
| 383 | + obConversion = openbabel.OBConversion() |
| 384 | + obConversion.SetInAndOutFormats(extin, extin) |
| 385 | + mol = openbabel.OBMol() |
| 386 | + obConversion.ReadFile(mol, str(sdfile)) |
| 387 | + total_valence_electrons = 0 |
| 388 | + for atom in openbabel.OBMolAtomIter(mol): |
| 389 | + total_valence_electrons += atom.GetAtomicNum() - atom.GetFormalCharge() |
| 390 | + return total_valence_electrons |
| 391 | + |
| 392 | + |
| 393 | +def get_electronegativity(atomic_number): |
| 394 | + electronegativity_table = { |
| 395 | + 1: 2.20, # Hydrogen (H) |
| 396 | + 6: 2.55, # Carbon (C) |
| 397 | + 7: 3.04, # Nitrogen (N) |
| 398 | + 8: 3.44, # Oxygen (O) |
| 399 | + 15: 2.19, # Phosphorus (P) |
| 400 | + 16: 2.58, # Sulfur (S) |
| 401 | + 9: 3.98, # Fluorine (F) |
| 402 | + 17: 3.16, # Chlorine (Cl) |
| 403 | + 35: 2.96, # Bromine (Br) |
| 404 | + 53: 2.66, # Iodine (I) |
| 405 | + } |
| 406 | + return electronegativity_table.get(atomic_number, 0) |
| 407 | + |
| 408 | + |
| 409 | +def adjust_charge_based_on_electronegativity(ob_mol): |
| 410 | + from openbabel import openbabel |
| 411 | + max_electronegativity = 0 |
| 412 | + target_atom = None |
| 413 | + |
| 414 | + for atom in openbabel.OBMolAtomIter(ob_mol): |
| 415 | + if atom.GetFormalCharge() != 0: |
| 416 | + electronegativity = get_electronegativity(atom.GetAtomicNum()) |
| 417 | + if electronegativity > max_electronegativity: |
| 418 | + max_electronegativity = electronegativity |
| 419 | + target_atom = atom |
| 420 | + |
| 421 | + if target_atom is not None: |
| 422 | + if target_atom.GetFormalCharge() > 0: |
| 423 | + target_atom.SetFormalCharge(target_atom.GetFormalCharge() - 1) |
| 424 | + elif target_atom.GetFormalCharge() < 0: |
| 425 | + target_atom.SetFormalCharge(target_atom.GetFormalCharge() + 1) |
| 426 | + |
| 427 | + |
| 428 | +def add_hydrogen(sdfile, outfile=None): |
| 429 | + from openbabel import openbabel |
| 430 | + extin = extout = Path(sdfile).suffix[1:] |
| 431 | + obConversion = openbabel.OBConversion() |
| 432 | + obConversion.SetInAndOutFormats(extin, extout) |
| 433 | + mol = openbabel.OBMol() |
| 434 | + obConversion.ReadFile(mol, str(sdfile)) |
| 435 | + mol.AddHydrogens() |
| 436 | + mol_string = obConversion.WriteString(mol) |
| 437 | + if outfile: |
| 438 | + with open(outfile, 'w') as fw: |
| 439 | + fw.write(mol_string) |
| 440 | + else: |
| 441 | + return mol |
| 442 | + |
| 443 | + |
| 444 | +def repair_ligand(sdfile, outfile=None): |
| 445 | + from openbabel import openbabel |
| 446 | + extin = Path(sdfile).suffix[1:] |
| 447 | + if outfile is None: |
| 448 | + fname = str(uuid.uuid4()) + '.' + extin |
| 449 | + outfile = Path('/tmp') / fname |
| 450 | + extout = Path(outfile).suffix[1:] |
| 451 | + obConversion = openbabel.OBConversion() |
| 452 | + obConversion.SetInAndOutFormats(extin, extout) |
| 453 | + mol = openbabel.OBMol() |
| 454 | + obConversion.ReadFile(mol, str(sdfile)) |
| 455 | + mol.AddHydrogens() |
| 456 | + mol.DeleteHydrogens() |
| 457 | + # Correct valence information |
| 458 | + pH = 7.4 |
| 459 | + charge_model = openbabel.OBChargeModel.FindType("mmff94") |
| 460 | + charge_model.ComputeCharges(mol, str(pH)) |
| 461 | + mol.CorrectForPH() |
| 462 | + mol.PerceiveBondOrders() |
| 463 | + total_valence_electrons = sum([atom.GetAtomicNum() - atom.GetFormalCharge() for atom in openbabel.OBMolAtomIter(mol)]) |
| 464 | + if total_valence_electrons % 2 == 1: |
| 465 | + adjust_charge_based_on_electronegativity(mol) |
| 466 | + mol_string = obConversion.WriteString(mol) |
| 467 | + new_string = [] |
| 468 | + for i, line in enumerate(mol_string.split('\n')): |
| 469 | + if i >= 4 and len(line) >= 30 and line.startswith(' '): |
| 470 | + line = line[:42] + ' 0 0 0 0 0 0 0 0 0' |
| 471 | + new_string.append(line) |
| 472 | + mol_string = '\n'.join(new_string) |
| 473 | + with open(outfile, 'w') as fw: |
| 474 | + fw.write(mol_string) |
| 475 | + add_hydrogen(outfile, outfile) |
| 476 | + return outfile |
| 477 | + |
| 478 | + |
| 479 | +def ligand_validate(sdfile, outfile=None): |
| 480 | + rc = check_element(sdfile) |
| 481 | + if rc == -1: |
| 482 | + logging.error(f'Failed to load {sdfile}, please check your input {sdfile}.') |
| 483 | + exit(256) |
| 484 | + elif rc == 1: |
| 485 | + logging.error(f'Ligand file only accept C N O S P H F Cl Br I, please check your input {sdfile}.') |
| 486 | + exit(257) |
| 487 | + total_valence_electrons = get_total_valence_electrons(sdfile) |
| 488 | + if total_valence_electrons % 2 == 1 or not check_forcefield(sdfile): |
| 489 | + logging.warning(f'The total valence electrons of your ligand is odd({total_valence_electrons}) or forcefield check error, try to repair input ligand.') |
| 490 | + outfile = repair_ligand(sdfile, outfile=outfile) |
| 491 | + total_valence_electrons = get_total_valence_electrons(outfile) |
| 492 | + if total_valence_electrons % 2 == 1 or not check_forcefield(outfile): |
| 493 | + logging.error(f'The total valence electrons of your ligand is stil odd({total_valence_electrons}) or forcefield check error after repair ligand, please check your input {sdfile}.') |
| 494 | + exit(257) |
| 495 | + else: |
| 496 | + return outfile |
| 497 | + else: |
| 498 | + return sdfile |
0 commit comments