Skip to content

Commit

Permalink
supporting xlink order, stereochemistry
Browse files Browse the repository at this point in the history
  • Loading branch information
jonrkarr committed Aug 29, 2019
1 parent 5b6b4e8 commit e9f7c24
Show file tree
Hide file tree
Showing 4 changed files with 184 additions and 6 deletions.
144 changes: 141 additions & 3 deletions bcforms/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
:License: MIT
"""

from bpforms import BondOrder, BondStereo
from bpforms.util import gen_genomic_viz
from ruamel import yaml
from wc_utils.util.chem import EmpiricalFormula, OpenBabelUtils, draw_molecule
Expand Down Expand Up @@ -706,6 +707,26 @@ def get_r_displaced_atoms(self):
"""
pass

@abc.abstractmethod
def get_order(self):
""" Get the order
Returns:
:obj:`BondOrder`: order
"""
pass

@abc.abstractmethod
def get_stereo(self):
""" Get the stereochemistry
Returns:
:obj:`BondStereo`: stereochemistry
"""
pass

@abc.abstractmethod
def __str__(self):
"""Generate a string representation
Expand Down Expand Up @@ -742,6 +763,9 @@ def is_equal(self, other):
if not self_atom.is_equal(other_atom):
return False

if self.get_order() != other.get_order() or self.get_stereo() != other.get_stereo():
return False

return True


Expand All @@ -753,10 +777,13 @@ class InlineCrosslink(Crosslink):
r_bond_atoms (:obj:`list` of :obj:`Atom`): atoms from the right subunit that bond with the left subunit
l_displaced_atoms (:obj:`list` of :obj:`Atom`): atoms from the left subunit displaced by the crosslink
r_displaced_atoms (:obj:`list` of :obj:`Atom`): atoms from the right subunit displaced by the crosslink
order (:obj:`BondOrder`): order
stereo (:obj:`BondStereo`): stereochemistry
comments (:obj:`str`): comments
"""

