Skip to content

Contacts

Selection and Filtering API

As mentioned, contacts are simply neighbor atoms that satisfy some additional criteria. In this section, we will discuss how to use Lahuta to extract contacts from neighbor atoms. We will also discuss how to customize the criteria that are used to extract contacts. It's best to start with an example:

Example - Applying Filters to Neihboring Atoms

Applying filters to neighboring atoms.

from lahuta import Luni

# Load the structure and compute neighbors
luni = Luni("path/to/file.pdb")
ns = luni.compute_neighbors()
print (ns.pairs.shape) # (1)!
#> (3024, 2)

# Apply a simple type filter
aromatic1_ns = ns.type_filter("aromatic", 1) # (2)!
print (aromatic1_ns.pairs.shape) # (3)!
#> (287, 2)

  1. After computing neighbors, we have 3024 pairs of neighboring atoms.
  2. We apply a type filter to the neighbors. This filter will only keep neighbors where the first atom is aromatic.
  3. After applying the filter, we are left with 287 pairs of aromatic neighbors.

Keep in mind

pairs and distances are uniquely sorted such that the first atom in a pair is the one with a lower index. This means that if we have a pair of atoms with indices (i, j), then i < j. This way, we can uniquely specify the first and second atom in a pair. 1 is used to specify the first atom and 2 is used to specify the second atom. This is true for all methods that apply filters to NeighborPairs objects.

Also note

The type_filter method returns a new NeighborPairs object. The original NeighborPairs object is not modified. This Fluent API design pattern is used throughout Lahuta. It allows you to chain multiple filters together and apply them in a single step. For example, we can apply two filters in a single step as follows:

aromatic_neighbors = ns.type_filter("aromatic", 1).type_filter("aromatic", 2)

This will only keep neighbors where both atoms are aromatic.

This selection syntax is quite powerful and allows you to capture complicated relationships between atoms in an intuitive way. It is also very flexible and extensible. To further demonstrate this, we provide the full code for how aromatic contacts are computed in Lahuta:

Example - Aromatic Contacts

Computing aromatic contacts in Lahuta.

aromatic_ns = ns.type_filter("aromatic", 1).type_filter("aromatic", 2).distance_filter(4.0) # (1)!
print (aromatic_ns.pairs.shape)

  1. This is all that is required to compute aromatic contacts!

Here is another example that demonstrates how to compute ionic contacts:

Example - Ionic Contacts

Computing ionic contacts in Lahuta.

contacts_atom12 = ns.type_filter("pos_ionisable", 1).type_filter("neg_ionisable", 2).distance_filter(4.0) # (1)!
contacts_atom21 = ns.type_filter("neg_ionisable", 1).type_filter("pos_ionisable", 2).distance_filter(4.0) # (2)!

contacts = contacts_atom12 + contacts_atom21 # (3)!

  1. This will only keep neighbors where the first atom is positively charged and the second atom is negatively charged.
  2. This will only keep neighbors where the first atom is negatively charged and the second atom is positively charged.
  3. This will combine the two NeighborPairs objects into a single NeighborPairs object.

Learn more

See the API documentation on contacts for more information.

I hope that these examples show how easy and intuitive it is to use Lahuta to extract contacts from neighboring atoms. I hope you also see how NeighborPairs objects can be combined to create more complex NeighborPairs objects. This is the core idea behind Lahuta's selection and filtering API. It is designed to be intuitive, flexible, and extensible.

Computed Atom Types

Below are the atom types that are computed by Lahuta:

  • aromatic: Aromatic atoms
  • pos_ionisable: Positively charged atoms
  • neg_ionisable: Negatively charged atoms
  • hbond_acceptor: Hydrogen bond acceptor atoms
  • hbond_donor: Hydrogen bond donor atoms
  • weak_hbond_acceptor: Weak hydrogen bond acceptor atoms
  • weak_hbond_donor: Weak hydrogen bond donor atoms
  • xbond_acceptor: Halogen bond acceptor atoms
  • xbond_donor: Halogen bond donor atoms
  • hydrophobic: Hydrophobic atoms
  • carbonyl_oxygen: Carbonyl oxygen atoms
  • carbonyl_carbon: Carbonyl carbon atoms

Types of Filters

NeighborPairs objects can be filtered using the following filters (methods):

Type Filter: NeighborPairs.type_filter(...)

Filter pairs based on atom types.

The method selects pairs from the NeighborPairs object where the atoms have the specified type. The partner parameter specifies the column (1 or 2) from which the atom types are selected.

Parameters:

Name Type Description Default
atom_type str

Specifies the atom type. Must be one of:

  • 'carbonyl_oxygen'
  • 'weak_hbond_donor'
  • 'pos_ionisable'
  • 'carbonyl_carbon'
  • 'hbond_acceptor'
  • 'hbond_donor'
  • 'neg_ionisable'
  • 'weak_hbond_acceptor'
  • 'xbond_acceptor'
  • 'aromatic'
  • 'hydrophobe'
required
partner int

The column for atom type selection. Can be either 1 or 2.

required

Returns:

Type Description
NeighborPairs

A NeighborPairs object containing the pairs that meet the atom type filter.

