Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reduce data handovers between adaptivity classes and the MicroManagerCoupling class #137

Merged
merged 5 commits into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading