File size: 3,629 Bytes
bb6d2aa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
"""Hydrocarbon geometry oracle.

The lactam oracles in :mod:`staplebridge.oracles.geometry` (``MockGeometryOracle``,
``CPComposerStapledOracle``) are **not modified**. This is a separate oracle for
the hydrocarbon branch, because the two chemistries have genuinely different
feasibility criteria:

* lactam K-D/E closes at i,i+3 / i,i+4 with a 4.0-6.5 A Cα window;
* hydrocarbon RCM staples span one helical turn (i,i+4) or two (i,i+7), so the
  i,i+7 window sits much further out.

``ctype`` here additionally requires that the anchor residues really are the
anchor monomers the block names, which the lactam oracle has no notion of.
"""

from __future__ import annotations

import math
from typing import Any

from staplebridge.data.schemas import BuildingBlock
from staplebridge.hydrocarbon.actions import FailureReason, validate_hydrocarbon_staple
from staplebridge.hydrocarbon.catalog import is_hydrocarbon_block
from staplebridge.oracles.base import GeometryOracleBase


def _ca_distance(
    coords: list[tuple[float, float, float]] | None, i: int, j: int
) -> float | None:
    """Cα(i)-Cα(j) distance, or ``None`` when coordinates are unavailable."""
    if coords is None or i < 0 or j < 0 or i >= len(coords) or j >= len(coords):
        return None
    xi, yi, zi = coords[i]
    xj, yj, zj = coords[j]
    return math.sqrt((xi - xj) ** 2 + (yi - yj) ** 2 + (zi - zj) ** 2)


class HydrocarbonGeometryOracle(GeometryOracleBase):
    """Feasibility for hydrocarbon staples.

    Args:
        catalog: the hydrocarbon blocks in play, used for topology validation.
        sentinel_cgeom: penalty returned when the staple is not even
            type-feasible. Matches the lactam oracles' convention of a large
            sentinel so that stage-aware geometry can detect it.
    """

    def __init__(
        self,
        catalog: list[BuildingBlock] | None = None,
        sentinel_cgeom: float = 10.0,
    ) -> None:
        self.catalog = list(catalog or [])
        self.sentinel_cgeom = float(sentinel_cgeom)

    def ctype(
        self,
        sequence: list[str],
        anchor_pair: tuple[int, int] | None,
        block: BuildingBlock | None,
        *,
        peptide_ca: Any = None,
    ) -> bool:
        """True when ``(sequence, anchor_pair, block)`` is a legal hydrocarbon staple.

        Sequence-level only; the Cα window is scored by :meth:`cgeom`.
        """
        del peptide_ca
        if block is None or not is_hydrocarbon_block(block):
            return False
        catalog = self.catalog or [block]
        verdict = validate_hydrocarbon_staple(sequence, anchor_pair, block, catalog)
        return verdict is FailureReason.OK

    def cgeom(
        self,
        sequence: list[str],
        anchor_pair: tuple[int, int] | None,
        block: BuildingBlock | None,
        *,
        peptide_ca: Any = None,
    ) -> float:
        """Distance of Cα(i)-Cα(j) from the block's allowed window, in Å.

        Returns ``0.0`` inside the window. Without coordinates, falls back to a
        sequence-only verdict: ``0.0`` when type-feasible, else the sentinel.
        """
        if not self.ctype(sequence, anchor_pair, block):
            return self.sentinel_cgeom
        assert anchor_pair is not None and block is not None  # ctype guarantees

        i, j = anchor_pair
        distance = _ca_distance(peptide_ca, i, j)
        if distance is None:
            return 0.0

        low, high = block.ca_window
        if distance < low:
            return float(low - distance)
        if distance > high:
            return float(distance - high)
        return 0.0