CodeEntropy 2.3.0__py3-none-any.whl → 2.4.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
CodeEntropy/__init__.py CHANGED
@@ -8,4 +8,4 @@ entropy estimates, supporting a wide range of applications in molecular simulati
8
8
  and statistical mechanics.
9
9
  """
10
10
 
11
- __version__ = "2.3.0"
11
+ __version__ = "2.4.1"
@@ -61,13 +61,15 @@ class AxesCalculator:
61
61
  self._rot_axes = None
62
62
  self._number_of_beads = None
63
63
 
64
- def get_residue_axes(self, data_container, index: int, residue=None):
64
+ def get_residue_axes(
65
+ self, data_container, index: int, relative_index: int, residue=None
66
+ ):
65
67
  """Compute residue-level translational and rotational axes.
66
68
 
67
69
  The translational and rotational axes at the residue level.
68
70
 
69
71
  - Identify the residue (either provided or selected by `resindex index`).
70
- - Determine whether the residue is bonded to neighboring residues
72
+ - Determine whether the residue is bonded to neighbouring residues
71
73
  (previous/next in sequence) using MDAnalysis bonded selections.
72
74
  - If there are *no* bonds to other residues:
73
75
  * Use a custom principal axes, from a moment-of-inertia (MOI) tensor
@@ -76,13 +78,21 @@ class AxesCalculator:
76
78
  * Set translational axes equal to rotational axes (as per the original
77
79
  code convention).
78
80
  - If bonded to other residues:
79
- * Use default axes and MOI (MDAnalysis principal axes / inertia).
81
+ * Translational axes are principal axes of data_container.
82
+ * Find edge heavy atoms (i.e. heavy atoms bonded to neighbour residues)
83
+ and find the shortest chain between them: the backbone. Edge
84
+ atoms + backbone COM are used to determine residue rotational axes.
85
+ (see get_residue_custom_axes).Compute a custom MOI, using heavy atom
86
+ positions and heavy atom + hydrogen masses.
80
87
 
81
88
  Args:
82
89
  data_container (MDAnalysis.Universe or AtomGroup):
83
90
  Molecule and trajectory data (the fragment/molecule container).
84
91
  index (int):
85
92
  Residue index (resindex) within `data_container`.
93
+ relative_index (int):
94
+ Index of first residue within 'data_container'.
95
+ This is used to obtain index in MDA Universe for atom selections.
86
96
  residue (MDAnalysis.AtomGroup, optional):
87
97
  If provided, this residue selection will be used rather than
88
98
  selecting again.
@@ -99,41 +109,75 @@ class AxesCalculator:
99
109
  If the residue selection is empty.
100
110
  """
101
111
  # TODO refine selection so that it will work for branched polymers
102
- index_prev = index - 1
103
- index_next = index + 1
112
+
113
+ index_prev = index + relative_index - 1
114
+ index_next = index + relative_index + 1
104
115
 
105
116
  if residue is None:
106
- residue = data_container.select_atoms(f"resindex {index}")
117
+ residue = data_container.select_atoms(f"resindex {index + relative_index}")
118
+ # residue of interest
107
119
  if len(residue) == 0:
108
- raise ValueError(f"Empty residue selection for resindex={index}")
120
+ raise ValueError(
121
+ f"Empty residue selection for resindex={index + relative_index}"
122
+ )
109
123
 
110
- center = residue.atoms.center_of_mass(unwrap=True)
111
- atom_set = data_container.select_atoms(
112
- f"(resindex {index_prev} or resindex {index_next}) and bonded resid {index}"
124
+ edge_atom_set = data_container.atoms.select_atoms(
125
+ f"resindex {index + relative_index} and "
126
+ f"(bonded resindex {index_prev} or "
127
+ f"resindex {index_next})"
113
128
  )
114
129
 
115
- if len(atom_set) == 0:
116
- # No bonds to other residues.
117
- # Use a custom principal axes, from a MOI tensor that uses positions of
118
- # heavy atoms only, but including masses of heavy atom + bonded H.
119
- uas = residue.select_atoms("mass 2 to 999")
120
- ua_masses = self.get_UA_masses(residue)
130
+ uas = residue.select_atoms("mass 2 to 999")
131
+ ua_masses = self.get_UA_masses(residue)
132
+
133
+ if len(edge_atom_set) == 0:
121
134
  moi_tensor = self.get_moment_of_inertia_tensor(
122
- center_of_mass=center,
135
+ center_of_mass=np.array(residue.center_of_mass()),
123
136
  positions=uas.positions,
124
137
  masses=ua_masses,
125
138
  dimensions=data_container.dimensions[:3],
126
139
  )
127
140
  rot_axes, moment_of_inertia = self.get_custom_principal_axes(moi_tensor)
128
141
  trans_axes = rot_axes # per original convention
142
+ rot_center = np.array(residue.center_of_mass())
129
143
  else:
130
- # If bonded to other residues, use default axes and MOI.
131
144
  make_whole(data_container.atoms)
132
145
  trans_axes = data_container.atoms.principal_axes()
133
- rot_axes, moment_of_inertia = self.get_vanilla_axes(residue)
134
- center = residue.center_of_mass(unwrap=True)
135
146
 
136
- return trans_axes, rot_axes, center, moment_of_inertia
147
+ if len(edge_atom_set) == 1:
148
+ if index == 0:
149
+ # first residue: use first heavy atom
150
+ edges = [residue.atoms[0], edge_atom_set[0]]
151
+ backbone = self.get_chain(
152
+ residue, residue.atoms[0], edge_atom_set[0]
153
+ )
154
+ else:
155
+ # last residue: last heavy atom
156
+ last_index = len(uas) - 1
157
+ last = None
158
+ if last_index > 0 and last is None:
159
+ heavy_atom = uas[last_index]
160
+ last = heavy_atom
161
+ edges = [edge_atom_set[0], last]
162
+
163
+ backbone = self.get_chain(residue, edge_atom_set[0], last)
164
+ else:
165
+ edges = [edge_atom_set[0], edge_atom_set[1]]
166
+ backbone = self.get_chain(residue, edge_atom_set[0], edge_atom_set[1])
167
+ backbone_center = np.zeros(3)
168
+ for heavy_atom in backbone:
169
+ backbone_center += heavy_atom.position
170
+ backbone_center = backbone_center / len(backbone)
171
+ rot_center, rot_axes = self.get_residue_custom_axes(edges, backbone_center)
172
+
173
+ moment_of_inertia = self.get_custom_residue_moment_of_inertia(
174
+ center_of_mass=rot_center,
175
+ positions=uas.positions,
176
+ masses=ua_masses,
177
+ custom_rot_axes=rot_axes,
178
+ dimensions=data_container.dimensions[:3],
179
+ )
180
+ return trans_axes, rot_axes, rot_center, moment_of_inertia
137
181
 
138
182
  def get_residue_axes_from_topology(
139
183
  self,
@@ -170,7 +214,6 @@ class AxesCalculator:
170
214
  if box is not None
171
215
  else np.asarray(u.dimensions[:3], dtype=float)
172
216
  )
173
-
174
217
  center = residue_atoms.center_of_mass(unwrap=True)
175
218
 
176
219
  if not topology.has_neighbor_bonds:
@@ -193,7 +236,7 @@ class AxesCalculator:
193
236
 
194
237
  return trans_axes, rot_axes, center, moment_of_inertia
195
238
 
196
- def get_UA_axes(self, data_container, index: int):
239
+ def get_UA_axes(self, data_container, index: int, res_position):
197
240
  """Compute united-atom-level translational and rotational axes.
