Skip to content

Commit

Permalink
Add generic aromatic bond
Browse files Browse the repository at this point in the history
  • Loading branch information
padix-key committed Jan 17, 2025
1 parent 8138e0e commit 2b0f6d9
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 13 deletions.
7 changes: 6 additions & 1 deletion src/biotite/structure/bonds.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ class BondType(IntEnum):
- `AROMATIC_SINGLE` - Aromatic bond with a single formal bond
- `AROMATIC_DOUBLE` - Aromatic bond with a double formal bond
- `AROMATIC_TRIPLE` - Aromatic bond with a triple formal bond
- `AROMATIC` - Aromatic bond without specification of the formal bond
- `COORDINATION` - Coordination complex involving a metal atom
"""
ANY = 0
Expand All @@ -71,6 +72,7 @@ class BondType(IntEnum):
AROMATIC_DOUBLE = 6
AROMATIC_TRIPLE = 7
COORDINATION = 8
AROMATIC = 9


def without_aromaticity(self):
Expand All @@ -97,6 +99,8 @@ class BondType(IntEnum):
return BondType.DOUBLE
elif self == BondType.AROMATIC_TRIPLE:
return BondType.TRIPLE
elif self == BondType.AROMATIC:
return BondType.ANY
else:
return self

Expand Down Expand Up @@ -517,7 +521,8 @@ class BondList(Copyable):
for aromatic_type, non_aromatic_type in [
(BondType.AROMATIC_SINGLE, BondType.SINGLE),
(BondType.AROMATIC_DOUBLE, BondType.DOUBLE),
(BondType.AROMATIC_TRIPLE, BondType.TRIPLE)
(BondType.AROMATIC_TRIPLE, BondType.TRIPLE),
(BondType.AROMATIC, BondType.ANY),
]:
bond_types[bond_types == aromatic_type] = non_aromatic_type

Expand Down
14 changes: 4 additions & 10 deletions src/biotite/structure/io/mol/ctab.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,13 @@
1: BondType.SINGLE,
2: BondType.DOUBLE,
3: BondType.TRIPLE,
4: BondType.AROMATIC,
5: BondType.ANY,
6: BondType.SINGLE,
7: BondType.DOUBLE,
6: BondType.AROMATIC_SINGLE,
7: BondType.AROMATIC_DOUBLE,
8: BondType.ANY,
}
BOND_TYPE_MAPPING_REV = {
BondType.SINGLE: 1,
BondType.DOUBLE: 2,
BondType.TRIPLE: 3,
BondType.AROMATIC_SINGLE: 1,
BondType.AROMATIC_DOUBLE: 2,
BondType.ANY: 8,
}
BOND_TYPE_MAPPING_REV = {v: k for k, v in BOND_TYPE_MAPPING.items()}

CHARGE_MAPPING = {0: 0, 1: 3, 2: 2, 3: 1, 5: -1, 6: -2, 7: -3}
CHARGE_MAPPING_REV = {val: key for key, val in CHARGE_MAPPING.items()}
Expand Down
2 changes: 2 additions & 0 deletions src/biotite/structure/io/pdbx/convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
BondType.AROMATIC_TRIPLE: "trip",
# These are masked later, it is merely added here to avoid a KeyError
BondType.ANY: "",
BondType.AROMATIC: "",
BondType.COORDINATION: "",
}
# Map 'chem_comp_bond' bond orders and aromaticity to 'BondType'...
Expand All @@ -92,6 +93,7 @@
("SING", "Y"): BondType.AROMATIC_SINGLE,
("DOUB", "Y"): BondType.AROMATIC_DOUBLE,
("TRIP", "Y"): BondType.AROMATIC_TRIPLE,
("AROM", "Y"): BondType.AROMATIC,
}
# ...and vice versa
COMP_BOND_TYPE_TO_ORDER = {
Expand Down
56 changes: 54 additions & 2 deletions tests/structure/io/test_mol.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import numpy as np
import pytest
import biotite.structure as struc
import biotite.structure.info as info
import biotite.structure.io.mol as mol
import biotite.structure.io.pdbx as pdbx
from biotite.structure.bonds import BondType
Expand Down Expand Up @@ -83,7 +84,7 @@ def test_header_conversion():
[False, True],
),
)
def test_structure_conversion(
def test_structure_conversion_from_file(
FileClass, # noqa: N803
path,
version,
Expand Down Expand Up @@ -116,7 +117,6 @@ def test_structure_conversion(

temp.seek(0)
mol_file = FileClass.read(temp)
print(mol_file)
test_atoms = mol.get_structure(mol_file)
if omit_charge:
assert np.all(test_atoms.charge == 0)
Expand All @@ -126,6 +126,58 @@ def test_structure_conversion(
assert test_atoms == ref_atoms


@pytest.mark.parametrize(
"FileClass, component_name, version, omit_charge, use_charge_property",
itertools.product(
[mol.MOLFile, mol.SDFile],
[
"ALA", # Alanine
"BNZ", # Benzene (has aromatic bonds)
"3P8", # Methylammonium ion (has charge)
"MCH", # Trichloromethane (has element with multiple letters)
],
["V2000", "V3000"],
[False, True],
[False, True],
),
)
def test_structure_conversion_to_file(
FileClass, # noqa: N803
component_name,
version,
omit_charge,
use_charge_property,
):
"""
Writing a component to a file and reading it again should give the same
structure.
"""
ref_atoms = info.residue(component_name)

mol_file = FileClass()
mol.set_structure(mol_file, ref_atoms, version=version)
temp = TemporaryFile("w+")
mol_file.write(temp)

if version == "V2000":
if use_charge_property:
# Enforce usage of 'M CHG' entries
_delete_charge_columns(temp)
else:
# Enforce usage of charge column in atom block
_delete_charge_property(temp)

temp.seek(0)
mol_file = FileClass.read(temp)
test_atoms = mol.get_structure(mol_file)
temp.close()

assert np.all(test_atoms.element == ref_atoms.element)
assert np.all(test_atoms.charge == ref_atoms.charge)
assert np.allclose(test_atoms.coord, ref_atoms.coord)
assert test_atoms.bonds == ref_atoms.bonds


@pytest.mark.parametrize(
"path",
[
Expand Down

0 comments on commit 2b0f6d9

Please sign in to comment.