Skip to content

Commit

Permalink
Reduce data handovers between adaptivity classes and the MicroManager…
Browse files Browse the repository at this point in the history
…Coupling class (#137)

* Moving adaptivity metrics Logger creation inside the adaptivity base class

* Move handling of adaptivity data inside the respective classes

* Pass rank to the LocalAdaptivityCalculator definition

* Fix tests

* Add CHANGELOG enty and remove unnecessary comments
  • Loading branch information
IshaanDesai authored Dec 16, 2024
1 parent 4f9acf7 commit aa7e8ed
Show file tree
Hide file tree
Showing 8 changed files with 202 additions and 222 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## latest

- Remove the `adaptivity_data` data structure and handle all adaptivity data internally https://github.com/precice/micro-manager/pull/137
- Improve logging by wrapping Python logger in a class https://github.com/precice/micro-manager/pull/133
- Refactor large parts of solve and adaptivity to group datasets and simplify handling https://github.com/precice/micro-manager/pull/135
- Add information about adaptivity tuning parameters https://github.com/precice/micro-manager/pull/131
Expand Down
16 changes: 14 additions & 2 deletions micro_manager/adaptivity/adaptivity.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,22 @@
from math import exp
from typing import Callable
from warnings import warn
from micro_manager.tools.logging_wrapper import Logger

import numpy as np


class AdaptivityCalculator:
def __init__(self, configurator) -> None:
def __init__(self, configurator, rank) -> None:
"""
Class constructor.
Parameters
----------
configurator : object of class Config
Object which has getter functions to get parameters defined in the configuration file.
logger : Logger defined from the standard package logging
rank : int
Rank of the MPI communicator.
"""
self._refine_const = configurator.get_adaptivity_refining_const()
self._coarse_const = configurator.get_adaptivity_coarsening_const()
Expand All @@ -29,10 +31,20 @@ def __init__(self, configurator) -> None:
self._coarse_tol = 0.0
self._ref_tol = 0.0

self._rank = rank

self._similarity_measure = self._get_similarity_measure(
configurator.get_adaptivity_similarity_measure()
)

self._metrics_logger = Logger(
"Adaptivity", "adaptivity-metrics.csv", rank, csv_logger=True
)

self._metrics_logger.log_info_one_rank(
"Time Window,Avg Active Sims,Avg Inactive Sims,Max Active,Max Inactive"
)

def _get_similarity_dists(
self, dt: float, similarity_dists: np.ndarray, data: dict
) -> np.ndarray:
Expand Down
134 changes: 72 additions & 62 deletions micro_manager/adaptivity/global_adaptivity.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,24 @@ def __init__(
comm : MPI.COMM_WORLD
Global communicator of MPI.
"""
super().__init__(configurator)
super().__init__(configurator, rank)
self._global_ids = global_ids
self._comm = comm
self._rank = rank

# similarity_dists: 2D array having similarity distances between each micro simulation pair
self._similarity_dists = np.zeros(
(global_number_of_sims, global_number_of_sims)
)

# is_sim_active: 1D array having state (active or inactive) of each micro simulation
# Start adaptivity calculation with all sims active
self._is_sim_active = np.array([True] * global_number_of_sims)

# sim_is_associated_to: 1D array with values of associated simulations of inactive simulations. Active simulations have None
# Active sims do not have an associated sim
self._sim_is_associated_to = np.full((global_number_of_sims), -2, dtype=np.intc)

local_number_of_sims = len(global_ids)

# Create a map of micro simulation global IDs and the ranks on which they are
Expand All @@ -65,13 +78,17 @@ def __init__(
if self._rank_of_sim[i] == self._rank:
self._is_sim_on_this_rank[i] = True

# Copies of variables for checkpointing
self._similarity_dists_cp = None
self._is_sim_active_cp = None
self._sim_is_associated_to_cp = None

def compute_adaptivity(
self,
dt: float,
micro_sims: list,
adaptivity_data_nm1: list,
data_for_adaptivity: dict,
) -> tuple:
) -> None:
"""
Compute adaptivity globally based on similarity distances and micro simulation states
Expand All @@ -81,21 +98,8 @@ def compute_adaptivity(
Current time step of the macro-micro coupled problem
micro_sims : list
List of objects of class MicroProblem, which are the micro simulations
adaptivity_data_nm1 : list
List of numpy arrays:
similarity_dists (2D array having similarity distances between each micro simulation pair)
is_sim_active (1D array having state (active or inactive) of each micro simulation)
sim_is_associated_to (1D array with values of associated simulations of inactive simulations. Active simulations have None)
data_for_adaptivity : dict
Dictionary with keys as names of data to be used in the similarity calculation, and values as the respective data for the micro simulations
Results
-------
list
List of numpy arrays:
similarity_dists (2D array having similarity distances between each micro simulation pair)
is_sim_active (1D array having state (active or inactive) of each micro simulation)
sim_is_associated_to (1D array with values of associated simulations of inactive simulations. Active simulations have None)
"""
for name in data_for_adaptivity.keys():
if name not in self._adaptivity_data_names:
Expand All @@ -112,71 +116,56 @@ def compute_adaptivity(
global_data_for_adaptivity[name] = np.concatenate((data_as_list[:]), axis=0)

similarity_dists = self._get_similarity_dists(
dt, adaptivity_data_nm1[0], global_data_for_adaptivity
dt, self._similarity_dists, global_data_for_adaptivity
)

is_sim_active = self._update_active_sims(
similarity_dists, adaptivity_data_nm1[1]
)
is_sim_active = self._update_active_sims(similarity_dists, self._is_sim_active)

is_sim_active, sim_is_associated_to = self._update_inactive_sims(
similarity_dists, is_sim_active, adaptivity_data_nm1[2], micro_sims
similarity_dists, is_sim_active, self._sim_is_associated_to, micro_sims
)

sim_is_associated_to = self._associate_inactive_to_active(
similarity_dists, is_sim_active, sim_is_associated_to
)

return similarity_dists, is_sim_active, sim_is_associated_to
# Update member variables
self._similarity_dists = similarity_dists
self._is_sim_active = is_sim_active
self._sim_is_associated_to = sim_is_associated_to

def get_active_sim_ids(self, is_sim_active: np.array) -> np.ndarray:
def get_active_sim_ids(self) -> np.ndarray:
"""
Get the ids of active simulations.
Parameters
----------
is_sim_active : numpy array
1D array having state (active or inactive) of each micro simulation
Returns
-------
numpy array
1D array of active simulation ids
"""
return np.where(is_sim_active[self._global_ids[0] : self._global_ids[-1] + 1])[
0
]
return np.where(
self._is_sim_active[self._global_ids[0] : self._global_ids[-1] + 1]
)[0]

def get_inactive_sim_ids(self, is_sim_active: np.array) -> np.ndarray:
def get_inactive_sim_ids(self) -> np.ndarray:
"""
Get the ids of inactive simulations.
Parameters
----------
is_sim_active : numpy array
1D array having state (active or inactive) of each micro simulation
Returns
-------
numpy array
1D array of inactive simulation ids
"""
return np.where(
is_sim_active[self._global_ids[0] : self._global_ids[-1] + 1] == False
self._is_sim_active[self._global_ids[0] : self._global_ids[-1] + 1] == False
)[0]

def get_full_field_micro_output(
self, adaptivity_data: list, micro_output: list
) -> list:
def get_full_field_micro_output(self, micro_output: list) -> list:
"""
Get the full field micro output from active simulations to inactive simulations.
Parameters
----------
adaptivity_data : list
List of numpy arrays:
similarity_dists (2D array having similarity distances between each micro simulation pair)
is_sim_active (1D array having state (active or inactive) of each micro simulation)
sim_is_associated_to (1D array with values of associated simulations of inactive simulations. Active simulations have None)
micro_output : list
List of dicts having individual output of each simulation. Only the active simulation outputs are entered.
Expand All @@ -186,17 +175,25 @@ def get_full_field_micro_output(
List of dicts having individual output of each simulation. Active and inactive simulation outputs are entered.
"""
micro_sims_output = deepcopy(micro_output)
self._communicate_micro_output(adaptivity_data[1:3], micro_sims_output)
self._communicate_micro_output(
self._is_sim_active, self._sim_is_associated_to, micro_sims_output
)

return micro_sims_output

def log_metrics(self, logger, adaptivity_data: list, n: int) -> None:
""" """
is_sim_active = adaptivity_data[1]
global_active_sims = np.count_nonzero(is_sim_active)
global_inactive_sims = np.count_nonzero(is_sim_active == False)
def log_metrics(self, n: int) -> None:
"""
Log metrics for global adaptivity.
logger.log_info_one_rank(
Parameters
----------
n : int
Time step count at which the metrics are logged
"""
global_active_sims = np.count_nonzero(self._is_sim_active)
global_inactive_sims = np.count_nonzero(self._is_sim_active == False)

self._metrics_logger.log_info_one_rank(
"{},{},{},{},{}".format(
n,
np.mean(global_active_sims),
Expand All @@ -206,9 +203,26 @@ def log_metrics(self, logger, adaptivity_data: list, n: int) -> None:
)
)

def write_checkpoint(self) -> None:
"""
Write checkpoint.
"""
self._similarity_dists_cp = np.copy(self._similarity_dists)
self._is_sim_active_cp = np.copy(self._is_sim_active)
self._sim_is_associated_to_cp = np.copy(self._sim_is_associated_to)

def read_checkpoint(self) -> None:
"""
Read checkpoint.
"""
self._similarity_dists = np.copy(self._similarity_dists_cp)
self._is_sim_active = np.copy(self._is_sim_active_cp)
self._sim_is_associated_to = np.copy(self._sim_is_associated_to_cp)

def _communicate_micro_output(
self,
adaptivity_data: list,
is_sim_active: np.ndarray,
sim_is_associated_to: np.ndarray,
micro_output: list,
) -> None:
"""
Expand All @@ -217,17 +231,13 @@ def _communicate_micro_output(
Parameters
----------
micro_sims : list
List of objects of class MicroProblem, which are the micro simulations
adaptivity_data : list
List of numpy arrays:
is_sim_active (1D array having state (active or inactive) of each micro simulation)
sim_is_associated_to (1D array with values of associated simulations of inactive simulations. Active simulations have None)
is_sim_active : np.ndarray
1D array having state (active or inactive) of each micro simulation
sim_is_associated_to : np.ndarray
1D array with values of associated simulations of inactive simulations. Active simulations have None
micro_output : list
List of dicts having individual output of each simulation. Only the active simulation outputs are entered.
"""
is_sim_active = adaptivity_data[0]
sim_is_associated_to = adaptivity_data[1]

inactive_local_ids = np.where(
is_sim_active[self._global_ids[0] : self._global_ids[-1] + 1] == False
Expand Down
Loading

0 comments on commit aa7e8ed

Please sign in to comment.