198
241
 
199
242
  The translational and rotational axes at the united-atom level.
@@ -201,22 +244,29 @@ class AxesCalculator:
201
244
  This preserves the original behaviour and its rationale:
202
245
 
203
246
  - Translational axes:
204
- Use the same custom principal-axes approach as residue level:
205
- compute a custom MOI tensor using heavy-atom coordinates but UA masses
206
- (heavy + bonded H masses), then compute the principal axes from it.
247
+ Use the same approach as residue level rotational.
248
+ Identify residue of interest and neighbours, then select
249
+ edge heavy atoms (i.e. heavy atoms bonded to neighbour residues).
250
+ If there are no bonds to neighbouring residues, use residue
251
+ .principal axes Otherwise, find the shortest chain between edge
252
+ residues: the backbone. Edge atoms + backbone COM are used to
253
+ determine UA translational axes (see get_residue_custom_axes)
207
254
 
208
255
  - Rotational axes:
209
256
  Identify heavy atoms in the residue/molecule of interest and choose
210
257
  the `index`-th heavy atom (where index corresponds to the bead index).
211
258
  Use bonded topology around that heavy atom to determine UA rotational
212
259
  axes (see :meth:`get_bonded_axes`).
260
+ Compute a custom MOI tensor using heavy-atom coordinates but UA masses
261
+ (heavy + bonded H masses), then compute the principal axes from it.
213
262
 
214
263
  Args:
215
264
  data_container (MDAnalysis.Universe or AtomGroup):
216
265
  Molecule and trajectory data.
217
266
  index (int):
218
267
  Bead index (ordinal among heavy atoms).
219
-
268
+ res_position: where the residue of interest is
269
+ in data_container
220
270
  Returns:
221
271
  Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
222
272
  - trans_axes: Translational axes (3, 3).
@@ -228,51 +278,124 @@ class AxesCalculator:
228
278
  IndexError:
229
279
  If `index` does not correspond to an existing heavy atom.
230
280
  ValueError:
