Skip to content

Commit aa42de4

Browse files
committed
Refactor get_beads to remove padding and simplify force/torque matrix storage
- Removed the `_pad_and_add` function from `get_beads` so it returns only the actual bead selections without added padding. - Changed force and torque matrix storage from large pre-allocated lists with None placeholders to dictionaries keyed by `(molecule_id, residue_id)`. - These changes eliminate unnecessary data padding and improve direct access to relevant matrices. - Result is cleaner data structures and simpler, more reliable indexing.
1 parent 9514e7e commit aa42de4

2 files changed

Lines changed: 34 additions & 69 deletions

File tree

CodeEntropy/entropy.py

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,10 @@ def execute(self):
6161
self._data_logger.residue_data,
6262
)
6363

64+
# Get reduced universe and initialize values
6465
reduced_atom = self._get_reduced_universe()
6566
number_molecules, levels = self._level_manager.select_levels(reduced_atom)
66-
number_residues = len(reduced_atom.residues)
67+
# number_residues = len(reduced_atom.residues)
6768

6869
ve = VibrationalEntropy(
6970
self._run_manager,
@@ -81,14 +82,14 @@ def execute(self):
8182
)
8283

8384
# Initialise force and torque matrices
84-
force_matrix_ua = [None for _ in range(number_residues)]
85+
force_matrix_ua = {}
86+
torque_matrix_ua = {}
8587
force_matrix_res = [None for _ in range(number_molecules)]
86-
force_matrix_poly = [None for _ in range(number_molecules)]
87-
torque_matrix_ua = [None for _ in range(number_residues)]
8888
torque_matrix_res = [None for _ in range(number_molecules)]
89+
force_matrix_poly = [None for _ in range(number_molecules)]
8990
torque_matrix_poly = [None for _ in range(number_molecules)]
9091

91-
states_ua = [None for _ in range(number_residues)]
92+
states_ua = {}
9293
states_res = [None for _ in range(number_molecules)]
9394

9495
for timestep in reduced_atom.trajectory[start:end:step]:
@@ -104,6 +105,8 @@ def execute(self):
104105
# Get matrices for vibrational entropy calculations
105106
if level == "united_atom":
106107
for res_id, residue in enumerate(mol_container.residues):
108+
key = (molecule_id, res_id)
109+
107110
res_container = self._run_manager.new_U_select_atom(
108111
mol_container,
109112
(
@@ -117,12 +120,13 @@ def execute(self):
117120
level,
118121
number_frames,
119122
highest_level,
120-
force_matrix_ua[res_id],
121-
torque_matrix_ua[res_id],
123+
force_matrix_ua.get(key),
124+
torque_matrix_ua.get(key),
122125
)
123126
)
124-
force_matrix_ua[res_id] = force_matrix
125-
torque_matrix_ua[res_id] = torque_matrix
127+
128+
force_matrix_ua[key] = force_matrix
129+
torque_matrix_ua[key] = torque_matrix
126130

127131
elif level == "residue":
128132
force_matrix, torque_matrix = self._level_manager.get_matrices(
@@ -148,18 +152,15 @@ def execute(self):
148152
force_matrix_poly[molecule_id] = force_matrix
149153
torque_matrix_poly[molecule_id] = torque_matrix
150154

151-
# TODO When function is ready
152-
# Get environment information for orientational entropy
153-
# if highest_level:
154-
# number_neighbours = get_neighbours()
155-
156155
# Get states for conformational entropy calculation
157156
bin_width = self._args.bin_width
158157
for molecule_id in range(number_molecules):
159158
mol_container = self._get_molecule_container(reduced_atom, molecule_id)
160159
for level in levels[molecule_id]:
161160
if level == "united_atom":
162161
for res_id, residue in enumerate(mol_container.residues):
162+
key = (molecule_id, res_id)
163+
163164
res_container = self._run_manager.new_U_select_atom(
164165
mol_container,
165166
(
@@ -173,7 +174,7 @@ def execute(self):
173174

174175
dihedrals = self._level_manager.get_dihedrals(heavy_res, level)
175176
for dihedral in dihedrals:
176-
states_ua[res_id] = ce.assign_conformation(
177+
states_ua[key] = ce.assign_conformation(
177178
heavy_res,
178179
dihedral,
179180
number_frames,
@@ -183,7 +184,7 @@ def execute(self):
183184
step,
184185
)
185186

186-
if level == "residue":
187+
elif level == "residue":
187188
dihedrals = self._level_manager.get_dihedrals(mol_container, level)
188189
for dihedral in dihedrals:
189190
states_res[molecule_id] = ce.assign_conformation(
@@ -200,21 +201,21 @@ def execute(self):
200201
for molecule_id in range(number_molecules):
201202
mol_container = self._get_molecule_container(reduced_atom, molecule_id)
202203
for level in levels[molecule_id]:
203-
# Identify if level is the highest (molecule) level
204204
highest_level = level == levels[molecule_id][-1]
205205

206206
if level == "united_atom":
207207
for res_id, residue in enumerate(mol_container.residues):
208+
key = (molecule_id, res_id)
208209
self._process_united_atom_entropy(
209210
molecule_id,
210211
mol_container,
211212
res_id,
212213
ve,
213214
ce,
214215
level,
215-
force_matrix_ua[res_id],
216-
torque_matrix_ua[res_id],
217-
states_ua[res_id],
216+
force_matrix_ua[key],
217+
torque_matrix_ua[key],
218+
states_ua[key],
218219
highest_level,
219220
)
220221

CodeEntropy/levels.py

Lines changed: 13 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -127,100 +127,64 @@ def get_matrices(
127127
data_container, list_of_beads[bead_index], rot_axes
128128
)
129129

130-
# Make covariance matrices - looping over pairs of beads
131-
# list of pairs of indices
132-
pair_list = [(i, j) for i in range(number_beads) for j in range(number_beads)]
133-
130+
# Create covariance submatrices
134131
force_submatrix = [
135132
[0 for _ in range(number_beads)] for _ in range(number_beads)
136133
]
137134
torque_submatrix = [
138135
[0 for _ in range(number_beads)] for _ in range(number_beads)
139136
]
140137

141-
for i, j in pair_list:
142-
# for each pair of beads
143-
# reducing effort because the matrix for [i][j] is the transpose of the one
144-
# for [j][i]
145-
if i <= j:
146-
# calculate the force covariance segment of the matrix
138+
for i in range(number_beads):
139+
for j in range(i, number_beads):
147140
f_sub = self.create_submatrix(
148141
weighted_forces[i], weighted_forces[j], number_frames
149142
)
150143
t_sub = self.create_submatrix(
151144
weighted_torques[i], weighted_torques[j], number_frames
152145
)
153-
154146
force_submatrix[i][j] = f_sub
155147
force_submatrix[j][i] = f_sub.T
156148
torque_submatrix[i][j] = t_sub
157149
torque_submatrix[j][i] = t_sub.T
158150

159-
# use np.block to make submatrices into one matrix
151+
# Convert block matrices to full matrix
160152
force_block = np.block(
161153
[
162154
[force_submatrix[i][j] for j in range(number_beads)]
163155
for i in range(number_beads)
164156
]
165157
)
166-
167158
torque_block = np.block(
168159
[
169160
[torque_submatrix[i][j] for j in range(number_beads)]
170161
for i in range(number_beads)
171162
]
172163
)
173164

174-
# Accumulate into full matrices, with shape-safe padding if needed
165+
# Enforce consistent shape before accumulation
175166
if force_matrix is None:
176167
force_matrix = np.zeros_like(force_block)
177168
elif force_matrix.shape != force_block.shape:
178-
force_matrix = self._pad_and_add(force_matrix, force_block)
169+
raise ValueError(
170+
f"Inconsistent force matrix shape: existing "
171+
f"{force_matrix.shape}, new {force_block.shape}"
172+
)
179173
else:
180174
force_matrix += force_block
181175

182176
if torque_matrix is None:
183177
torque_matrix = np.zeros_like(torque_block)
184178
elif torque_matrix.shape != torque_block.shape:
185-
torque_matrix = self._pad_and_add(torque_matrix, torque_block)
179+
raise ValueError(
180+
f"Inconsistent torque matrix shape: existing "
181+
f"{torque_matrix.shape}, new {torque_block.shape}"
182+
)
186183
else:
187184
torque_matrix += torque_block
188185

189186
return force_matrix, torque_matrix
190187

191-
def _pad_and_add(self, A, B):
192-
"""
193-
Pads two 2D numpy arrays with zeros to match their largest dimensions
194-
and returns their element-wise sum.
195-
196-
Both input arrays A and B can have different shapes. This function
197-
creates zero-padded versions of A and B with the shape equal to the
198-
maximum number of rows and columns from both arrays, then adds them.
199-
200-
Parameters
201-
----------
202-
A : np.ndarray
203-
First 2D array to pad and add.
204-
B : np.ndarray
205-
Second 2D array to pad and add.
206-
207-
Returns
208-
-------
209-
np.ndarray
210-
A new 2D array containing the element-wise sum of the padded A and B,
211-
with shape (max_rows, max_cols).
212-
"""
213-
max_rows = max(A.shape[0], B.shape[0])
214-
max_cols = max(A.shape[1], B.shape[1])
215-
216-
A_pad = np.zeros((max_rows, max_cols))
217-
B_pad = np.zeros((max_rows, max_cols))
218-
219-
A_pad[: A.shape[0], : A.shape[1]] = A
220-
B_pad[: B.shape[0], : B.shape[1]] = B
221-
222-
return A_pad + B_pad
223-
224188
def get_dihedrals(self, data_container, level):
225189
"""
226190
Define the set of dihedrals for use in the conformational entropy function.

0 commit comments

Comments
 (0)