2 @brief Support for the JAX Python library.
4 IMP currently has rudimentary support for running on a graphics
5 processing unit (GPU) or similar systems such as
6 Tensor Processing Units (TPUs). This support uses the
7 [JAX](https://docs.jax.dev/) Python library.
11 import jax.numpy
as jnp
18 """The space in which restraints are evaluated. See FreeSpace for the
19 default unbounded space, or PeriodicSpace for a space that implements
20 periodic boundary conditions."""
23 """If given an array of particle-particle vectors, return an array
24 of distances. If given a single particle-particle vector, return
29 """Shift r by dr and return new r"""
33 """Modify r[indexes] in place by adding dr"""
37 class FreeSpace(Space):
38 """An unbounded space with no periodic boundary conditions."""
42 return jnp.linalg.norm(dr, axis=-1)
50 return r.at[..., indexes, :].add(dr)
54 """A space with periodic boundary conditions.
56 @param side A 3D vector of the periodic boundary box dimensions.
59 def __init__(self, side):
60 self.side = jnp.asarray(side)
63 p_dr = jnp.mod(dr + self.side * 0.5, self.side) - 0.5 * self.side
64 return jnp.linalg.norm(p_dr, axis=-1)
66 def shift(self, r, dr):
67 return jnp.mod(r + dr, self.side)
70 newr = jnp.mod(r[..., indexes, :] + dr, self.side)
71 return r.at[..., indexes, :].set(newr)
75 """Get a new JAX random key seeded from IMP's random number generator"""
80 """A mapping from full particle indexes to and from the compact array.
81 This is used in _CompactArray, below. It is hashed by identity so
82 that it (unlike the arrays it contains) can be used as pytree aux data.
84 @param full_view A NumPy view of the entire IMP Model.
85 @param indexes A NumPy array of indexes of the particles that
87 @param mapping A NumPy array that maps original Particle indexes
88 to indexes into the `data` array.
90 def __init__(self, full_view, indexes, mapping):
91 self.full_view, self.indexes = full_view, indexes
92 self.mapping = mapping
95 @jax.tree_util.register_pytree_node_class
97 """Access an IMP Model Float attribute as a compacted or sparse array.
98 An IMP attribute array (returned by Model.get_numpy()) can be sparsely
99 populated. Compact it down to a flat array of only the particles that
100 have the attribute. Original Particle indexes are mapped to indexes
101 into the compacted array at JAX trace time. This should make JAX code
102 more performant since only the compacted array needs to be transferred
105 @param data Compact array of only the used particles.
106 @param remap A _Remap object that maps full particle indexes to and
107 from the compact array.
110 def __init__(self, data, remap):
111 self.data, self.remap = data, remap
113 shape = property(
lambda s: s.remap.full_view.shape)
116 def from_model(cls, m, fk):
117 """Create a new CompactArray for the given FloatKey `fk` in the
118 given IMP Model `m`."""
119 full_view = m.get_numpy(fk)
121 if full_view.ndim == 2:
124 indexes = np.unique(np.nonzero(full_view != np.inf)[-1])
126 indexes = np.nonzero(full_view != np.inf)[0]
129 len_map = full_view.shape[-1]
130 mapping = np.full(len_map, len_map + 1, dtype=np.int32)
131 mapping[indexes] = np.arange(len(indexes), dtype=np.int32)
132 remap = _Remap(full_view, indexes, mapping)
133 infpad = np.array([np.inf])
134 if full_view.ndim == 2:
135 infpad = np.broadcast_to(infpad, (full_view.shape[0], 1))
136 concat = np.concatenate((full_view[..., indexes], infpad), axis=-1)
137 return cls(concat, remap)
139 def tree_flatten(self):
142 return (self.data,), self.remap
145 def tree_unflatten(cls, aux_data, children):
146 return cls(data=children[0], remap=aux_data)
149 return self.remap.mapping[idx]
151 def _full_map(self, idx):
152 if isinstance(idx, tuple):
153 return idx[:-1] + (self._map(idx[-1]),)
155 return self._map(idx)
157 def __getitem__(self, idx):
158 """Lookup by particle index"""
159 return self.data[self._full_map(idx)]
163 """Like jax.Array.at; allow in-place array modification"""
164 return _AtCompactArray(self)
167 """Copy the JAX data back to the IMP Model"""
168 self.remap.full_view[self.remap.indexes] = self.data[:-1]
171 class _AtCompactArray:
172 """Helper class for _CompactArray.at"""
173 def __init__(self, arr):
176 def __getitem__(self, idx):
177 return _AtIdxCompactArray(self.arr, self.arr._full_map(idx))
180 class _AtIdxCompactArray:
181 """Helper class for _CompactArray.at[idx]"""
182 def __init__(self, arr, rows):
183 self.arr, self.rows = arr, rows
185 def _new(self, data):
186 return _CompactArray(data, self.arr.remap)
189 return self._new(self.arr.data.at[self.rows].set(v))
192 return self._new(self.arr.data.at[self.rows].add(v))
195 @jax.tree_util.register_pytree_node_class
197 """JAX equivalent of an IMP Model.
198 This acts like a regular Python dict.
199 @see IMP._jax_util._get_jax_model
201 def __init__(self, multi_attr_keys=None, **kwargs):
202 self._d = dict(**kwargs)
203 self.multi_attr_keys = frozenset(multi_attr_keys
or ())
205 def __getitem__(self, k):
208 def __setitem__(self, k, v):
211 def __contains__(self, k):
215 return self._d.keys()
218 return self._d.items()
221 return self._d.values()
224 m = _JAXModel(multi_attr_keys=self.multi_attr_keys)
225 m._d = self._d.copy()
231 def tree_flatten(self):
234 d_leaves, d_treedef = jax.tree.flatten(
235 self._d, is_leaf=
lambda x: x
is None)
236 return (d_leaves, (d_treedef, self.multi_attr_keys))
239 def tree_unflatten(cls, aux_data, children):
241 d_treedef, multi_attr_keys = aux_data
243 c._d = jax.tree.unflatten(d_treedef, d_leaves)
244 c.multi_attr_keys = multi_attr_keys
247 def get_vmap_axes(self):
248 """Return a suitable value for jax.vmap's in_axes.
249 If multiple attribute sets are being used, jax.vmap should
250 be used on restraints, scores, etc. to extend the functions
251 (that act on a single attribute set) to work on all sets.
252 This method returns a suitable value for in_axes for the JAX
253 model itself. If no attribute sets are being used, return None."""
254 if not self.multi_attr_keys:
256 d = _JAXModel(multi_attr_keys=self.multi_attr_keys)
257 for k, v
in self.items():
258 val = 0
if k
in self.multi_attr_keys
else None
260 if isinstance(v, _CompactArray):
261 val = _CompactArray(val, v.remap)
def shift_indexes
Modify r[indexes] in place by adding dr.
def get_random_key
Get a new JAX random key seeded from IMP's random number generator.
A space with periodic boundary conditions.
The space in which restraints are evaluated.
def shift
Shift r by dr and return new r.
def distance
If given an array of particle-particle vectors, return an array of distances.
RandomNumberGenerator random_number_generator
A shared non-GPU random number generator.