231
- If bonded-axis construction fails.
281
+ If axis construction fails.
232
282
  """
233
283
 
234
- index = int(index) # bead index
235
-
284
+ index = int(index) # UA bead index
285
+ heavy_atoms = data_container.select_atoms("mass 2 to 999")
236
286
  # use the same customPI trans axes as the residue level
237
- heavy_atoms = data_container.select_atoms("prop mass > 1.1")
238
287
  if len(heavy_atoms) > 1:
239
- UA_masses = self.get_UA_masses(data_container.atoms)
240
- center = data_container.atoms.center_of_mass(unwrap=True)
241
- moment_of_inertia_tensor = self.get_moment_of_inertia_tensor(
242
- center, heavy_atoms.positions, UA_masses, data_container.dimensions[:3]
243
- )
244
- trans_axes, _moment_of_inertia = self.get_custom_principal_axes(
245
- moment_of_inertia_tensor
288
+ if len(data_container.residues) == 1:
289
+ # only the one residue => use principal axes
290
+ residue = data_container
291
+ trans_center = data_container.atoms.center_of_mass(unwrap=True)
292
+ trans_axes = data_container.atoms.principal_axes()
293
+ residue_heavy_atoms = heavy_atoms
294
+ else:
295
+ # residue of interest has at least one neighbour
296
+ if res_position == -1:
297
+ residue = data_container.residues[0]
298
+ resindex = residue.resindex
299
+ resindex_next = resindex + 1
300
+
301
+ second_edge = data_container.select_atoms(
302
+ f"resindex {resindex} and bonded resindex {resindex_next}"
303
+ )
304
+
305
+ edges = [residue.atoms[0], second_edge[0]]
306
+ backbone = self.get_chain(
307
+ residue, residue.atoms[0], second_edge.atoms[0]
308
+ )
309
+
310
+ elif res_position == 0:
311
+ # between 2 residues
312
+ residue = data_container.residues[1]
313
+ resindex = residue.resindex
314
+ resindex_next = resindex + 1
315
+ resindex_prev = resindex - 1
316
+
317
+ edge_set = data_container.select_atoms(
318
+ f"resindex {resindex} and "
319
+ f"(bonded resindex {resindex_prev} or "
320
+ f"resindex {resindex_next})"
321
+ )
322
+
323
+ edges = [edge_set[0], edge_set[1]]
324
+ backbone = self.get_chain(residue, edge_set[0], edge_set[1])
325
+
326
+ else:
327
+ # last resid
328
+ # always resindex 1 in data_container
329
+ residue = data_container.residues[1]
330
+ resindex = residue.resindex
331
+ resindex_prev = resindex - 1
332
+ first_edge = data_container.select_atoms(
333
+ f"resindex {resindex} and bonded resindex {resindex_prev}"
334
+ )
335
+
336
+ last_index = len(heavy_atoms) - 1
337
+ last = None
338
+ # look for last heavy atom
339
+ # with only one bond to another
340
+ if last_index > 0 and last is None:
341
+ heavy_atom = heavy_atoms[last_index]
342
+ last = heavy_atom
343
+
344
+ edges = [first_edge.atoms[0], last]
345
+ backbone = self.get_chain(residue, first_edge.atoms[0], last)
346
+
347
+ backbone_center = np.zeros(3)
348
+ for heavy_atom in backbone:
349
+ backbone_center += heavy_atom.position
350
+ backbone_center = backbone_center / len(backbone)
351
+
352
+ trans_center, trans_axes = self.get_residue_custom_axes(
353
+ edges, backbone_center
354
+ )
355
+ residue_heavy_atoms = residue.atoms.select_atoms("mass 2 to 999")
356
+
357
+ # look for heavy atoms in residue of interest
358
+ heavy_atom_indices = []
359
+ for atom in residue_heavy_atoms:
360
+ heavy_atom_indices.append(atom.index)
361
+ # we find the nth heavy atom
362
+ # where n is the bead index
363
+ heavy_atom_index = heavy_atom_indices[index]
364
+ heavy_atom = residue.atoms.select_atoms(f"index {heavy_atom_index}")[0]
365
+ rot_center = heavy_atom.position
366
+ rot_axes, moment_of_inertia = self.get_bonded_axes(
367
+ system=data_container,
368
+ atom=heavy_atom,
369
+ dimensions=data_container.dimensions[:3],
246
370
  )
371
+
247
372
  else:
248
- # use standard PA for UA not bonded to anything else
249
- make_whole(data_container.atoms)
250
- trans_axes = data_container.atoms.principal_axes()
373
+ # 1 heavy atom in the data_container
374
+ heavy_atom = heavy_atoms[0]
375
+ # trans and rot centres are centre of mass
376
+ rot_center = data_container.center_of_mass()
377
+ rot_axes, moment_of_inertia = self.get_bonded_axes(
378
+ system=data_container,
379
+ atom=heavy_atom,
380
+ dimensions=data_container.dimensions[:3],
381
+ )
382
+ trans_center = rot_center
383
+ # principal axes
384
+ trans_axes = rot_axes
385
+
386
+ if trans_axes is None:
387
+ raise ValueError("Unable to compute translation axes for UA bead.")
251
388
 
252
- # look for heavy atoms in residue of interest
253
- heavy_atom_indices = []
254
- for atom in heavy_atoms:
255
- heavy_atom_indices.append(atom.index)
256
- # we find the nth heavy atom
257
- # where n is the bead index
258
- heavy_atom_index = heavy_atom_indices[index]
259
- heavy_atom = data_container.select_atoms(f"index {heavy_atom_index}")
260
-
261
- center = heavy_atom.positions[0]
262
- rot_axes, moment_of_inertia = self.get_bonded_axes(
263
- system=data_container,
264
- atom=heavy_atom[0],
265
- dimensions=data_container.dimensions[:3],
266
- )
267
389
  if rot_axes is None or moment_of_inertia is None:
268
390
  raise ValueError("Unable to compute bonded axes for UA bead.")
269
391
 
270
- logger.debug(f"Translational Axes: {trans_axes}")
271
- logger.debug(f"Rotational Axes: {rot_axes}")
272
- logger.debug(f"Center: {center}")
273
- logger.debug(f"Moment of Inertia: {moment_of_inertia}")
392
+ logger.debug("Translational Axes: %s", trans_axes)
393
+ logger.debug("Rotational Axes: %s", rot_axes)
394
+ logger.debug("Translational center: %s", trans_center)
395
+ logger.debug("Rotational center: %s", rot_center)
396
+ logger.debug("Moment of Inertia: %s", moment_of_inertia)
274
397
 
275
- return trans_axes, rot_axes, center, moment_of_inertia
398
+ return trans_axes, rot_axes, rot_center, moment_of_inertia
276
399
 
277
400
  def get_UA_axes_from_topology(
278
401
  self,
@@ -429,6 +552,55 @@ class AxesCalculator:
429
552
 
430
553
  return custom_axes, custom_moment_of_inertia
431
554
 
555
+ def get_residue_custom_axes(self, edges, center):
556
+ """
557
+ Compute rotation axes at the residue level, given
558
+ two edge atoms of the residue (E1+E2),
559
+ and the centre of geometry of backbone atoms
560
+ that are not edges (C).
561
+ x axis is O-E1
562
+ y axis is O-C (perpendicular to O-E1 in the
563
+ same plane as E2)
564
+ z axis is perpendicular to the two other axes
565
+
566
+ ::
567
+
568
+ C
569
+ |
570
+ |
571
+ E1 ---- O --- E2
572
+
573
+ Args:
574
+ edges: (2,3) positions of two edge atoms
575
+ center: (3,) coordinates of the inner backbone
576
+ centre of geometry
577
+
578
+ Returns:
579
+ rot_center: (3,) rotation centre,
580
+ lies on the E1-E2 vector
581
+ rot_axes: (3,3) rotation axes of residue
582
+ """
583
+ first_edge_centre_of_geometry_vector = center - edges[0].position
584
+ # look for projection of E1-O onto E1-E2 (E1-C)
585
+ first_edge_second_edge_vector = edges[1].position - edges[0].position
586
+ first_edge_origin_vector = (
587
+ np.dot(first_edge_second_edge_vector, first_edge_centre_of_geometry_vector)
588
+ / (np.linalg.norm(first_edge_second_edge_vector) ** 2)
589
+ ) * first_edge_second_edge_vector
590
+ x_axis = -first_edge_origin_vector
591
+ # O-C = O-E1 + E1-C
592
+ origin_centre_of_geometry_vector = (
593
+ -first_edge_origin_vector + first_edge_centre_of_geometry_vector
594
+ )
595
+ y_axis = origin_centre_of_geometry_vector
596
+ z_axis = np.cross(x_axis, y_axis)
597
+ x_axis /= np.linalg.norm(x_axis)
598
+ y_axis /= np.linalg.norm(y_axis)
599
+ z_axis /= np.linalg.norm(z_axis)
600
+ rot_axes = np.array([x_axis, y_axis, z_axis])
601
+ rot_center = first_edge_origin_vector + edges[0].position
602
+ return rot_center, rot_axes
603
+
432
604
  def get_bonded_axes(self, system, atom, dimensions: np.ndarray):
433
605
  r"""Compute UA rotational axes from bonded topology around a heavy atom.
434
606
 
@@ -659,6 +831,41 @@ class AxesCalculator:
659
831
  scaled_custom_axes = unscaled_custom_axes / mod[:, np.newaxis]
660
832
  return scaled_custom_axes
661
833
 
