File size: 5,985 Bytes
d766458
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
import jax
import jax.numpy as jnp
import numpy as np

from colabdesign.shared.utils import copy_dict
from colabdesign.af.alphafold.model import config

def setup(self, crop_len=128, crop_mode="slide", crop_iter=5):

  def set_crop(crop_len=128, crop_mode="slide", crop_iter=5):
    assert crop_mode in ["slide","roll","pair"]
    assert crop_len < sum(self._lengths)
    assert self._args["copies"] == 1 or self._args["repeat"]
    assert self.protocol not in ["partial","binder"]
    self._args["crop"] = {"len":crop_len, "mode":crop_mode, "iter":crop_iter}

  #################################################################
  # function to apply to inputs
  #################################################################
  def pre_callback(inputs, opt):    
    def crop_feat(feat, pos):  
      '''crop features to specified [pos]itions'''
      if feat is None: return None
      def find(x,k):
        i = []
        for j,y in enumerate(x):
          if y == k: i.append(j)
        return i
      shapes = config.CONFIG.data.eval.feat
      NUM_RES = "num residues placeholder"
      idx = {k:find(v,NUM_RES) for k,v in shapes.items()}
      new_feat = copy_dict(feat)
      for k in new_feat.keys():
        if k in ["batch","prev"]:
          new_feat[k] = crop_feat(feat[k], pos)
        if k in idx:
          for i in idx[k]: new_feat[k] = jnp.take(new_feat[k], pos, i)
      return new_feat

    p = opt["crop_pos"]
    inputs.update(crop_feat(inputs, p))

  #################################################################
  # function to apply to outputs
  #################################################################
  def post_callback(aux, opt):
    length = sum(self._lengths)
    def uncrop_feat(x, pos, pair=False):
      '''uncrop features'''
      if pair:
        p1, p2 = pos[:,None], pos[None,:]
        return jnp.zeros((length,length)+x.shape[2:]).at[p1,p2].set(x)
      else:
        return jnp.zeros((length,)+x.shape[1:]).at[pos].set(x)
    p,x = opt["crop_pos"], aux["prev"]
    full_aux = {"prev":{
        "prev_pos":  uncrop_feat(x["prev_pos"],  p),
        "prev_pair": uncrop_feat(x["prev_pair"], p, pair=True),
        "prev_msa_first_row": uncrop_feat(x["prev_msa_first_row"], p)
      },
    }
    aux.update(full_aux)

  #################################################################
  # function to apply before design step
  #################################################################
  def pre_design_callback(self):
    def update_pos():
      c = self._args["crop"]
      L = sum(self._lengths)
      if c["mode"] == "slide":
        i = np.random.randint(0,(L-c["len"])+1)
        self.opt["crop_pos"] = np.arange(i,i+c["len"])      
      if c["mode"] == "roll":
        i = np.random.randint(0,L)
        self.opt["crop_pos"] = np.sort(np.roll(np.arange(L),L-i)[:c["len"]])
      if c["mode"] == "pair":
        # pick random pair of interactig crops
        max_L = c["len"] // 2
        # pick first crop
        i_range = np.append(np.arange(0,(L-2*max_L)+1),np.arange(max_L,(L-max_L)+1))
        i = np.random.choice(i_range)          
        # pick second crop
        j_range = np.append(np.arange(0,(i-max_L)+1),np.arange(i+max_L,(L-max_L)+1))
        if hasattr(self,"_cmap"):
          # if contact map defined, bias to interacting pairs
          w = np.array([self._cmap[i:i+max_L,j:j+max_L].sum() for j in j_range]) + 1e-8
          j = np.random.choice(j_range, p=w/w.sum())
        else:
          j = np.random.choice(j_range)               
        self.opt["crop_pos"] = np.sort(np.append(np.arange(i,i+max_L),np.arange(j,j+max_L)))

    if "crop" not in self._tmp:
      self._tmp["crop"] = {"k":self._k}
      update_pos()
    if self._k != self._tmp["crop"]["k"]:        
      if (self._k % self._args["crop"]["iter"]) == 0:
        update_pos()

      self._tmp["crop"]["k"] = self._k
      if hasattr(self,"aux"):
        for k in ["cmap","pae","plddt","atom_positions"]:
          self._tmp["crop"][k] = self.aux[k]
  
  #################################################################
  # function to apply after design step
  #################################################################
  def post_design_callback(self):
    # uncrop/accumulate features
    L = sum(self._lengths)
    def uncrop_feat(x, pos, pair=False):
      if pair:
        y = np.full((L,L)+x.shape[2:],np.nan)
        y[pos[:,None],pos[None,:]] = x
      else:
        y = np.full((L,)+x.shape[1:],np.nan)
        y[pos] = x
      return y        
    
    # uncrop features
    a, p = self.aux, self.opt["crop_pos"]
    vs = {k:uncrop_feat(a[k],p,pair=True) for k in ["cmap","pae"]}
    vs.update({k:uncrop_feat(a[k],p) for k in ["plddt","atom_positions"]})          
    
    # accumulate features
    for k,v in vs.items():
      w = self._tmp["crop"].get(k,v)
      w = np.where(np.isnan(w),v,w)
      vs[k] = np.where(np.isnan(v),w,(v+w)/2)

    self.aux.update(vs)

  #################################################################
  # SETUP
  #################################################################
  if hasattr(self,"set_crop"):
    self.set_crop(crop_len, crop_mode, crop_iter)
  
  else:
    self.set_crop = set_crop
    self.set_crop(crop_len, crop_mode, crop_iter)

    # add distances
    if self.protocol == "fixbb":
      cb_atoms = self._pdb["cb_feat"]["atoms"]
      cb_atoms[self._pdb["cb_feat"]["mask"] == 0,:] = np.nan
      cb_dist = np.sqrt(np.square(cb_atoms[:,None] - cb_atoms[None,:]).sum(-1))
      self._cmap = cb_dist < self.opt["cmap_cutoff"]

    # populate callbacks
    self._callbacks["model"]["pre"].append(pre_callback)
    self._callbacks["model"]["post"].append(post_callback)
    self._callbacks["design"]["pre"].append(pre_design_callback)
    self._callbacks["design"]["post"].append(post_design_callback)