def __init__(self, l_bond_atoms=None, r_bond_atoms=None, l_displaced_atoms=None, r_displaced_atoms=None,
order=BondOrder.single, stereo=None,
comments=None):
"""
Expand All @@ -765,6 +792,8 @@ def __init__(self, l_bond_atoms=None, r_bond_atoms=None, l_displaced_atoms=None,
r_bond_atoms (:obj:`list`): atoms from the right subunit that bond with the left subunit
l_displaced_atoms (:obj:`list`): atoms from the left subunit displaced by the crosslink
r_displaced_atoms (:obj:`list`): atoms from the right subunit displaced by the crosslink
order (:obj:`BondOrder`, optional): order
stereo (:obj:`BondStereo`, optional): stereochemistry
comments (:obj:`str`): comments
"""
if l_bond_atoms is None:
Expand All @@ -787,6 +816,9 @@ def __init__(self, l_bond_atoms=None, r_bond_atoms=None, l_displaced_atoms=None,
else:
self.r_displaced_atoms = r_bond_atoms

self.order = order
self.stereo = stereo

self.comments = comments

@property
Expand Down Expand Up @@ -889,6 +921,52 @@ def r_displaced_atoms(self, value):
raise ValueError('`value` must be an instance of `list`')
self._r_displaced_atoms = value

@property
def order(self):
""" Get the order
Returns:
:obj:`BondOrder`: order
"""
return self._order

@order.setter
def order(self, value):
""" Set the order
Args:
value (:obj:`BondOrder`): order
Raises:
:obj:`ValueError`: if `order` is not an instance of `BondOrder`
"""
if not isinstance(value, BondOrder):
raise ValueError('`order` must be an instance of `BondOrder`')
self._order = value

@property
def stereo(self):
""" Get the stereochemistry
Returns:
:obj:`BondStereo`: stereochemistry
"""
return self._stereo

@stereo.setter
def stereo(self, value):
""" Set the stereo
Args:
value (:obj:`BondStereo`): stereochemistry
Raises:
:obj:`ValueError`: if `stereo` is not an instance of `BondStereo`
"""
if value is not None and not isinstance(value, BondStereo):
raise ValueError('`stereo` must be an instance of `BondStereo` or `None`')
self._stereo = value

@property
def comments(self):
""" Get comments
Expand Down Expand Up @@ -925,6 +1003,11 @@ def __str__(self):
for atom in getattr(self, atom_type):
s += ' {}: {} |'.format(atom_type[:-1].replace('_', '-'), str(atom))

if self.order != BondOrder.single:
s += ' order: "{}" |'.format(self.order.name)
if self.stereo is not None:
s += ' stereo: "{}" |'.format(self.stereo.name)

if self.comments:
s += ' comments: "{}" |'.format(self.comments.replace('"', '\\"'))

Expand Down Expand Up @@ -967,6 +1050,22 @@ def get_r_displaced_atoms(self):
"""
return self.r_displaced_atoms

def get_order(self):
""" Get the order
Returns:
:obj:`BondOrder`: order
"""
return self.order

def get_stereo(self):
""" Get the stereochemistry
Returns:
:obj:`BondStereo`: stereochemistry
"""
return self.stereo


_xlink_filename = pkg_resources.resource_filename('bpforms', 'xlink/xlink.yml')

Expand Down Expand Up @@ -1301,6 +1400,26 @@ def get_r_displaced_atoms(self):
atoms.append(atom)
return atoms

def get_order(self):
""" Get the order
Returns:
:obj:`BondOrder`: order
"""
return BondOrder[self.xlink_details[1].get('order' , 'single')]

def get_stereo(self):
""" Get the stereochemistry
Returns:
:obj:`BondStereo`: stereochemistry
"""
val = self.xlink_details[1].get('stereo', None)
if val is None:
return None
else:
return BondStereo[val]

def __str__(self):
"""Generate a string representation
Expand Down Expand Up @@ -1564,7 +1683,7 @@ def inline_crosslink(self, *args):
for arg in args:
if isinstance(arg, lark.tree.Tree):
attr, val = arg.children[0]
if attr == 'comments':
if attr in ['order', 'stereo', 'comments']:
setattr(bond, attr, val)
else:
attr_val_list = getattr(bond, attr + "s")
Expand Down Expand Up @@ -1607,6 +1726,14 @@ def inline_crosslink_atom(self, *args):
def inline_crosslink_atom_type(self, *args):
return ('inline_crosslink_atom_type', args[0].value + '_' + args[1].value + '_atom')

@lark.v_args(inline=True)
def inline_crosslink_order(self, *args):
return ('order', BondOrder[args[-2].value])

@lark.v_args(inline=True)
def inline_crosslink_stereo(self, *args):
return ('stereo', BondStereo[args[-2].value])

@lark.v_args(inline=True)
def inline_crosslink_comments(self, *args):
return ('comments', args[1].value[1:-1])
Expand Down Expand Up @@ -2022,7 +2149,7 @@ def get_structure(self):
# print(OpenBabelUtils.export(mol, format='smiles', options=[]))

# make the crosslink bonds
for atoms in crosslinks_atoms:
for crosslink, atoms in zip(self.crosslinks, crosslinks_atoms):

for atom, i_subunit, subunit_idx, i_monomer, i_position, atom_charge in itertools.chain(atoms['l_displaced_atoms'], atoms['r_displaced_atoms']):
if atom:
Expand All @@ -2035,7 +2162,18 @@ def get_structure(self):
bond = openbabel.OBBond()
bond.SetBegin(l_atom)
bond.SetEnd(r_atom)
bond.SetBondOrder(1)
bond.SetBondOrder(crosslink.get_order().value)
stereo = crosslink.get_stereo()
if stereo is None:
pass
elif stereo == BondStereo.wedge:
bond.SetWedge()
elif stereo == BondStereo.hash:
bond.SetHash()
elif stereo == BondStereo.up:
bond.SetUp()
elif stereo == BondStereo.down:
bond.SetDown()
assert mol.AddBond(bond)

if l_atom_charge:
Expand Down
7 changes: 6 additions & 1 deletion bcforms/grammar.lark
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ onto_crosslink_monomer: onto_crosslink_monomer_type FIELD_SEP subunit subunit_id
onto_crosslink_monomer_type: /(l|r)/

inline_crosslink: inline_crosslink_attr (ATTR_SEP inline_crosslink_attr)*
inline_crosslink_attr: inline_crosslink_atom | inline_crosslink_comments
inline_crosslink_attr: inline_crosslink_atom | inline_crosslink_order | inline_crosslink_stereo | inline_crosslink_comments
inline_crosslink_atom: inline_crosslink_atom_type FIELD_SEP subunit subunit_idx? "-" monomer_position atom_element atom_position atom_component_type? atom_charge?
inline_crosslink_atom_type: /(l|r)/ "-" /(bond|displaced)/ "-atom"
inline_crosslink_order: "order" field_sep QUOTE_DELIMITER /(single|double|triple|aromatic)/ QUOTE_DELIMITER
inline_crosslink_stereo: "stereo" field_sep QUOTE_DELIMITER /(wedge|hash|up|down)/ QUOTE_DELIMITER
inline_crosslink_comments: "comments" FIELD_SEP ESCAPED_STRING

monomer_position: /[0-9]+/
Expand All @@ -25,11 +27,14 @@ atom_position: /[0-9]+/
atom_charge: /[\+\-][0-9]+/
atom_component_type: /[mb]/

?field_sep: WS? ":" WS?

NAME: /(?!(^|\b)(\d+(\.\d*)?(\b|$))|(\.\d+$)|(0[x][0-9a-f]+(\b|$))|([0-9]+e[0-9]+(\b|$)))[a-z0-9_]+/
PLUS_SEP: WS* "+" WS*
STAR_SEP: WS* "*" WS*
ATTR_SEP: WS* "|" WS*
FIELD_SEP: WS* ":" WS*
QUOTE_DELIMITER: "\""

WS: /[ \t\f\r\n]+/
INT: /[0-9]+/
Expand Down
7 changes: 5 additions & 2 deletions bcforms/web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -195,11 +195,14 @@ <h3>Examples</h3>

<div class="large-5 cell grammar">
<a name="inline_xlink"></a>
<h4>Inline definition of crosslinks</h4>
<p class="before_list">Each crosslink can be described using four types of attributes:</p>
<h4>User-defined crosslinks</h4>
<p class="before_list">Each crosslink can be described using the following attributes:</p>
<ul>
<li><tt>l-bond-atom</tt> and <tt>r-bond-atom</tt>: These attributes indicate the atoms involved in the bond. The values of these attributes are the position of the monomeric form within the sequence of the subunit, the element of the atom, the position of the atom within the monomeric form, and the charge of the atom (e.g., <tt>sub_a(1)-8N3+1</tt>). Open Babel can be used to display the numbers of the atoms within monomeric forms.</li>
<li><tt>l-displaced-atom</tt> and <tt>r-displaced-atom</tt>: These attributes indicate the atoms displaced by the formation of the bond. The values of these attributes are also the position of the monomeric form within the sequence of the subunit, the element of the atom, the position of the atom within the monomeric form, and the charge of the atom.</li>
<li><tt>order</tt>: This attribute can indicate the order (single, double, triple, aromatic) of the bond.</li>
<li><tt>stereo</tt>: This attribute can indicate the stereochemistry of the bond (wedge, hash, up, down).</li>
<li><tt>comments</tt>: This attribute can indicate comments about the crosslink, such as uncertainty about its location or structure.</li>
</ul>

<p>Each crosslink can have one or more left and right bond atoms, and zero or more left and right displaced atoms. Each crosslink must have the same number of left and right bond atoms.</p>
Expand Down
32 changes: 32 additions & 0 deletions tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,19 @@ def test_set_r_displaced_atoms(self):
with self.assertRaises(ValueError):
crosslink.r_displaced_atoms = None

def test_set_order(self):
crosslink = core.InlineCrosslink()
crosslink.order = bpforms.BondOrder.double
with self.assertRaises(ValueError):
crosslink.order = None

def test_set_stereo(self):
crosslink = core.InlineCrosslink()
crosslink.stereo = None
crosslink.stereo = bpforms.BondStereo.wedge
with self.assertRaises(ValueError):
crosslink.stereo = 1

def test_str(self):
crosslink = core.InlineCrosslink()
atom_1 = core.Atom(subunit='abc', subunit_idx=1, element='H', position=1, monomer=10, charge=0)
Expand All @@ -320,6 +333,12 @@ def test_str(self):

self.assertEqual(str(crosslink), 'x-link: [ l-bond-atom: abc(1)-10H1 | r-bond-atom: def(1)-10H1 ]')

crosslink = core.InlineCrosslink(order=bpforms.BondOrder.double)
self.assertEqual(str(crosslink), 'x-link: [ order: "double" ]')

crosslink = core.InlineCrosslink(stereo=bpforms.BondStereo.wedge)
self.assertEqual(str(crosslink), 'x-link: [ stereo: "wedge" ]')

def test_is_equal(self):
atom_1 = core.Atom(subunit='abc', subunit_idx=1, element='H', position=1, monomer=10, charge=0)
atom_2 = core.Atom(subunit='abc', subunit_idx=1, element='H', position=1, monomer=10, charge=0)
Expand Down Expand Up @@ -644,6 +663,19 @@ def test_from_str(self):
' comments: "a comment"]')
self.assertEqual(list(bc_form_9.crosslinks)[0].comments, 'a comment')

bc_form_10 = core.BcForm().from_str('unit_1 + unit_2'
'| x-link: [ l-bond-atom: unit_1-1C2 |'
' r-bond-atom: unit_2-2N1-1 |'
' l-displaced-atom: unit_1-1O1 |'
' l-displaced-atom: unit_1-1H1 |'
' r-displaced-atom: unit_2-2H1+1 |'
' r-displaced-atom: unit_2-2H1 | '
' order: "triple" |'
' stereo: "wedge"'
']')
self.assertEqual(list(bc_form_10.crosslinks)[0].order, bpforms.BondOrder.triple)
self.assertEqual(list(bc_form_10.crosslinks)[0].stereo, bpforms.BondStereo.wedge)


def test_from_set(self):

Expand Down

0 comments on commit e9f7c24

Please sign in to comment.