834
+ def get_custom_residue_moment_of_inertia(
835
+ self,
836
+ center_of_mass: np.ndarray,
837
+ positions: np.ndarray,
838
+ masses: np.ndarray,
839
+ custom_rot_axes: np.ndarray,
840
+ dimensions: np.ndarray,
841
+ ):
842
+ """
843
+ Compute moment of inertia around custom axes for a bead
844
+ formed of multiple UAs.
845
+
846
+ Args:
847
+ center_of_mass: (3, ) COM for bead
848
+ positions: (N,3) positions of the UAs in the bead
849
+ masses: (N,) masses of the UAs in the bead
850
+ custom_rot_axes: (3,3) array of residue rotation axes
851
+ dimensions: (3,) simulation_box_dimensions
852
+
853
+ Returns:
854
+ np.ndarray: (3,) moment of inertia array.
855
+
856
+ """
857
+
858
+ translated_coords = self.get_vector(center_of_mass, positions, dimensions)
859
+ custom_moment_of_inertia = np.zeros(3, dtype=float)
860
+
861
+ for coord, mass in zip(translated_coords, masses, strict=True):
862
+ axis_component = np.sum(
863
+ np.cross(custom_rot_axes, coord) ** 2 * mass, axis=1
864
+ )
865
+ custom_moment_of_inertia += axis_component
866
+
867
+ return custom_moment_of_inertia
868
+
662
869
  def get_custom_moment_of_inertia(
663
870
  self,
664
871
  UA,
@@ -849,3 +1056,78 @@ class AxesCalculator:
849
1056
  ua_mass += float(h.mass)
850
1057
  ua_masses.append(ua_mass)
851
1058
  return ua_masses
1059
+
1060
+ def get_chain(self, residue, first, last):
1061
+ """
1062
+ For a given MDAnalysis AtomGroup and two given heavy atoms
1063
+ within that AtomGroup, return the
1064
+ shortest path between the two atoms.
1065
+
1066
+ Args:
1067
+ residue: MDAnalysis AtomGroup representing
1068
+ the residue/monomer of interest.
1069
+ first: First heavy atom in the chain
1070
+ last: Last heavy atom in the chain
1071
+
1072
+ Returns:
1073
+ chain: Array containing chain atoms.
1074
+ """
1075
+
1076
+ chain = []
1077
+ # at the beggining we've only visited the first atom
1078
+ visited_dict = {first: True}
1079
+ # keep the previous atom to trace back the path
1080
+ prev = {}
1081
+ # queue of next heavy atoms to visit
1082
+ next_to_visit = [first]
1083
+ # all others heavy atoms in the residue, we have not yet visited
1084
+ remaining_heavy_atoms = residue.atoms.select_atoms(
1085
+ f"(mass 2 to 999) and not index {first.index}"
1086
+ )
1087
+ for atom in remaining_heavy_atoms:
1088
+ visited_dict[atom] = False
1089
+ current = first
1090
+
1091
+ while not visited_dict[last]:
1092
+ # we haven't found a path to the last residue
1093
+ next_to_visit.pop(0)
1094
+ # we're visiting the current atom => we remove it from the queue
1095
+ bonded_atoms = residue.atoms.select_atoms(
1096
+ f"(mass 2 to 999) and bonded index {current.index}"
1097
+ )
1098
+
1099
+ if last in bonded_atoms:
1100
+ # we found a path to the last atom
1101
+ visited_dict[last] = True
1102
+ chain.append(last)
1103
+ prev[last] = current
1104
+
1105
+ else:
1106
+ for bonded_atom in bonded_atoms:
1107
+ # look for unvisited bonded atoms to the current atom we're visiting
1108
+ if not visited_dict[bonded_atom]:
1109
+ # we're going to want to visit the atoms
1110
+ next_to_visit.append(bonded_atom)
1111
+ prev[bonded_atom] = current
1112
+ # we visit the next atom in the queue
1113
+ current = next_to_visit[0]
1114
+ visited_dict[current] = True
1115
+
1116
+ # we track the previous atom back to the first atom now
1117
+ current = last
1118
+ chain = [last]
1119
+ # subtract index of first atom in resid
1120
+ # most likely will coincide with first
1121
+ # but this will work even if it doesn't
1122
+ # accout for in-residue index
1123
+ # start from last atom in chain
1124
+ while chain[-1] != first:
1125
+ # we haven't yet returned to the first atom
1126
+ current = prev[current]
1127
+ chain.append(current)
1128
+
1129
+ chain = np.flip(chain)
1130
+ # only get in between residues
1131
+ chain = chain[1:-1]
1132
+ # accout for in-residue index
1133
+ return chain
@@ -86,17 +86,24 @@ class InitCovarianceAccumulatorsNode:
86
86
 
87
87
  @staticmethod
88
88
  def _build_group_index(groups: dict[int, Any]) -> GroupIndex:
89
- """Build group id <-> index mappings.
89
+ """Build deterministic mappings between group IDs and contiguous indices.
90
+
91
+ Group IDs are sorted before indices are assigned, ensuring that the
92
+ resulting mappings do not depend on dictionary insertion order.
90
93
 
91
94
  Args:
92
- groups: Mapping of group id to group members.
95
+ groups: Mapping of group IDs to their members.
93
96
 
94
97
  Returns:
95
- GroupIndex mapping object.
98
+ Bidirectional mappings between group IDs and contiguous indices.
96
99
  """
97
- group_ids = list(groups.keys())
98
- gid2i = {gid: i for i, gid in enumerate(group_ids)}
99
- return GroupIndex(group_id_to_index=gid2i, index_to_group_id=list(group_ids))
100
+ group_ids = sorted(groups)
101
+ gid2i = {gid: index for index, gid in enumerate(group_ids)}
102
+
103
+ return GroupIndex(
104
+ group_id_to_index=gid2i,
105
+ index_to_group_id=group_ids,
106
+ )
100
107
 
101
108
  @staticmethod
102
109
  def _build_accumulators(n_groups: int) -> CovarianceAccumulators:
@@ -185,6 +185,15 @@ class FrameCovarianceNode:
185
185
  molcount: dict[tuple[int, int], int],
186
186
  ) -> None:
187
187
  """Compute united-atom second moments for one molecule.
188
+ If there are multiple residues in the molecule, build
189
+ residue group and attribute res_position:
190
+ - First residue: residue_group contains first two
191
+ residues. res_position is -1.
192
+ - Last residue: residue_group contains last two
193
+ residues. res_position is 1.
194
+ - Non-terminal residue: residue_group contains
195
+ residue of interest + two neighbours. res_position
196
+ is 0.
188
197
 
189
198
  Args:
190
199
  u: Universe-like object used to resolve bead atom indices.
@@ -203,6 +212,32 @@ class FrameCovarianceNode:
203
212
  molcount: Per-residue group sample counters, mutated in place.
204
213
  """
205
214
  for local_res_i, res in enumerate(mol.residues):
215
+ if len(mol.residues) > 1:
216
+ if local_res_i == 0:
217
+ relative_id = res.resindex
218
+ # index relative to first in mda universe
219
+ res_position = -1
220
+ residue_group = mol.select_atoms(
221
+ f"resindex {local_res_i + relative_id} "
222
+ f"or resindex {local_res_i + 1 + relative_id}"
223
+ )
224
+ elif local_res_i == len(mol.residues) - 1:
225
+ res_position = 1
226
+ residue_group = mol.select_atoms(
227
+ f"resindex {local_res_i - 1 + relative_id} "
228
+ f"or resindex {local_res_i + relative_id}"
229
+ )
230
+ else:
231
+ res_position = 0
232
+ residue_group = mol.select_atoms(
233
+ f"resindex {local_res_i - 1 + relative_id} "
234
+ f"or resindex {local_res_i + relative_id} "
235
+ f"or resindex {local_res_i + 1 + relative_id}"
236
+ )
237
+
238
+ else:
239
+ res_position = None
240
+ residue_group = res
206
241
  bead_key = (mol_id, "united_atom", local_res_i)
207
242
  bead_idx_list = beads.get(bead_key, [])
208
243
  if not bead_idx_list:
@@ -216,7 +251,7 @@ class FrameCovarianceNode:
216
251
  u=u,
217
252
  mol_id=mol_id,
218
253
  local_res_i=local_res_i,
219
- residue_atoms=res.atoms,
254
+ residue_group=residue_group,
220
255
  bead_groups=bead_groups,
221
256
  axes_manager=axes_manager,
222
257
  axes_topology=axes_topology,
@@ -224,6 +259,7 @@ class FrameCovarianceNode:
224
259
  force_partitioning=force_partitioning,
225
260
  customised_axes=customised_axes,
226
261
  is_highest=is_highest,
262
+ res_position=res_position,
227
263
  )
228
264
 
229
265
  F, T = self._ft.compute_frame_covariance(force_vecs, torque_vecs)
@@ -406,13 +442,14 @@ class FrameCovarianceNode:
406
442
  mol_id: int,
407
443
  local_res_i: int,
408
444
  bead_groups: list[Any],
409
- residue_atoms: Any,
445
+ residue_group: Any,
410
446
  axes_manager: Any,
411
447
  axes_topology: Any | None,
412
448
  box: np.ndarray | None,
413
449
  force_partitioning: float,
414
450
  customised_axes: bool,
415
451
  is_highest: bool,
452
+ res_position: int,
416
453
  ) -> tuple[list[np.ndarray], list[np.ndarray]]:
417
454
  """Build force and torque vectors for united-atom beads.
418
455
 
@@ -421,13 +458,14 @@ class FrameCovarianceNode:
421
458
  mol_id: Molecule index used in axes-topology lookup keys.
422
459
  local_res_i: Local residue index used in axes-topology lookup keys.
423
460
  bead_groups: Atom groups representing UA beads in a residue.
424
- residue_atoms: Atom group for the parent residue.
461
+ residue_group: Atom group for the parent residue group.
425
462
  axes_manager: Axes helper used to select axes, centres, and moments.
426
463
  axes_topology: Optional cached axes topology generated during static setup.
427
464
  box: Optional periodic box vector.
428
465
  force_partitioning: Force partitioning factor for highest-level vectors.
429
466
  customised_axes: Whether customised UA axes should be used.
430
467
  is_highest: Whether UA is the highest active level.
468
+ res_position: Where the residue is in the residue group
431
469
 
432
470
  Returns:
433
471
  A tuple containing lists of force vectors and torque vectors.
@@ -436,29 +474,39 @@ class FrameCovarianceNode:
436
474
  torque_vecs: list[np.ndarray] = []
437
475
 
438
476
  for ua_i, bead in enumerate(bead_groups):
477
+ if res_position == -1:
478
+ residue = residue_group.residues[0]
479
+ elif res_position == 0 or res_position == 1:
480
+ residue = residue_group.residues[1]
481
+ else:
482
+ residue = residue_group
483
+
439
484
  if customised_axes:
440
485
  ua_topology = None
441
486
  if axes_topology is not None:
442
487
  ua_topology = axes_topology.ua.get((mol_id, local_res_i, ua_i))
443
-
444
488
  if ua_topology is not None:
445
489
  trans_axes, rot_axes, center, moi = (
446
490
  axes_manager.get_UA_axes_from_topology(
447
491
  u=u,
448
- residue_atoms=residue_atoms,
492
+ residue_atoms=residue.atoms,
449
493
  topology=ua_topology,
450
494
  box=box,
451
495
  )
452
496
  )
453
497
  else:
498
+ # we don't use the topology
499
+ make_whole(residue_group)
500
+ make_whole(bead)
454
501
  trans_axes, rot_axes, center, moi = axes_manager.get_UA_axes(
455
- residue_atoms, ua_i
502
+ residue_group, ua_i, res_position
456
503
  )
504
+
457
505
  else:
458
- make_whole(residue_atoms)
506
+ # principal axes
507
+ make_whole(residue.atoms)
459
508
  make_whole(bead)
460
-
461
- trans_axes = residue_atoms.principal_axes()
509
+ trans_axes = residue.atoms.principal_axes()
462
510
  rot_axes, moi = axes_manager.get_vanilla_axes(bead)
463
511
  center = bead.center_of_mass(unwrap=True)
464
512
 
@@ -518,6 +566,8 @@ class FrameCovarianceNode:
518
566
  force_vecs: list[np.ndarray] = []
519
567
  torque_vecs: list[np.ndarray] = []
520
568
 
569
+ relative_res_i = mol.residues[0].resindex
570
+
521
571
  for local_res_i, bead in enumerate(bead_groups):
522
572
  trans_axes, rot_axes, center, moi = self._get_residue_axes(
523
573
  u=u,
@@ -525,6 +575,7 @@ class FrameCovarianceNode:
525
575
  mol_id=mol_id,
526
576
  bead=bead,
527
577
  local_res_i=local_res_i,
578
+ relative_res_i=relative_res_i,
528
579
  axes_manager=axes_manager,
529
580
  axes_topology=axes_topology,
530
581
  box=box,
@@ -561,6 +612,7 @@ class FrameCovarianceNode:
561
612
  mol_id: int,
562
613
  bead: Any,
563
614
  local_res_i: int,
615
+ relative_res_i: int,
564
616
  axes_manager: Any,
565
617
  axes_topology: Any | None,
566
618
  box: np.ndarray | None,
@@ -574,6 +626,8 @@ class FrameCovarianceNode:
574
626
  mol_id: Molecule index used in axes-topology lookup keys.
575
627
  bead: Atom group representing the residue bead.
576
628
  local_res_i: Residue index local to ``mol``.
629
+ relative_res_i: Residue index of first residue within ``mol``. This
630
+ might differ from residue index of first residue in topology.
577
631
  axes_manager: Axes helper used to select axes, centres, and moments.
578
632
  axes_topology: Optional cached axes topology generated during static setup.
579
633
  box: Optional periodic box vector.
@@ -597,7 +651,9 @@ class FrameCovarianceNode:
597
651
  box=box,
598
652
  )
599
653
 
600
- return axes_manager.get_residue_axes(mol, local_res_i, residue=res.atoms)
654
+ return axes_manager.get_residue_axes(
655
+ mol, local_res_i, relative_res_i, residue=res.atoms
656
+ )
601
657
 
602
658
  make_whole(mol.atoms)
603
659
  make_whole(bead)
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: CodeEntropy
3
- Version: 2.3.0
3
+ Version: 2.4.1
4
4
  Summary: CodeEntropy is a Python package for computing the configurational entropy of macromolecular systems using forces sampled from molecular dynamics (MD) simulations. It implements the multiscale cell correlation method to provide accurate and efficient entropy estimates, supporting a wide range of applications in molecular simulation and statistical mechanics.
5
5
  Keywords: entropy,macromolecular systems,MD simulation
6
6
  Author-email: Arghya 'Argo' Chakravorty <arghyac@umich.edu>, Donald Chung-HK <donald.chung@stfc.ac.uk>, Sarah Fegan <sarah.fegan@stfc.ac.uk>, James Gebbie-Rayet <james.gebbie@stfc.ac.uk>, Sarah Harris <sarah.harris@sheffield.ac.uk>, Richard Henchman <rhen7213@uni.sydney.edu.au>, Jonathan Higham <j.higham4@lancaster.ac.uk>, Jas Kalayan <jas.kalayan@stfc.ac.uk>, Ioana Papa <iapapa1@sheffield.ac.uk>
@@ -40,19 +40,19 @@ Requires-Dist: sphinx_copybutton>=0.5,<1.0 ; extra == "docs"
40
40
  Requires-Dist: furo>=2025.0,<2026.0 ; extra == "docs"
41
41
  Requires-Dist: markupsafe>=3.0,<4.0 ; extra == "docs"
42
42
  Requires-Dist: pre-commit>=4.5,<5.0 ; extra == "pre-commit"
43
- Requires-Dist: ruff>=0.15,<0.16 ; extra == "pre-commit"
43
+ Requires-Dist: ruff>=0.16,<0.17 ; extra == "pre-commit"
44
44
  Requires-Dist: pylint>=4.0,<5.0 ; extra == "pre-commit"
45
45
  Requires-Dist: rstcheck>=6.2,<7.0 ; extra == "pre-commit"
46
46
  Requires-Dist: pytest>=9.0,<10.0 ; extra == "testing"
47
47
  Requires-Dist: pytest-cov>=7.0,<8.0 ; extra == "testing"
48
48
  Requires-Dist: pytest-sugar>=1.1,<2.0 ; extra == "testing"
49
- Requires-Dist: pytest-xdist ; extra == "testing"
50
49
  Project-URL: Documentation, https://codeentropy.readthedocs.io
51
50
  Project-URL: Homepage, https://ccpbiosim.github.io/CodeEntropy/
52
51
  Project-URL: Repository, https://github.com/CCPBioSim/CodeEntropy
53
52
  Provides-Extra: docs
54
53
  Provides-Extra: pre-commit
55
54
  Provides-Extra: testing
55
+ Import-Name: CodeEntropy
56
56
 
57
57
  CodeEntropy
58
58
  ==============================
@@ -1,65 +1,65 @@
1
- CodeEntropy/__init__.py,sha256=e1AFhzKDwidArRO8JqsGTV9enxo_YcQhZcfD2Kt9tAs,404
2
- CodeEntropy/__main__.py,sha256=5i_ZrxQaq0q2aPllbB4SZ6BApV6oUFCgx0cGyjfMPrU,108
3
- CodeEntropy/cli.py,sha256=xgSgagCV6HQ3CoXUpG7p7Ef5hCExgxp7orz6dZZWA6c,960
4
- CodeEntropy/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
- CodeEntropy/config/argparse.py,sha256=Yjb2SWNdfKhHmfznU439cZPtdPM_QjhUD4uug2fnsek,19515
6
- CodeEntropy/config/runtime.py,sha256=iyeUrSAmz3eGpkAslAGsDzEzu7s4T8npy_Di3d8tnaU,14846
7
- CodeEntropy/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- CodeEntropy/core/dask_clusters.py,sha256=FsvLyYQPImTWRf9J4x7wUILqf3syS0Ji5JUsJN-Qp1A,8992
9
- CodeEntropy/core/logging.py,sha256=XLr2lSSC5kGPq7rQDhWCi7Vcvh3DGO9e5TDbI4l2Bvc,7775
10
- CodeEntropy/entropy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
- CodeEntropy/entropy/configurational.py,sha256=3erd1Ul4sGL5BMBDYq33f27aRTZqiEb6hv1bVfWo9QA,2392
12
- CodeEntropy/entropy/graph.py,sha256=LeDF2KxxNVgpetrIE9j0dnW64UhGywDLqoBJEf54vtc,4612
13
- CodeEntropy/entropy/orientational.py,sha256=gfpYqCKQjAAx22rrUdbiLvzfEIXy1nwJoub1rgYKCvY,3415
14
- CodeEntropy/entropy/vibrational.py,sha256=jgPWWiUW6aRCx949cFrNKuLB1MJoawD-9Uk8OB04Cs8,9366
15
- CodeEntropy/entropy/water.py,sha256=Ob3V08DyQNSWDMqpQ27rqjgHeU5AlKZfjqf9EqUdgqc,8225
16
- CodeEntropy/entropy/workflow.py,sha256=bxg8AVXGk7uwlv4u2AZ4kTQ26PMge_Z5ubWghdLtn9U,16100
17
- CodeEntropy/entropy/nodes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
- CodeEntropy/entropy/nodes/aggregate.py,sha256=4SyXP1_bM0IesOuqflRLvPgEfFu0PmKiEm4EFgnNxwg,3062
19
- CodeEntropy/entropy/nodes/configurational.py,sha256=-3j2ji_p-twPjZyjQ1T3tgN0pYLSIof9any1w5NI2as,7135
20
- CodeEntropy/entropy/nodes/orientational.py,sha256=yPN_XRJ1LOxJ7hOE2cnsjIbgQlI2avM1g3mFbGhW6fM,2481
21
- CodeEntropy/entropy/nodes/vibrational.py,sha256=3uhRqaMpK8u7QK2SDQ6RLRcr2rS3sPh6-GlTaNdTFWw,16978
22
- CodeEntropy/levels/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
- CodeEntropy/levels/axes.py,sha256=lJuVj74IB0YVZCR2m-jrv5kOOxMNnhqBwPp_BKc9D_c,33222
24
- CodeEntropy/levels/forces.py,sha256=c7XnNIBTFCHHXllr1WxTIrxEV_CkUB2b1vdFO8hUofY,11595
25
- CodeEntropy/levels/hierarchy.py,sha256=3cd4t6Tywg7VMgw4TboFF5VAHdhG4FaGBXsMptchisY,5158
26
- CodeEntropy/levels/linalg.py,sha256=PJlrKtIOYcYDycW3Tbetm6k94FKFZTq-hm28qiu_tS0,3565
27
- CodeEntropy/levels/neighbors.py,sha256=vswveQCIVBlDPQBFGc0e3whzmTJD01GjEJTpJ2UMnZ0,6255
28
- CodeEntropy/levels/search.py,sha256=p4aqyj6GV7YGLDnlmZqi12cDbjfajSIB8IIcBAoUmaQ,8769
29
- CodeEntropy/levels/dihedrals/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
- CodeEntropy/levels/dihedrals/angle_observations.py,sha256=ZsIaXRvNRzZQjBxafiWD2_3zn_GnKjc9EkN8GjrVh6Q,9861
31
- CodeEntropy/levels/dihedrals/conformational_state_builder.py,sha256=3ajT_v1Cga1JocIvuJtHPx2JZwGUtleemuF45S31iAQ,7598
32
- CodeEntropy/levels/dihedrals/kernels.py,sha256=gX75jgDDs-dv-IdI0V-u7NM-M4RVmpVppDAADFkHWxc,3594
33
- CodeEntropy/levels/dihedrals/peak_detection.py,sha256=h8j78gczkf9tlYFTMqITDc51K9l9NxS5Y-ul1gHytzw,9486
34
- CodeEntropy/levels/dihedrals/state_assignment.py,sha256=QtfYjXEBbW95lomT9linK1_5uU0nsHh1P6PyMT_VR1g,9672
35
- CodeEntropy/levels/dihedrals/topology.py,sha256=LJ_u6cY-N91A5CuNBYzk_I0xHEpvqYSC30u9T4aku7c,8825
36
- CodeEntropy/levels/execution/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
37
- CodeEntropy/levels/execution/chunks.py,sha256=VD1LQA-thRaJZKVR_5qIQk5VZQWHGE2e2JzF5JspDe0,754
38
- CodeEntropy/levels/execution/policy.py,sha256=4Yoxd7wgjyIph8YwKYpS9vHDt1u-7X7GVcoOVo9k-9Q,2915
39
- CodeEntropy/levels/execution/reducers.py,sha256=7H3vXtV2Jhe7tCQLyjxEmSEhn6iYLwyXMXKsm9XiB2s,14751
40
- CodeEntropy/levels/execution/scheduler.py,sha256=luVBarqXNO24SWwQjKNJhecFO3fng6Yu8kcd5Ky_uOc,10629
41
- CodeEntropy/levels/execution/tasks.py,sha256=hGRbC2VmJOWndGX6PnZopUxdQhcLTL6RjAudXAESd2g,11125
42
- CodeEntropy/levels/graph/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
43
- CodeEntropy/levels/graph/conformation_dag.py,sha256=2d9d9SwffYJn3ffRkPrL5HM_O5i5uiN0n_uRD-vt8O8,3147
44
- CodeEntropy/levels/graph/frame_dag.py,sha256=YxdFt81q6mhPBZuNWqN4MHZecyXpavfRw_tV3Jkv9MU,3683
45
- CodeEntropy/levels/graph/level_dag.py,sha256=RglkyLjvR0pYd4e_ScUXG75j-H9T0SWKmtLiBviiZK8,7324
46
- CodeEntropy/levels/nodes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
47
- CodeEntropy/levels/nodes/accumulators.py,sha256=PrfEFkyWC6pKB4ppjFnvCfjxblbNB5rjv-h_gpMBvT4,6002
48
- CodeEntropy/levels/nodes/axes_topology.py,sha256=QzrWbCL9YA8fzF4_WWpQGaIAAsOnjY6cVH7INDE40dk,11285
49
- CodeEntropy/levels/nodes/beads.py,sha256=9Ow065FY4jrIqPKCZJCvDNfIekJ17OBSX-bmEFsxtbc,8010
50
- CodeEntropy/levels/nodes/covariance.py,sha256=VWRRJbXbKviOPGqTiAcaZ8buylS9g3crb4G9WmOARPU,27670
51
- CodeEntropy/levels/nodes/detect_levels.py,sha256=YrM5SSceLiMqbOhr8Fy6SV-E_zkKRpvgHfDT8Grbin8,1864
52
- CodeEntropy/levels/nodes/detect_molecules.py,sha256=jILCddEicVngA7FjvQzafbG8HslaRICpT6hj2Uyi6YY,3059
53
- CodeEntropy/molecules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
54
- CodeEntropy/molecules/grouping.py,sha256=rlRVbRcxS_bPCZV8yoOCZKSpLS1F5AdpdIhaK2nWf1U,6426
55
- CodeEntropy/results/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
56
- CodeEntropy/results/reporter.py,sha256=ZmCEKhh_h7-0JGumlyrpH0Y-FaG8aGv-KkzdRAamEAQ,18368
57
- CodeEntropy/trajectory/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
58
- CodeEntropy/trajectory/frames.py,sha256=MSx_swzFjo4TgkZTR1y6W-1zuKNPg_C36I_uFqvOKpk,4354
59
- CodeEntropy/trajectory/mda.py,sha256=cWqgSgamdPnL4n3aWEKUgycjtzjpdgRgMVX6G5fln94,15803
60
- CodeEntropy/trajectory/source.py,sha256=NfYsdm1amr29h2tmJP-RKyCp3phb1SOAtAELJmGm740,1475
61
- codeentropy-2.3.0.dist-info/entry_points.txt,sha256=8Oa3H9ErsrGtx5Hc6hIHOw73wK3Ylkvfgtl_4cBb_GU,52
62
- codeentropy-2.3.0.dist-info/licenses/LICENSE,sha256=hgMaAAVW1Q6dL586OHoz8vFO-6BYn-d2aAFBpVQylFc,1072
63
- codeentropy-2.3.0.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
64
- codeentropy-2.3.0.dist-info/METADATA,sha256=lg_zE5jQc0VXhFTOHyP_wFnHhUinI8FLTPpG0vlqjfM,6531
65
- codeentropy-2.3.0.dist-info/RECORD,,
1
+ CodeEntropy/__init__.py,sha256=8puOcFWsWfTb7sI2KaqxNlWXHfuZIMrQBihf1qbsQAE,404
2
+ CodeEntropy/__main__.py,sha256=5i_ZrxQaq0q2aPllbB4SZ6BApV6oUFCgx0cGyjfMPrU,108
3
+ CodeEntropy/cli.py,sha256=xgSgagCV6HQ3CoXUpG7p7Ef5hCExgxp7orz6dZZWA6c,960
4
+ CodeEntropy/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ CodeEntropy/config/argparse.py,sha256=Yjb2SWNdfKhHmfznU439cZPtdPM_QjhUD4uug2fnsek,19515
6
+ CodeEntropy/config/runtime.py,sha256=iyeUrSAmz3eGpkAslAGsDzEzu7s4T8npy_Di3d8tnaU,14846
7
+ CodeEntropy/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ CodeEntropy/core/dask_clusters.py,sha256=FsvLyYQPImTWRf9J4x7wUILqf3syS0Ji5JUsJN-Qp1A,8992
9
+ CodeEntropy/core/logging.py,sha256=XLr2lSSC5kGPq7rQDhWCi7Vcvh3DGO9e5TDbI4l2Bvc,7775
10
+ CodeEntropy/entropy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ CodeEntropy/entropy/configurational.py,sha256=3erd1Ul4sGL5BMBDYq33f27aRTZqiEb6hv1bVfWo9QA,2392
12
+ CodeEntropy/entropy/graph.py,sha256=LeDF2KxxNVgpetrIE9j0dnW64UhGywDLqoBJEf54vtc,4612
13
+ CodeEntropy/entropy/orientational.py,sha256=gfpYqCKQjAAx22rrUdbiLvzfEIXy1nwJoub1rgYKCvY,3415
14
+ CodeEntropy/entropy/vibrational.py,sha256=jgPWWiUW6aRCx949cFrNKuLB1MJoawD-9Uk8OB04Cs8,9366
15
+ CodeEntropy/entropy/water.py,sha256=Ob3V08DyQNSWDMqpQ27rqjgHeU5AlKZfjqf9EqUdgqc,8225
16
+ CodeEntropy/entropy/workflow.py,sha256=bxg8AVXGk7uwlv4u2AZ4kTQ26PMge_Z5ubWghdLtn9U,16100
17
+ CodeEntropy/entropy/nodes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
+ CodeEntropy/entropy/nodes/aggregate.py,sha256=4SyXP1_bM0IesOuqflRLvPgEfFu0PmKiEm4EFgnNxwg,3062
19
+ CodeEntropy/entropy/nodes/configurational.py,sha256=-3j2ji_p-twPjZyjQ1T3tgN0pYLSIof9any1w5NI2as,7135
20
+ CodeEntropy/entropy/nodes/orientational.py,sha256=yPN_XRJ1LOxJ7hOE2cnsjIbgQlI2avM1g3mFbGhW6fM,2481
21
+ CodeEntropy/entropy/nodes/vibrational.py,sha256=3uhRqaMpK8u7QK2SDQ6RLRcr2rS3sPh6-GlTaNdTFWw,16978
22
+ CodeEntropy/levels/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
+ CodeEntropy/levels/axes.py,sha256=PzFKuac22C_JQvkQamKB7j4W6OUXBx-YMAyKHXlw_E0,44699
24
+ CodeEntropy/levels/forces.py,sha256=c7XnNIBTFCHHXllr1WxTIrxEV_CkUB2b1vdFO8hUofY,11595
25
+ CodeEntropy/levels/hierarchy.py,sha256=3cd4t6Tywg7VMgw4TboFF5VAHdhG4FaGBXsMptchisY,5158
26
+ CodeEntropy/levels/linalg.py,sha256=PJlrKtIOYcYDycW3Tbetm6k94FKFZTq-hm28qiu_tS0,3565
27
+ CodeEntropy/levels/neighbors.py,sha256=vswveQCIVBlDPQBFGc0e3whzmTJD01GjEJTpJ2UMnZ0,6255
28
+ CodeEntropy/levels/search.py,sha256=p4aqyj6GV7YGLDnlmZqi12cDbjfajSIB8IIcBAoUmaQ,8769
29
+ CodeEntropy/levels/dihedrals/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
+ CodeEntropy/levels/dihedrals/angle_observations.py,sha256=ZsIaXRvNRzZQjBxafiWD2_3zn_GnKjc9EkN8GjrVh6Q,9861
31
+ CodeEntropy/levels/dihedrals/conformational_state_builder.py,sha256=3ajT_v1Cga1JocIvuJtHPx2JZwGUtleemuF45S31iAQ,7598
32
+ CodeEntropy/levels/dihedrals/kernels.py,sha256=gX75jgDDs-dv-IdI0V-u7NM-M4RVmpVppDAADFkHWxc,3594
33
+ CodeEntropy/levels/dihedrals/peak_detection.py,sha256=h8j78gczkf9tlYFTMqITDc51K9l9NxS5Y-ul1gHytzw,9486
34
+ CodeEntropy/levels/dihedrals/state_assignment.py,sha256=QtfYjXEBbW95lomT9linK1_5uU0nsHh1P6PyMT_VR1g,9672
35
+ CodeEntropy/levels/dihedrals/topology.py,sha256=LJ_u6cY-N91A5CuNBYzk_I0xHEpvqYSC30u9T4aku7c,8825
36
+ CodeEntropy/levels/execution/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
37
+ CodeEntropy/levels/execution/chunks.py,sha256=VD1LQA-thRaJZKVR_5qIQk5VZQWHGE2e2JzF5JspDe0,754
38
+ CodeEntropy/levels/execution/policy.py,sha256=4Yoxd7wgjyIph8YwKYpS9vHDt1u-7X7GVcoOVo9k-9Q,2915
39
+ CodeEntropy/levels/execution/reducers.py,sha256=7H3vXtV2Jhe7tCQLyjxEmSEhn6iYLwyXMXKsm9XiB2s,14751
40
+ CodeEntropy/levels/execution/scheduler.py,sha256=luVBarqXNO24SWwQjKNJhecFO3fng6Yu8kcd5Ky_uOc,10629
41
+ CodeEntropy/levels/execution/tasks.py,sha256=hGRbC2VmJOWndGX6PnZopUxdQhcLTL6RjAudXAESd2g,11125
42
+ CodeEntropy/levels/graph/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
43
+ CodeEntropy/levels/graph/conformation_dag.py,sha256=2d9d9SwffYJn3ffRkPrL5HM_O5i5uiN0n_uRD-vt8O8,3147
44
+ CodeEntropy/levels/graph/frame_dag.py,sha256=YxdFt81q6mhPBZuNWqN4MHZecyXpavfRw_tV3Jkv9MU,3683
45
+ CodeEntropy/levels/graph/level_dag.py,sha256=RglkyLjvR0pYd4e_ScUXG75j-H9T0SWKmtLiBviiZK8,7324
46
+ CodeEntropy/levels/nodes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
47
+ CodeEntropy/levels/nodes/accumulators.py,sha256=XM7phCAIo0RK9YAtx2wrdo2oinmpwXYrH9mp1_LQD7U,6259
48
+ CodeEntropy/levels/nodes/axes_topology.py,sha256=QzrWbCL9YA8fzF4_WWpQGaIAAsOnjY6cVH7INDE40dk,11285
49
+ CodeEntropy/levels/nodes/beads.py,sha256=9Ow065FY4jrIqPKCZJCvDNfIekJ17OBSX-bmEFsxtbc,8010
50
+ CodeEntropy/levels/nodes/covariance.py,sha256=zwLPG_VRAH6hmCXeKkeQgqk7e_FRTBq0XXo-bNsR-U8,30210
51
+ CodeEntropy/levels/nodes/detect_levels.py,sha256=YrM5SSceLiMqbOhr8Fy6SV-E_zkKRpvgHfDT8Grbin8,1864
52
+ CodeEntropy/levels/nodes/detect_molecules.py,sha256=jILCddEicVngA7FjvQzafbG8HslaRICpT6hj2Uyi6YY,3059
53
+ CodeEntropy/molecules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
54
+ CodeEntropy/molecules/grouping.py,sha256=rlRVbRcxS_bPCZV8yoOCZKSpLS1F5AdpdIhaK2nWf1U,6426
55
+ CodeEntropy/results/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
56
+ CodeEntropy/results/reporter.py,sha256=ZmCEKhh_h7-0JGumlyrpH0Y-FaG8aGv-KkzdRAamEAQ,18368
57
+ CodeEntropy/trajectory/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
58
+ CodeEntropy/trajectory/frames.py,sha256=MSx_swzFjo4TgkZTR1y6W-1zuKNPg_C36I_uFqvOKpk,4354
59
+ CodeEntropy/trajectory/mda.py,sha256=cWqgSgamdPnL4n3aWEKUgycjtzjpdgRgMVX6G5fln94,15803
60
+ CodeEntropy/trajectory/source.py,sha256=NfYsdm1amr29h2tmJP-RKyCp3phb1SOAtAELJmGm740,1475
61
+ codeentropy-2.4.1.dist-info/entry_points.txt,sha256=8Oa3H9ErsrGtx5Hc6hIHOw73wK3Ylkvfgtl_4cBb_GU,52
62
+ codeentropy-2.4.1.dist-info/licenses/LICENSE,sha256=hgMaAAVW1Q6dL586OHoz8vFO-6BYn-d2aAFBpVQylFc,1072
63
+ codeentropy-2.4.1.dist-info/WHEEL,sha256=_yMHPaBV-s6fQMwUoo83NSdTCM3c8xXWFXRhqafN-7Y,81
64
+ codeentropy-2.4.1.dist-info/METADATA,sha256=Y3Cxq1oJet2Pq6A5xYm6tYu4R5-RI5quJh-IIR8I4r4,6507
65
+ codeentropy-2.4.1.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: flit 3.12.0
2
+ Generator: flit 4.0.2
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any