Source code in lahuta/core/neighbors.py
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
def type_filter(self, atom_type: str, partner: int) -> "NeighborPairs":
    """Filter pairs based on atom types.

    The method selects pairs from the NeighborPairs object where the atoms have the specified type.
    The `partner` parameter specifies the column (1 or 2) from which the atom types are selected.

    Args:
        atom_type (str): Specifies the atom type. Must be one of:

            - 'carbonyl_oxygen'
            - 'weak_hbond_donor'
            - 'pos_ionisable'
            - 'carbonyl_carbon'
            - 'hbond_acceptor'
            - 'hbond_donor'
            - 'neg_ionisable'
            - 'weak_hbond_acceptor'
            - 'xbond_acceptor'
            - 'aromatic'
            - 'hydrophobe'

        partner (int): The column for atom type selection. Can be either 1 or 2.

    Returns:
        A NeighborPairs object containing the pairs that meet the atom type filter.
    """
    atom_type_col_num = AVAILABLE_ATOM_TYPES[atom_type.upper()]
    nonzeros: NDArray[np.int32] = self.atom_types.getcol(atom_type_col_num).nonzero()[0]
    mask = np.in1d(self.pairs[:, partner - 1], nonzeros)

    return self.clone(self.pairs[mask], self.distances[mask])

Distance Filter: NeighborPairs.distance_filter(...)

Select pairs based on the distance.

The method selects pairs from the NeighborPairs object where the distances between the atoms are less than or equal to the specified distance.

Parameters:

Name Type Description Default
distance float

The distance to select.

required

Returns:

Type Description
NeighborPairs

A NeighborPairs object containing the pairs that meet the distance filter.

Source code in lahuta/core/neighbors.py
205
206
207
208
209
210
211
212
213
214
215
216
217
218
def distance_filter(self, distance: float) -> "NeighborPairs":
    """Select pairs based on the distance.

    The method selects pairs from the NeighborPairs object where the distances between the atoms are
    less than or equal to the specified distance.

    Args:
        distance (float): The distance to select.

    Returns:
        A NeighborPairs object containing the pairs that meet the distance filter.
    """
    mask = self.distances <= distance
    return self.clone(self.pairs[mask], self.distances[mask])

Index Filter: NeighborPairs.index_filter(...)

Select pairs based on the atom indices.

The method selects pairs from the NeighborPairs object where the atoms have the specified indices. The partner parameter specifies the column (1 or 2) from which the atom indices are selected.

Parameters:

Name Type Description Default
indices NDArray[int32]

The atom indices to select.

required
partner int

The column to select the atom indices from. It can be either 1 or 2.

required

Returns:

Type Description
NeighborPairs

A NeighborPairs object containing the pairs that meet the index filter.

Source code in lahuta/core/neighbors.py
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
def index_filter(
    self,
    indices: NDArray[np.int32],
    partner: int,
) -> "NeighborPairs":
    """Select pairs based on the atom indices.

    The method selects pairs from the NeighborPairs object where the atoms have the specified indices.
    The `partner` parameter specifies the column (1 or 2) from which the atom indices are selected.

    Args:
        indices (NDArray[np.int32]): The atom indices to select.
        partner (int): The column to select the atom indices from. It can be either 1 or 2.

    Returns:
        A NeighborPairs object containing the pairs that meet the index filter.
    """
    mask = np.in1d(self.pairs[:, partner - 1], indices)
    return self.clone(self.pairs[mask], self.distances[mask])

Numeric Filter NeighborPairs.numeric_filter(...)

Select pairs based on a numeric cutoff.

The method selects pairs from the NeighborPairs object where the values in the specified array are less than or equal to the cutoff (if lte is True) or greater than the cutoff (if lte is False).

Parameters:

Name Type Description Default
array NDArray[float32]

The array containing the values to compare with the cutoff.

required
cutoff float

The cutoff value for the filter.

required

Returns:

Type Description
NeighborPairs

A NeighborPairs object containing the pairs that meet the numeric filter.

Source code in lahuta/core/neighbors.py
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
def numeric_filter(self, array: NDArray[np.float32], cutoff: float) -> "NeighborPairs":
    """Select pairs based on a numeric cutoff.

    The method selects pairs from the NeighborPairs object where the values in the specified array are less than or
    equal to the cutoff (if `lte` is True) or greater than the cutoff (if `lte` is False).

    Args:
        array (NDArray[np.float32]): The array containing the values to compare with the cutoff.
        cutoff (float): The cutoff value for the filter.

    Returns:
        A NeighborPairs object containing the pairs that meet the numeric filter.
    """
    mask = array <= cutoff
    return self.clone(self.pairs[mask], self.distances[mask])

Radius Filter: NeighborPairs.radius_filter(...)

Select pairs based on the radius.

The method selects pairs from the NeighborPairs object where the van der Waals radii of the atoms are less than or equal to the specified radius. The partner parameter specifies the column (1 or 2) from which the radii are selected.

Parameters:

Name Type Description Default
radius float

The radius to select.

required
partner int

The column to select the radii from. It can be either 1 or 2.

required

Returns:

Type Description
NeighborPairs

A NeighborPairs object containing the pairs that meet the radius filter.

Source code in lahuta/core/neighbors.py
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
def radius_filter(self, radius: float, partner: int) -> "NeighborPairs":
    """Select pairs based on the radius.

    The method selects pairs from the NeighborPairs object where the van der Waals radii of the atoms
    are less than or equal to the specified radius. The `partner` parameter specifies the column (1 or 2)
    from which the radii are selected.

    Args:
        radius (float): The radius to select.
        partner (int): The column to select the radii from. It can be either 1 or 2.

    Returns:
        A NeighborPairs object containing the pairs that meet the radius filter.
    """
    col_func = self._get_pair_column(partner)
    mask = col_func.atoms.vdw_radii <= radius

    return self.clone(self.pairs[mask], self.distances[mask])

There are also hbond-specific filters:

  • hbond_distance_filter: Filters based on the distance between the hbonded atoms
  • hbond_angle_filter: Filters based on the angle between the hydrogen bonded atoms

The last two filters operate on three atoms at a time and Lahuta implements vectorized versions of these filters. This means that they are very fast and efficient, but also that the code is a bit more complicated. For this reason, we will not discuss them here. Please see the API documentation for more information.

Learn more

See the API documentation on NeighborPairs for more information.