Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add generic aromatic bond #738

Merged
merged 1 commit into from
Jan 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading