Skip to content

Commit 3ad8c1b

Browse files
authored
Performance related modifications (#162)
* Avoid copying similarity distance matrix locally inside functions * Change all copy and explicit modify operations by in-place modify in adaptivity * Fix tests * Fix tests * Formatting and adding Changelog entry * Compare float32 against a float32 * Add custom event to global adaptivity * Add custom profiling section to local adaptivity, and fix tests * Add a configurable option to output memory usage in every time window * Add psutil as a dependency * Calculate maximum similarity distance only once * Fix serial adaptivity test * Add a note of data type restriction to the collected global data for global adaptivity
1 parent 25a32c3 commit 3ad8c1b

File tree

9 files changed

+288
-371
lines changed

9 files changed

+288
-371
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## latest
44

5+
- Performance improvements: restricting data types, in-place modifications https://github.com/precice/micro-manager/pull/162
56
- Handle adaptivity case when deactivation and activation happens in the same time window https://github.com/precice/micro-manager/pull/165
67
- Add command line input argument to set log file https://github.com/precice/micro-manager/pull/163
78
- Fix adaptivity metrics logging and add logging documentation https://github.com/precice/micro-manager/pull/160

micro_manager/adaptivity/adaptivity.py

Lines changed: 45 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313

1414
class AdaptivityCalculator:
15-
def __init__(self, configurator, rank) -> None:
15+
def __init__(self, configurator, rank, nsims) -> None:
1616
"""
1717
Class constructor.
1818
@@ -22,6 +22,8 @@ def __init__(self, configurator, rank) -> None:
2222
Object which has getter functions to get parameters defined in the configuration file.
2323
rank : int
2424
Rank of the MPI communicator.
25+
nsims : int
26+
Number of micro simulations.
2527
"""
2628
self._refine_const = configurator.get_adaptivity_refining_const()
2729
self._coarse_const = configurator.get_adaptivity_coarsening_const()
@@ -36,6 +38,23 @@ def __init__(self, configurator, rank) -> None:
3638

3739
self._rank = rank
3840

41+
# similarity_dists: 2D array having similarity distances between each micro simulation pair
42+
# This matrix is modified in place via the function update_similarity_dists
43+
# NOTE: Data type restricted to float32 to save memory. Remove this restriction if higher precision is needed.
44+
self._similarity_dists = np.zeros((nsims, nsims), dtype=np.float32)
45+
46+
self._max_similarity_dist = 0.0
47+
48+
# is_sim_active: 1D array having state (active or inactive) of each micro simulation
49+
# Start adaptivity calculation with all sims active
50+
# This array is modified in place via the function update_active_sims and update_inactive_sims
51+
self._is_sim_active = np.array([True] * nsims, dtype=np.bool_)
52+
53+
# sim_is_associated_to: 1D array with values of associated simulations of inactive simulations. Active simulations have None
54+
# Active sims do not have an associated sim
55+
# This array is modified in place via the function associate_inactive_to_active
56+
self._sim_is_associated_to = np.full((nsims), -2, dtype=np.intc)
57+
3958
self._just_deactivated: list[int] = []
4059

4160
self._similarity_measure = self._get_similarity_measure(
@@ -69,29 +88,19 @@ def __init__(self, configurator, rank) -> None:
6988
csv_logger=True,
7089
)
7190

72-
def _get_similarity_dists(
73-
self, dt: float, similarity_dists: np.ndarray, data: dict
74-
) -> np.ndarray:
91+
def _update_similarity_dists(self, dt: float, data: dict) -> None:
7592
"""
7693
Calculate metric which determines if two micro simulations are similar enough to have one of them deactivated.
7794
7895
Parameters
7996
----------
8097
dt : float
8198
Current time step
82-
similarity_dists : numpy array
83-
2D array having similarity distances between each micro simulation pair
8499
data : dict
85100
Data to be used in similarity distance calculation
86-
87-
Returns
88-
-------
89-
similarity_dists : numpy array
90-
Updated 2D array having similarity distances between each micro simulation pair
91101
"""
92-
_similarity_dists = np.copy(similarity_dists)
102+
self._similarity_dists = exp(-self._hist_param * dt) * self._similarity_dists
93103

94-
data_diff = np.zeros_like(_similarity_dists)
95104
for name in data.keys():
96105
data_vals = np.array(data[name])
97106
if data_vals.ndim == 1:
@@ -100,97 +109,56 @@ def _get_similarity_dists(
100109
# The axis is later reduced with a norm.
101110
data_vals = np.expand_dims(data_vals, axis=1)
102111

103-
data_diff += self._similarity_measure(data_vals)
112+
self._similarity_dists += dt * self._similarity_measure(data_vals)
104113

105-
return exp(-self._hist_param * dt) * _similarity_dists + dt * data_diff
114+
self._max_similarity_dist = np.amax(self._similarity_dists)
106115

107-
def _update_active_sims(
108-
self, similarity_dists: np.ndarray, is_sim_active: np.ndarray
109-
) -> np.ndarray:
116+
def _update_active_sims(self) -> None:
110117
"""
111118
Update set of active micro simulations. Active micro simulations are compared to each other
112119
and if found similar, one of them is deactivated.
113-
114-
Parameters
115-
----------
116-
similarity_dists : numpy array
117-
2D array having similarity distances between each micro simulation pair
118-
is_sim_active : numpy array
119-
1D array having state (active or inactive) of each micro simulation
120-
121-
Returns
122-
-------
123-
_is_sim_active : numpy array
124-
Updated 1D array having state (active or inactive) of each micro simulation
125120
"""
126-
max_similarity_dist = np.amax(similarity_dists)
127-
128-
if max_similarity_dist == 0.0:
121+
if self._max_similarity_dist == 0.0:
129122
warn(
130123
"All similarity distances are zero, probably because all the data for adaptivity is the same. Coarsening tolerance will be manually set to minimum float number."
131124
)
132125
self._coarse_tol = sys.float_info.min
133126
else:
134127
self._coarse_tol = (
135-
self._coarse_const * self._refine_const * max_similarity_dist
128+
self._coarse_const * self._refine_const * self._max_similarity_dist
136129
)
137130

138-
_is_sim_active = np.copy(
139-
is_sim_active
140-
) # Input is_sim_active is not longer used after this point
141-
142131
# Update the set of active micro sims
143-
for i in range(_is_sim_active.size):
144-
if _is_sim_active[i]: # if sim is active
145-
if self._check_for_deactivation(i, similarity_dists, _is_sim_active):
146-
_is_sim_active[i] = False
132+
for i in range(self._is_sim_active.size):
133+
if self._is_sim_active[i]: # if sim is active
134+
if self._check_for_deactivation(i, self._is_sim_active):
135+
self._is_sim_active[i] = False
147136
self._just_deactivated.append(i)
148137

149-
return _is_sim_active
150-
151-
def _associate_inactive_to_active(
152-
self,
153-
similarity_dists: np.ndarray,
154-
is_sim_active: np.ndarray,
155-
sim_is_associated_to: np.ndarray,
156-
) -> np.ndarray:
138+
def _associate_inactive_to_active(self) -> None:
157139
"""
158140
Associate inactive micro simulations to most similar active micro simulation.
159-
160-
Parameters
161-
----------
162-
similarity_dists : numpy array
163-
2D array having similarity distances between each micro simulation pair
164-
is_sim_active : numpy array
165-
1D array having state (active or inactive) of each micro simulation
166-
sim_is_associated_to : numpy array
167-
1D array with values of associated simulations of inactive simulations. Active simulations have None
168-
169-
Returns
170-
-------
171-
_sim_is_associated_to : numpy array
172-
1D array with values of associated simulations of inactive simulations. Active simulations have None
173141
"""
174-
active_ids = np.where(is_sim_active)[0]
175-
inactive_ids = np.where(is_sim_active == False)[0]
142+
active_ids = np.where(self._is_sim_active)[0]
143+
inactive_ids = np.where(self._is_sim_active == False)[0]
176144

177-
_sim_is_associated_to = np.copy(sim_is_associated_to)
145+
# Start with a large distance to trigger the search for the most similar active sim
146+
dist_min_start_value = 2 * self._max_similarity_dist
178147

179148
# Associate inactive micro sims to active micro sims
180149
for inactive_id in inactive_ids:
181-
dist_min = sys.float_info.max
150+
# Begin with a large distance to trigger the search for the most similar active sim
151+
dist_min = dist_min_start_value
182152
for active_id in active_ids:
183153
# Find most similar active sim for every inactive sim
184-
if similarity_dists[inactive_id, active_id] < dist_min:
154+
if self._similarity_dists[inactive_id, active_id] < dist_min:
185155
associated_active_id = active_id
186-
dist_min = similarity_dists[inactive_id, active_id]
187-
188-
_sim_is_associated_to[inactive_id] = associated_active_id
156+
dist_min = self._similarity_dists[inactive_id, active_id]
189157

190-
return _sim_is_associated_to
158+
self._sim_is_associated_to[inactive_id] = associated_active_id
191159

192160
def _check_for_activation(
193-
self, inactive_id: int, similarity_dists: np.ndarray, is_sim_active: np.ndarray
161+
self, inactive_id: int, is_sim_active: np.ndarray
194162
) -> bool:
195163
"""
196164
Check if an inactive simulation needs to be activated.
@@ -199,8 +167,6 @@ def _check_for_activation(
199167
----------
200168
inactive_id : int
201169
ID of inactive simulation which is checked for activation.
202-
similarity_dists : numpy array
203-
2D array having similarity distances between each micro simulation pair.
204170
is_sim_active : numpy array
205171
1D array having state (active or inactive) of each micro simulation.
206172
@@ -211,13 +177,13 @@ def _check_for_activation(
211177
"""
212178
active_sim_ids = np.where(is_sim_active)[0]
213179

214-
dists = similarity_dists[inactive_id, active_sim_ids]
180+
dists = self._similarity_dists[inactive_id, active_sim_ids]
215181

216182
# If inactive sim is not similar to any active sim, activate it
217183
return min(dists) > self._ref_tol
218184

219185
def _check_for_deactivation(
220-
self, active_id: int, similarity_dists: np.ndarray, is_sim_active: np.ndarray
186+
self, active_id: int, is_sim_active: np.ndarray
221187
) -> bool:
222188
"""
223189
Check if an active simulation needs to be deactivated.
@@ -226,8 +192,6 @@ def _check_for_deactivation(
226192
----------
227193
active_id : int
228194
ID of active simulation which is checked for deactivation.
229-
similarity_dists : numpy array
230-
2D array having similarity distances between each micro simulation pair.
231195
is_sim_active : numpy array
232196
1D array having state (active or inactive) of each micro simulation.
233197
@@ -241,7 +205,7 @@ def _check_for_deactivation(
241205
for active_id_2 in active_sim_ids:
242206
if active_id != active_id_2: # don't compare active sim to itself
243207
# If active sim is similar to another active sim, deactivate it
244-
if similarity_dists[active_id, active_id_2] < self._coarse_tol:
208+
if self._similarity_dists[active_id, active_id_2] < self._coarse_tol:
245209
return True
246210
return False
247211

0 commit comments

Comments
 (0)