From 9bfddb4427da9ace6e4eecb837974bab6d5d646a Mon Sep 17 00:00:00 2001 From: Ishaan Desai Date: Thu, 7 Nov 2024 12:29:12 +0100 Subject: [PATCH 01/17] Change logging by wrapping Python logger in a new class --- micro_manager/adaptivity/adaptivity.py | 4 +- micro_manager/adaptivity/global_adaptivity.py | 25 +++--- micro_manager/adaptivity/local_adaptivity.py | 12 +-- micro_manager/config.py | 23 +++--- micro_manager/micro_manager.py | 54 ++++++------- micro_manager/micro_manager_base.py | 18 +---- micro_manager/tools/logging_wrapper.py | 81 +++++++++++++++++++ 7 files changed, 136 insertions(+), 81 deletions(-) create mode 100644 micro_manager/tools/logging_wrapper.py diff --git a/micro_manager/adaptivity/adaptivity.py b/micro_manager/adaptivity/adaptivity.py index b1240505..bf0fa3c0 100644 --- a/micro_manager/adaptivity/adaptivity.py +++ b/micro_manager/adaptivity/adaptivity.py @@ -10,7 +10,7 @@ class AdaptivityCalculator: - def __init__(self, configurator, logger) -> None: + def __init__(self, configurator) -> None: """ Class constructor. @@ -26,8 +26,6 @@ def __init__(self, configurator, logger) -> None: self._adaptivity_data_names = configurator.get_data_for_adaptivity() self._adaptivity_type = configurator.get_adaptivity_type() - self._logger = logger - self._coarse_tol = 0.0 self._ref_tol = 0.0 diff --git a/micro_manager/adaptivity/global_adaptivity.py b/micro_manager/adaptivity/global_adaptivity.py index 90690ec7..0e30c7a3 100644 --- a/micro_manager/adaptivity/global_adaptivity.py +++ b/micro_manager/adaptivity/global_adaptivity.py @@ -19,7 +19,6 @@ class GlobalAdaptivityCalculator(AdaptivityCalculator): def __init__( self, configurator, - logger, global_number_of_sims: float, global_ids: list, rank: int, @@ -43,7 +42,7 @@ def __init__( comm : MPI.COMM_WORLD Global communicator of MPI. """ - super().__init__(configurator, logger) + super().__init__(configurator) self._global_ids = global_ids self._comm = comm self._rank = rank @@ -127,17 +126,17 @@ def compute_adaptivity( similarity_dists, is_sim_active, sim_is_associated_to ) - self._logger.info( - "{} active simulations, {} inactive simulations".format( - np.count_nonzero( - is_sim_active[self._global_ids[0] : self._global_ids[-1] + 1] - ), - np.count_nonzero( - is_sim_active[self._global_ids[0] : self._global_ids[-1] + 1] - == False - ), - ) - ) + # self._logger.info( + # "{} active simulations, {} inactive simulations".format( + # np.count_nonzero( + # is_sim_active[self._global_ids[0] : self._global_ids[-1] + 1] + # ), + # np.count_nonzero( + # is_sim_active[self._global_ids[0] : self._global_ids[-1] + 1] + # == False + # ), + # ) + # ) return similarity_dists, is_sim_active, sim_is_associated_to diff --git a/micro_manager/adaptivity/local_adaptivity.py b/micro_manager/adaptivity/local_adaptivity.py index a2defb8c..54f5eb78 100644 --- a/micro_manager/adaptivity/local_adaptivity.py +++ b/micro_manager/adaptivity/local_adaptivity.py @@ -80,12 +80,12 @@ def compute_adaptivity( similarity_dists, is_sim_active, sim_is_associated_to ) - self._logger.info( - "{} active simulations, {} inactive simulations".format( - np.count_nonzero(is_sim_active), - np.count_nonzero(is_sim_active == False), - ) - ) + # self._logger.info( + # "{} active simulations, {} inactive simulations".format( + # np.count_nonzero(is_sim_active), + # np.count_nonzero(is_sim_active == False), + # ) + # ) return similarity_dists, is_sim_active, sim_is_associated_to diff --git a/micro_manager/config.py b/micro_manager/config.py index b6eb7cd4..11884db9 100644 --- a/micro_manager/config.py +++ b/micro_manager/config.py @@ -5,6 +5,7 @@ import json import os from warnings import warn +from .tools.logging_wrapper import Logger class Config: @@ -13,7 +14,7 @@ class Config: the config class in https://github.com/precice/fenics-adapter/tree/develop/fenicsadapter """ - def __init__(self, logger, config_filename): + def __init__(self, config_filename): """ Constructor of the Config class. @@ -22,7 +23,7 @@ def __init__(self, logger, config_filename): config_filename : string Name of the JSON configuration file """ - self._logger = logger + self._logger = Logger("Config", "micro_manager_config.log", 0, 1) self._micro_file_name = None @@ -96,7 +97,7 @@ def read_json(self, config_filename): "Write data dictionary as a value other than 'scalar' or 'vector'" ) except BaseException: - self._logger.info( + self._logger.log_info_one_rank( "No write data names provided. Micro manager will only read data from preCICE." ) @@ -115,7 +116,7 @@ def read_json(self, config_filename): "Read data dictionary as a value other than 'scalar' or 'vector'" ) except BaseException: - self._logger.info( + self._logger.log_info_one_rank( "No read data names provided. Micro manager will only write data to preCICE." ) @@ -126,7 +127,7 @@ def read_json(self, config_filename): self._output_micro_sim_time = True self._write_data_names["micro_sim_time"] = False except BaseException: - self._logger.info( + self._logger.log_info_one_rank( "Micro manager will not output time required to solve each micro simulation in each time step." ) @@ -165,7 +166,7 @@ def read_json_micro_manager(self): "Adaptivity settings are provided but adaptivity is turned off." ) except BaseException: - self._logger.info( + self._logger.log_info_one_rank( "Micro Manager will not adaptively run micro simulations, but instead will run all micro simulations." ) @@ -253,14 +254,14 @@ def read_json_micro_manager(self): "Diagnostics data dictionary as a value other than 'scalar' or 'vector'" ) except BaseException: - self._logger.info( + self._logger.log_info_one_rank( "No diagnostics data is defined. Micro Manager will not output any diagnostics data." ) try: self._micro_output_n = self._data["diagnostics"]["micro_output_n"] except BaseException: - self._logger.info( + self._logger.log_info_one_rank( "Output interval of micro simulations not specified, if output is available then it will be called " "in every time window." ) @@ -278,7 +279,7 @@ def read_json_snapshot(self): .replace(".py", "") ) except BaseException: - self._logger.info( + self._logger.log_info_one_rank( "No post-processing file name provided. Snapshot computation will not perform any post-processing." ) self._postprocessing_file_name = None @@ -298,7 +299,7 @@ def read_json_snapshot(self): "Diagnostics data dictionary has a value other than 'scalar' or 'vector'" ) except BaseException: - self._logger.info( + self._logger.log_info_one_rank( "No diagnostics data is defined. Snapshot computation will not output any diagnostics data." ) @@ -306,7 +307,7 @@ def read_json_snapshot(self): if self._data["snapshot_params"]["initialize_once"] == "True": self._initialize_once = True except BaseException: - self._logger.info( + self._logger.log_info_one_rank( "For each snapshot a new micro simulation object will be created" ) diff --git a/micro_manager/micro_manager.py b/micro_manager/micro_manager.py index cd5a5e88..a4382289 100644 --- a/micro_manager/micro_manager.py +++ b/micro_manager/micro_manager.py @@ -29,6 +29,7 @@ from .adaptivity.local_adaptivity import LocalAdaptivityCalculator from .domain_decomposition import DomainDecomposer from .micro_simulation import create_simulation_class +from .tools.logging_wrapper import Logger try: from .interpolation import Interpolation @@ -48,6 +49,10 @@ def __init__(self, config_file: str) -> None: config_file : string Name of the JSON configuration file (provided by the user). """ + self._logger = Logger( + "MicroManagerCoupling", "micro_manager_coupling.log", 0, 1 + ) + super().__init__(config_file) self._config.read_json_micro_manager() # Define the preCICE Participant @@ -66,7 +71,7 @@ def __init__(self, config_file: str) -> None: self._interpolate_crashed_sims = self._config.interpolate_crashed_micro_sim() if self._interpolate_crashed_sims: if Interpolation is None: - self._logger.info( + self._logger.log_info_one_rank( "Interpolation is turned off as the required package is not installed." ) self._interpolate_crashed_sims = False @@ -256,7 +261,7 @@ def solve(self) -> None: ) if crash_ratio > self._crash_threshold: - self._logger.info( + self._logger.log_info_any_rank( "{:.1%} of the micro simulations have crashed exceeding the threshold of {:.1%}. " "Exiting simulation.".format(crash_ratio, self._crash_threshold) ) @@ -287,14 +292,6 @@ def solve(self) -> None: if ( self._participant.is_time_window_complete() ): # Time window has converged, now micro output can be generated - self._logger.info( - "Micro simulations {} - {} have converged at t = {}".format( - self._micro_sims[0].get_global_id(), - self._micro_sims[-1].get_global_id(), - t, - ) - ) - if self._micro_sims_have_output: if n % self._micro_n_out == 0: for sim in self._micro_sims: @@ -342,13 +339,13 @@ def initialize(self) -> None: assert self._mesh_vertex_coords.size != 0, "Macro mesh has no vertices." self._local_number_of_sims, _ = self._mesh_vertex_coords.shape - self._logger.info( + self._logger.log_info_any_rank( "Number of local micro simulations = {}".format(self._local_number_of_sims) ) if self._local_number_of_sims == 0: if self._is_parallel: - self._logger.info( + self._logger.log_info_any_rank( "Rank {} has no micro simulations and hence will not do any computation.".format( self._rank ) @@ -409,22 +406,13 @@ def initialize(self) -> None: self._global_ids_of_local_sims[i] ) - self._logger.info( - "Micro simulations with global IDs {} - {} created.".format( - self._global_ids_of_local_sims[0], self._global_ids_of_local_sims[-1] - ) - ) - if self._is_adaptivity_on: if self._adaptivity_type == "local": - self._adaptivity_controller = LocalAdaptivityCalculator( - self._config, self._logger - ) + self._adaptivity_controller = LocalAdaptivityCalculator(self._config) self._number_of_sims_for_adaptivity = self._local_number_of_sims elif self._adaptivity_type == "global": self._adaptivity_controller = GlobalAdaptivityCalculator( self._config, - self._logger, self._global_number_of_sims, self._global_ids_of_local_sims, self._rank, @@ -466,7 +454,7 @@ def initialize(self) -> None: "The initialize() method of the Micro simulation has an incorrect number of arguments." ) except TypeError: - self._logger.info( + self._logger.log_one_rank( "The signature of initialize() method of the micro simulation cannot be determined. Trying to determine the signature by calling the method." ) # Try to get the signature of the initialize() method, if it is not written in Python @@ -474,7 +462,7 @@ def initialize(self) -> None: self._micro_sims[0].initialize() is_initial_data_required = False except TypeError: - self._logger.info( + self._logger.log_one_rank( "The initialize() method of the micro simulation has arguments. Attempting to call it again with initial data." ) try: # Try to call the initialize() method with initial data @@ -670,7 +658,7 @@ def _solve_micro_simulations(self, micro_sims_input: list, dt: float) -> list: # If simulation crashes, log the error and keep the output constant at the previous iteration's output except Exception as error_message: - self._logger.error( + self._logger.log_error_any_rank( "Micro simulation at macro coordinates {} with input {} has experienced an error. " "See next entry on this rank for error message.".format( self._mesh_vertex_coords[count], micro_sims_input[count] @@ -686,7 +674,9 @@ def _solve_micro_simulations(self, micro_sims_input: list, dt: float) -> list: np.sum(self._has_sim_crashed), crashed_sims_on_all_ranks ) if sum(crashed_sims_on_all_ranks) > 0: - self._logger.info("Exiting simulation after micro simulation crash.") + self._logger.log_info_any_rank( + "Exiting simulation after micro simulation crash." + ) sys.exit() # Interpolate result for crashed simulation @@ -697,7 +687,7 @@ def _solve_micro_simulations(self, micro_sims_input: list, dt: float) -> list: # Iterate over all crashed simulations to interpolate output if self._interpolate_crashed_sims: for unset_sim in unset_sims: - self._logger.info( + self._logger.log_info_any_rank( "Interpolating output for crashed simulation at macro vertex {}.".format( self._mesh_vertex_coords[unset_sim] ) @@ -785,7 +775,7 @@ def _solve_micro_simulations_with_adaptivity( # If simulation crashes, log the error and keep the output constant at the previous iteration's output except Exception as error_message: - self._logger.error( + self._logger.log_error_any_rank( "Micro simulation at macro coordinates {} has experienced an error. " "See next entry on this rank for error message.".format( self._mesh_vertex_coords[active_id] @@ -801,7 +791,9 @@ def _solve_micro_simulations_with_adaptivity( np.sum(self._has_sim_crashed), crashed_sims_on_all_ranks ) if sum(crashed_sims_on_all_ranks) > 0: - self._logger.info("Exiting simulation after micro simulation crash.") + self._logger.log_error_any_rank( + "Exiting simulation after micro simulation crash." + ) sys.exit() # Interpolate result for crashed simulation unset_sims = [] @@ -812,7 +804,7 @@ def _solve_micro_simulations_with_adaptivity( # Iterate over all crashed simulations to interpolate output if self._interpolate_crashed_sims: for unset_sim in unset_sims: - self._logger.info( + self._logger.log_info_any_rank( "Interpolating output for crashed simulation at macro vertex {}.".format( self._mesh_vertex_coords[unset_sim] ) @@ -907,7 +899,7 @@ def _interpolate_output_for_crashed_sim( micro_sims_active_values.append(micro_sims_output[i].copy()) # Find nearest neighbors if len(micro_sims_active_input_lists) == 0: - self._logger.error( + self._logger.log_error_any_rank( "No active neighbors available for interpolation at macro vertex {}. Value cannot be interpolated".format( self._mesh_vertex_coords[unset_sim] ) diff --git a/micro_manager/micro_manager_base.py b/micro_manager/micro_manager_base.py index 9e8d54b4..b9c3f78b 100644 --- a/micro_manager/micro_manager_base.py +++ b/micro_manager/micro_manager_base.py @@ -8,7 +8,6 @@ """ from mpi4py import MPI -import logging from abc import ABC, abstractmethod from .config import Config @@ -52,20 +51,6 @@ def __init__(self, config_file): self._rank = self._comm.Get_rank() self._size = self._comm.Get_size() - self._logger = logging.getLogger(__name__) - self._logger.setLevel(level=logging.INFO) - - # Create file handler which logs messages - fh = logging.FileHandler("micro-manager.log") - fh.setLevel(logging.INFO) - - # Create formatter and add it to handlers - formatter = logging.Formatter( - "[" + str(self._rank) + "] %(name)s - %(levelname)s - %(message)s" - ) - fh.setFormatter(formatter) - self._logger.addHandler(fh) # add the handlers to the logger - self._is_parallel = self._size > 1 self._micro_sims_have_output = False @@ -73,8 +58,7 @@ def __init__(self, config_file): self._global_number_of_sims = 0 self._is_rank_empty = False - self._logger.info("Provided configuration file: {}".format(config_file)) - self._config = Config(self._logger, config_file) + self._config = Config(config_file) # Data names of data to output to the snapshot database self._write_data_names = self._config.get_write_data_names() diff --git a/micro_manager/tools/logging_wrapper.py b/micro_manager/tools/logging_wrapper.py new file mode 100644 index 00000000..f77817b3 --- /dev/null +++ b/micro_manager/tools/logging_wrapper.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 +""" +Provides a logging wrapper for the Micro Manager classes. +""" +import logging + + +class Logger: + """ + Provides a logging wrapper for the Micro Manager classes. + """ + + def __init__(self, name, log_file, rank=0, level=logging.INFO): + """ + Set up a logger. + + Parameters + ---------- + name : string + Name of the logger. + log_file : string + Name of the log file. + level : int, optional + Logging level (default is logging.INFO). + """ + + self._rank = rank + + handler = logging.FileHandler(log_file) + handler.setLevel(level) + + formatter = logging.Formatter( + "%(asctime)s - %(name)s - %(levelname)s - %(message)s" + ) + handler.setFormatter(formatter) + + logger = logging.getLogger(name) + logger.setLevel(level) + logger.addHandler(handler) + + return logger + + def log_info_one_rank(self, logger, message): + """ + Log a message. Only the rank 0 logs the message. + + Parameters + ---------- + logger : Logger + Logger object. + message : string + Message to log. + """ + if self._rank == 0: + logger.info(message) + + def log_info_any_rank(self, logger, message): + """ + Log a message. All ranks log the message. + + Parameters + ---------- + logger : Logger + Logger object. + message : string + Message to log. + """ + logger.info(message) + + def log_error_any_rank(self, logger, message): + """ + Log an error message. Only the rank 0 logs the message. + + Parameters + ---------- + logger : Logger + Logger object. + message : string + Message to log. + """ + logger.error(message) From 5fcf75b35fe27f6084cfc3c0fc604254c1571ef8 Mon Sep 17 00:00:00 2001 From: Ishaan Desai Date: Thu, 7 Nov 2024 13:29:11 +0100 Subject: [PATCH 02/17] Use new logging in all parts of the codebase --- micro_manager/adaptivity/local_adaptivity.py | 4 +-- micro_manager/config.py | 8 ++--- micro_manager/domain_decomposition.py | 2 +- micro_manager/interpolation.py | 2 +- micro_manager/micro_manager.py | 5 +-- micro_manager/snapshot/snapshot.py | 28 +++++++++++----- micro_manager/tools/__init__.py | 0 micro_manager/tools/logging_wrapper.py | 35 +++++++++++--------- pyproject.toml | 2 +- 9 files changed, 50 insertions(+), 36 deletions(-) create mode 100644 micro_manager/tools/__init__.py diff --git a/micro_manager/adaptivity/local_adaptivity.py b/micro_manager/adaptivity/local_adaptivity.py index 54f5eb78..2acc87bb 100644 --- a/micro_manager/adaptivity/local_adaptivity.py +++ b/micro_manager/adaptivity/local_adaptivity.py @@ -9,7 +9,7 @@ class LocalAdaptivityCalculator(AdaptivityCalculator): - def __init__(self, configurator, logger) -> None: + def __init__(self, configurator) -> None: """ Class constructor. @@ -20,7 +20,7 @@ def __init__(self, configurator, logger) -> None: logger : object of logging Logger defined from the standard package logging """ - super().__init__(configurator, logger) + super().__init__(configurator) def compute_adaptivity( self, diff --git a/micro_manager/config.py b/micro_manager/config.py index 11884db9..af771d40 100644 --- a/micro_manager/config.py +++ b/micro_manager/config.py @@ -23,7 +23,7 @@ def __init__(self, config_filename): config_filename : string Name of the JSON configuration file """ - self._logger = Logger("Config", "micro_manager_config.log", 0, 1) + self._logger = Logger("Config", "micro_manager_config.log", 0) self._micro_file_name = None @@ -148,7 +148,7 @@ def read_json_micro_manager(self): try: self._ranks_per_axis = self._data["simulation_params"]["decomposition"] except BaseException: - self._logger.info( + self._logger.log_info_one_rank( "Domain decomposition is not specified, so the Micro Manager will expect to be run in serial." ) @@ -213,7 +213,7 @@ def read_json_micro_manager(self): "adaptivity_settings" ]["similarity_measure"] else: - self._logger.info( + self._logger.log_info_one_rank( "No similarity measure provided, using L1 norm as default" ) self._adaptivity_similarity_measure = "L1" @@ -228,7 +228,7 @@ def read_json_micro_manager(self): self._adaptivity_every_implicit_iteration = False if not self._adaptivity_every_implicit_iteration: - self._logger.info( + self._logger.log_info_one_rank( "Micro Manager will compute adaptivity once at the start of every time window" ) diff --git a/micro_manager/domain_decomposition.py b/micro_manager/domain_decomposition.py index cfc7cbe7..573eac07 100644 --- a/micro_manager/domain_decomposition.py +++ b/micro_manager/domain_decomposition.py @@ -98,6 +98,6 @@ def decompose_macro_domain(self, macro_bounds: list, ranks_per_axis: list) -> li if rank_in_axis[d] + 1 == ranks_per_axis[d]: mesh_bounds[d * 2 + 1] = macro_bounds[d * 2 + 1] - self._logger.info("Bounding box limits are {}".format(mesh_bounds)) + self._logger.log_info_any_rank("Bounding box limits are {}".format(mesh_bounds)) return mesh_bounds diff --git a/micro_manager/interpolation.py b/micro_manager/interpolation.py index 9fa08d62..092e32ec 100644 --- a/micro_manager/interpolation.py +++ b/micro_manager/interpolation.py @@ -31,7 +31,7 @@ def get_nearest_neighbor_indices( Local indices of the k nearest neighbors in all local points. """ if len(coords) < k: - self._logger.info( + self._logger.log_info_any_rank( "Number of desired neighbors k = {} is larger than the number of available neighbors {}. Resetting k = {}.".format( k, len(coords), len(coords) ) diff --git a/micro_manager/micro_manager.py b/micro_manager/micro_manager.py index a4382289..b595992c 100644 --- a/micro_manager/micro_manager.py +++ b/micro_manager/micro_manager.py @@ -49,11 +49,12 @@ def __init__(self, config_file: str) -> None: config_file : string Name of the JSON configuration file (provided by the user). """ + super().__init__(config_file) + self._logger = Logger( - "MicroManagerCoupling", "micro_manager_coupling.log", 0, 1 + "MicroManagerCoupling", "micro_manager_coupling.log", self._rank ) - super().__init__(config_file) self._config.read_json_micro_manager() # Define the preCICE Participant self._participant = precice.Participant( diff --git a/micro_manager/snapshot/snapshot.py b/micro_manager/snapshot/snapshot.py index e94456d0..a0a068b7 100644 --- a/micro_manager/snapshot/snapshot.py +++ b/micro_manager/snapshot/snapshot.py @@ -19,6 +19,7 @@ from micro_manager.micro_manager import MicroManager from .dataset import ReadWriteHDF from micro_manager.micro_simulation import create_simulation_class +from micro_manager.tools.logging_wrapper import Logger sys.path.append(os.getcwd()) @@ -34,6 +35,11 @@ def __init__(self, config_file: str) -> None: Name of the JSON configuration file (provided by the user). """ super().__init__(config_file) + + self._logger = Logger( + "MicroManagerSnapshot", "micro_manager_snapshot.log", self._rank + ) + self._config.read_json_snapshot() # Path to the parameter file containing input parameters for micro simulations @@ -95,12 +101,12 @@ def solve(self) -> None: micro_sims_output ) else: - self._logger.info( + self._logger.log_info_one_rank( "No post-processing script with the provided path found. Skipping post-processing." ) self._post_processing_file_name = None except Exception: - self._logger.info( + self._logger.log_info_one_rank( "No post-processing script with the provided path found. Skipping post-processing." ) self._post_processing_file_name = None @@ -113,7 +119,9 @@ def solve(self) -> None: ) # Log error and write macro data to database if simulation has crashed else: - self._logger.info("Skipping snapshot storage for crashed simulation.") + self._logger.log_info_one_rank( + "Skipping snapshot storage for crashed simulation." + ) self._data_storage.write_output_to_hdf( self._output_file_path, micro_sims_input, @@ -132,7 +140,7 @@ def solve(self) -> None: # Merge output files if self._is_parallel: - self._logger.info( + self._logger.log_info_any_rank( "Snapshots have been computed and stored. Merging output files" ) self._data_storage.set_status(self._output_file_path, "reading/deleting") @@ -146,7 +154,7 @@ def solve(self) -> None: else: self._data_storage.set_status(self._output_file_path, "finished") if self._rank == 0: - self._logger.info("Snapshot computation completed.") + self._logger.log_info_one_rank("Snapshot computation completed.") def initialize(self) -> None: """ @@ -199,15 +207,17 @@ def initialize(self) -> None: self._output_subdirectory, self._file_name ) self._data_storage.create_file(self._output_file_path) - self._logger.info("Output file created: {}".format(self._output_file_path)) + self._logger.log_error_any_rank( + "Output file created: {}".format(self._output_file_path) + ) self._local_number_of_sims = len(self._macro_parameters) - self._logger.info( + self._logger.log_info_any_rank( "Number of local micro simulations = {}".format(self._local_number_of_sims) ) if self._local_number_of_sims == 0: if self._is_parallel: - self._logger.info( + self._logger.log_info_any_rank( "Rank {} has no micro simulations and hence will not do any computation.".format( self._rank ) @@ -275,7 +285,7 @@ def _solve_micro_simulation(self, micro_sims_input: dict) -> dict | None: return micro_sims_output # Handle simulation crash except Exception as e: - self._logger.error( + self._logger.log_error_any_rank( "Micro simulation with input {} has crashed. See next entry on this rank for error message".format( micro_sims_input ) diff --git a/micro_manager/tools/__init__.py b/micro_manager/tools/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/micro_manager/tools/logging_wrapper.py b/micro_manager/tools/logging_wrapper.py index f77817b3..a38c32e1 100644 --- a/micro_manager/tools/logging_wrapper.py +++ b/micro_manager/tools/logging_wrapper.py @@ -34,48 +34,51 @@ def __init__(self, name, log_file, rank=0, level=logging.INFO): ) handler.setFormatter(formatter) - logger = logging.getLogger(name) - logger.setLevel(level) - logger.addHandler(handler) + self._logger = logging.getLogger(name) + self._logger.setLevel(level) + self._logger.addHandler(handler) - return logger + def get_logger(self): + """ + Get the logger. + + Returns + ------- + logger : object of logging + Logger defined from the standard package logging + """ + return self._logger - def log_info_one_rank(self, logger, message): + def log_info_one_rank(self, message): """ Log a message. Only the rank 0 logs the message. Parameters ---------- - logger : Logger - Logger object. message : string Message to log. """ if self._rank == 0: - logger.info(message) + self._logger.info(message) - def log_info_any_rank(self, logger, message): + def log_info_any_rank(self, message): """ Log a message. All ranks log the message. Parameters ---------- - logger : Logger - Logger object. message : string Message to log. """ - logger.info(message) + self._logger.info(message) - def log_error_any_rank(self, logger, message): + def log_error_any_rank(self, message): """ Log an error message. Only the rank 0 logs the message. Parameters ---------- - logger : Logger - Logger object. message : string Message to log. """ - logger.error(message) + self._logger.error("[" + str(self._rank) + "] " + message) diff --git a/pyproject.toml b/pyproject.toml index cee97d2a..5ed18012 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,7 @@ Repository = "https://github.com/precice/micro-manager" micro-manager-precice = "micro_manager:main" [tool.setuptools] -packages=["micro_manager", "micro_manager.adaptivity", "micro_manager.snapshot"] +packages=["micro_manager", "micro_manager.adaptivity", "micro_manager.snapshot", "micro_manager.tools"] [tool.setuptools-git-versioning] enabled = true From 10f434b776e1aff0c34efbe19d78d80d1edbae16 Mon Sep 17 00:00:00 2001 From: Ishaan Desai Date: Thu, 7 Nov 2024 14:45:40 +0100 Subject: [PATCH 03/17] Fix tests and add CHANGELOG entry --- CHANGELOG.md | 1 + micro_manager/micro_manager.py | 4 ++-- micro_manager/tools/logging_wrapper.py | 6 ++++-- tests/unit/test_adaptivity_parallel.py | 6 +++--- tests/unit/test_adaptivity_serial.py | 13 +++++-------- tests/unit/test_micro_manager.py | 2 +- tests/unit/test_snapshot_computation.py | 2 +- 7 files changed, 17 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd8133c5..4c3e31be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## latest +- Improve logging by wrapping Python logger in a class https://github.com/precice/micro-manager/pull/133 - Add information about adaptivity tuning parameters https://github.com/precice/micro-manager/pull/131 - Put computation of counting active steps inside the adaptivity variant `if` condition https://github.com/precice/micro-manager/pull/130 diff --git a/micro_manager/micro_manager.py b/micro_manager/micro_manager.py index b595992c..8d212838 100644 --- a/micro_manager/micro_manager.py +++ b/micro_manager/micro_manager.py @@ -665,7 +665,7 @@ def _solve_micro_simulations(self, micro_sims_input: list, dt: float) -> list: self._mesh_vertex_coords[count], micro_sims_input[count] ) ) - self._logger.error(error_message) + self._logger.log_error_any_rank(error_message) self._has_sim_crashed[count] = True # If interpolate is off, terminate after crash @@ -782,7 +782,7 @@ def _solve_micro_simulations_with_adaptivity( self._mesh_vertex_coords[active_id] ) ) - self._logger.error(error_message) + self._logger.log_error_any_rank(error_message) self._has_sim_crashed[active_id] = True # If interpolate is off, terminate after crash diff --git a/micro_manager/tools/logging_wrapper.py b/micro_manager/tools/logging_wrapper.py index a38c32e1..72c44aa7 100644 --- a/micro_manager/tools/logging_wrapper.py +++ b/micro_manager/tools/logging_wrapper.py @@ -30,7 +30,9 @@ def __init__(self, name, log_file, rank=0, level=logging.INFO): handler.setLevel(level) formatter = logging.Formatter( - "%(asctime)s - %(name)s - %(levelname)s - %(message)s" + "[" + + str(self._rank) + + "] %(asctime)s - %(name)s - %(levelname)s - %(message)s" ) handler.setFormatter(formatter) @@ -81,4 +83,4 @@ def log_error_any_rank(self, message): message : string Message to log. """ - self._logger.error("[" + str(self._rank) + "] " + message) + self._logger.error(message) diff --git a/tests/unit/test_adaptivity_parallel.py b/tests/unit/test_adaptivity_parallel.py index b07ee923..7d5c3156 100644 --- a/tests/unit/test_adaptivity_parallel.py +++ b/tests/unit/test_adaptivity_parallel.py @@ -31,7 +31,7 @@ def test_update_inactive_sims_global_adaptivity(self): configurator = MagicMock() configurator.get_adaptivity_similarity_measure = MagicMock(return_value="L1") adaptivity_controller = GlobalAdaptivityCalculator( - configurator, MagicMock(), 5, global_ids, rank=self._rank, comm=self._comm + configurator, 5, global_ids, rank=self._rank, comm=self._comm ) # Force the activation of sim #0 and #4 @@ -105,7 +105,7 @@ def test_update_all_active_sims_global_adaptivity(self): configurator.get_adaptivity_coarsening_const = MagicMock(return_value=0.2) configurator.get_adaptivity_similarity_measure = MagicMock(return_value="L2rel") adaptivity_controller = GlobalAdaptivityCalculator( - configurator, MagicMock(), 5, global_ids, rank=self._rank, comm=self._comm + configurator, 5, global_ids, rank=self._rank, comm=self._comm ) adaptivity_controller._adaptivity_data_names = { @@ -173,7 +173,7 @@ def test_communicate_micro_output(self): configurator = MagicMock() configurator.get_adaptivity_similarity_measure = MagicMock(return_value="L1") adaptivity_controller = GlobalAdaptivityCalculator( - configurator, MagicMock(), 5, global_ids, rank=self._rank, comm=self._comm + configurator, 5, global_ids, rank=self._rank, comm=self._comm ) adaptivity_controller.communicate_micro_output( diff --git a/tests/unit/test_adaptivity_serial.py b/tests/unit/test_adaptivity_serial.py index 13155351..72349f39 100644 --- a/tests/unit/test_adaptivity_serial.py +++ b/tests/unit/test_adaptivity_serial.py @@ -68,7 +68,7 @@ def test_get_similarity_dists(self): """ configurator = MagicMock() configurator.get_adaptivity_similarity_measure = MagicMock(return_value="L1") - adaptivity_controller = AdaptivityCalculator(configurator, logger=MagicMock()) + adaptivity_controller = AdaptivityCalculator(configurator) adaptivity_controller._hist_param = 0.5 adaptivity_controller._adaptivity_data_names = [ "micro-scalar-data", @@ -102,7 +102,7 @@ def test_update_active_sims(self): """ configurator = MagicMock() configurator.get_adaptivity_similarity_measure = MagicMock(return_value="L1") - adaptivity_controller = AdaptivityCalculator(configurator, logger=MagicMock()) + adaptivity_controller = AdaptivityCalculator(configurator) adaptivity_controller._refine_const = self._refine_const adaptivity_controller._coarse_const = self._coarse_const adaptivity_controller._adaptivity_data_names = [ @@ -127,8 +127,7 @@ def test_adaptivity_norms(self): """ Test functionality for calculating similarity criteria between pairs of simulations using different norms in class AdaptivityCalculator. """ - logger = MagicMock() - calc = AdaptivityCalculator(Config(logger, "micro-manager-config.json"), logger) + calc = AdaptivityCalculator(Config("micro-manager-config.json")) fake_data = np.array([[1], [2], [3]]) self.assertTrue( @@ -209,7 +208,7 @@ def test_associate_active_to_inactive(self): """ configurator = MagicMock() configurator.get_adaptivity_similarity_measure = MagicMock(return_value="L1") - adaptivity_controller = AdaptivityCalculator(configurator, logger=MagicMock()) + adaptivity_controller = AdaptivityCalculator(configurator) adaptivity_controller._refine_const = self._refine_const adaptivity_controller._coarse_const = self._coarse_const adaptivity_controller._adaptivity_data_names = [ @@ -236,9 +235,7 @@ def test_update_inactive_sims_local_adaptivity(self): """ configurator = MagicMock() configurator.get_adaptivity_similarity_measure = MagicMock(return_value="L1") - adaptivity_controller = LocalAdaptivityCalculator( - configurator, logger=MagicMock() - ) + adaptivity_controller = LocalAdaptivityCalculator(configurator) adaptivity_controller._refine_const = self._refine_const adaptivity_controller._coarse_const = self._coarse_const adaptivity_controller._adaptivity_data_names = [ diff --git a/tests/unit/test_micro_manager.py b/tests/unit/test_micro_manager.py index b43850cd..b733069f 100644 --- a/tests/unit/test_micro_manager.py +++ b/tests/unit/test_micro_manager.py @@ -119,7 +119,7 @@ def test_config(self): """ Test if the functions in the Config class work. """ - config = micro_manager.Config(MagicMock(), "micro-manager-config.json") + config = micro_manager.Config("micro-manager-config.json") config.read_json_micro_manager() self.assertEqual(config._config_file_name.split("/")[-1], "dummy-config.xml") self.assertEqual(config._micro_file_name, "test_micro_manager") diff --git a/tests/unit/test_snapshot_computation.py b/tests/unit/test_snapshot_computation.py index 68bde45e..c62bef24 100644 --- a/tests/unit/test_snapshot_computation.py +++ b/tests/unit/test_snapshot_computation.py @@ -136,7 +136,7 @@ def test_config(self): """ Test if the functions in the SnapshotConfig class work. """ - config = Config(MagicMock(), "snapshot-config.json") + config = Config("snapshot-config.json") config.read_json_snapshot() self.assertEqual( From 60d8607e15f7670d8d75bbc61f17d09ae465eb75 Mon Sep 17 00:00:00 2001 From: Ishaan Desai Date: Tue, 12 Nov 2024 18:04:11 +0100 Subject: [PATCH 04/17] Add a separate logger for adaptivity --- micro_manager/adaptivity/global_adaptivity.py | 12 --------- micro_manager/adaptivity/local_adaptivity.py | 7 ------ micro_manager/micro_manager.py | 25 +++++++++++++++++++ 3 files changed, 25 insertions(+), 19 deletions(-) diff --git a/micro_manager/adaptivity/global_adaptivity.py b/micro_manager/adaptivity/global_adaptivity.py index 0e30c7a3..83f19a80 100644 --- a/micro_manager/adaptivity/global_adaptivity.py +++ b/micro_manager/adaptivity/global_adaptivity.py @@ -126,18 +126,6 @@ def compute_adaptivity( similarity_dists, is_sim_active, sim_is_associated_to ) - # self._logger.info( - # "{} active simulations, {} inactive simulations".format( - # np.count_nonzero( - # is_sim_active[self._global_ids[0] : self._global_ids[-1] + 1] - # ), - # np.count_nonzero( - # is_sim_active[self._global_ids[0] : self._global_ids[-1] + 1] - # == False - # ), - # ) - # ) - return similarity_dists, is_sim_active, sim_is_associated_to def communicate_micro_output( diff --git a/micro_manager/adaptivity/local_adaptivity.py b/micro_manager/adaptivity/local_adaptivity.py index 2acc87bb..acb32600 100644 --- a/micro_manager/adaptivity/local_adaptivity.py +++ b/micro_manager/adaptivity/local_adaptivity.py @@ -80,13 +80,6 @@ def compute_adaptivity( similarity_dists, is_sim_active, sim_is_associated_to ) - # self._logger.info( - # "{} active simulations, {} inactive simulations".format( - # np.count_nonzero(is_sim_active), - # np.count_nonzero(is_sim_active == False), - # ) - # ) - return similarity_dists, is_sim_active, sim_is_associated_to def _update_inactive_sims( diff --git a/micro_manager/micro_manager.py b/micro_manager/micro_manager.py index 8d212838..1d4ae501 100644 --- a/micro_manager/micro_manager.py +++ b/micro_manager/micro_manager.py @@ -87,6 +87,14 @@ def __init__(self, config_file: str) -> None: self._is_adaptivity_on = self._config.turn_on_adaptivity() if self._is_adaptivity_on: + self._adaptivity_logger = Logger( + "Adaptivity", "micro-manager-adaptivity.log", self._rank + ) + + self._adaptivity_logger.log_info_one_rank( + "Time,Avg Active Sims,Avg Inactive Sims" + ) + self._number_of_sims_for_adaptivity = 0 self._data_for_adaptivity: Dict[str, np.ndarray] = dict() @@ -298,6 +306,23 @@ def solve(self) -> None: for sim in self._micro_sims: sim.output() + if self._is_adaptivity_on: + local_active_sims = np.where(is_sim_active)[0] + global_active_sims = self._comm.gather(local_active_sims) + + local_inactive_sims = np.where(is_sim_active == False)[0] + global_inactive_sims = self._comm.gather( + local_inactive_sims + ) + + self._adaptivity_logger.log_info_one_rank( + "{},{},{}".format( + t, + np.mean(global_active_sims), + np.mean(global_inactive_sims), + ) + ) + self._participant.finalize() def initialize(self) -> None: From a2845e41962f5e624cd84629b77b92bb80e686d9 Mon Sep 17 00:00:00 2001 From: Ishaan Desai Date: Mon, 2 Dec 2024 17:45:39 +0100 Subject: [PATCH 05/17] Add CSV logging --- micro_manager/micro_manager.py | 20 ++++++++++++-------- micro_manager/tools/logging_wrapper.py | 20 ++++++++++++++------ 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/micro_manager/micro_manager.py b/micro_manager/micro_manager.py index 1d4ae501..84576151 100644 --- a/micro_manager/micro_manager.py +++ b/micro_manager/micro_manager.py @@ -88,11 +88,11 @@ def __init__(self, config_file: str) -> None: if self._is_adaptivity_on: self._adaptivity_logger = Logger( - "Adaptivity", "micro-manager-adaptivity.log", self._rank + "Adaptivity", "adaptivity_metrics.csv", self._rank, csv_logger=True ) self._adaptivity_logger.log_info_one_rank( - "Time,Avg Active Sims,Avg Inactive Sims" + "Time,Avg Active Sims,Avg Inactive Sims,Max Active,Max Inactive" ) self._number_of_sims_for_adaptivity = 0 @@ -316,12 +316,15 @@ def solve(self) -> None: ) self._adaptivity_logger.log_info_one_rank( - "{},{},{}".format( + "{},{},{},{},{}".format( t, np.mean(global_active_sims), np.mean(global_inactive_sims), + np.max(global_active_sims), + np.max(global_inactive_sims), ) ) + self._logger.log_info_one_rank("Time window {} converged.".format(n)) self._participant.finalize() @@ -365,9 +368,6 @@ def initialize(self) -> None: assert self._mesh_vertex_coords.size != 0, "Macro mesh has no vertices." self._local_number_of_sims, _ = self._mesh_vertex_coords.shape - self._logger.log_info_any_rank( - "Number of local micro simulations = {}".format(self._local_number_of_sims) - ) if self._local_number_of_sims == 0: if self._is_parallel: @@ -389,6 +389,10 @@ def initialize(self) -> None: # Get global number of micro simulations self._global_number_of_sims = np.sum(nms_all_ranks) + self._logger.log_info_one_rank( + "Number of micro simulations: {}".format(self._global_number_of_sims) + ) + if self._is_adaptivity_on: for name, is_data_vector in self._adaptivity_data_names.items(): if is_data_vector: @@ -480,7 +484,7 @@ def initialize(self) -> None: "The initialize() method of the Micro simulation has an incorrect number of arguments." ) except TypeError: - self._logger.log_one_rank( + self._logger.log_info_one_rank( "The signature of initialize() method of the micro simulation cannot be determined. Trying to determine the signature by calling the method." ) # Try to get the signature of the initialize() method, if it is not written in Python @@ -488,7 +492,7 @@ def initialize(self) -> None: self._micro_sims[0].initialize() is_initial_data_required = False except TypeError: - self._logger.log_one_rank( + self._logger.log_info_one_rank( "The initialize() method of the micro simulation has arguments. Attempting to call it again with initial data." ) try: # Try to call the initialize() method with initial data diff --git a/micro_manager/tools/logging_wrapper.py b/micro_manager/tools/logging_wrapper.py index 72c44aa7..8ef7c6be 100644 --- a/micro_manager/tools/logging_wrapper.py +++ b/micro_manager/tools/logging_wrapper.py @@ -10,7 +10,7 @@ class Logger: Provides a logging wrapper for the Micro Manager classes. """ - def __init__(self, name, log_file, rank=0, level=logging.INFO): + def __init__(self, name, log_file, rank=0, level=logging.INFO, csv_logger=False): """ Set up a logger. @@ -20,8 +20,12 @@ def __init__(self, name, log_file, rank=0, level=logging.INFO): Name of the logger. log_file : string Name of the log file. + rank : int, optional + Rank of the logger (default is 0). level : int, optional Logging level (default is logging.INFO). + csv_logger : bool, optional + If True, the logger will log in CSV format (default is False). """ self._rank = rank @@ -29,11 +33,15 @@ def __init__(self, name, log_file, rank=0, level=logging.INFO): handler = logging.FileHandler(log_file) handler.setLevel(level) - formatter = logging.Formatter( - "[" - + str(self._rank) - + "] %(asctime)s - %(name)s - %(levelname)s - %(message)s" - ) + if csv_logger: + formatter = logging.Formatter("%(message)s") + else: + formatter = logging.Formatter( + "[" + + str(self._rank) + + "] %(asctime)s - %(name)s - %(levelname)s - %(message)s" + ) + handler.setFormatter(formatter) self._logger = logging.getLogger(name) From 907aac1835a3e6d1a453d532e486dfeac4d683c8 Mon Sep 17 00:00:00 2001 From: Ishaan Desai Date: Tue, 3 Dec 2024 14:24:12 +0100 Subject: [PATCH 06/17] Adding more logging points to have more information during a coupled simulation --- micro_manager/config.py | 14 ++++-- micro_manager/micro_manager.py | 49 +++++++++++-------- .../integration/test_unit_cube/clean-test.sh | 1 + 3 files changed, 40 insertions(+), 24 deletions(-) diff --git a/micro_manager/config.py b/micro_manager/config.py index af771d40..71855cbc 100644 --- a/micro_manager/config.py +++ b/micro_manager/config.py @@ -5,7 +5,6 @@ import json import os from warnings import warn -from .tools.logging_wrapper import Logger class Config: @@ -23,8 +22,6 @@ def __init__(self, config_filename): config_filename : string Name of the JSON configuration file """ - self._logger = Logger("Config", "micro_manager_config.log", 0) - self._micro_file_name = None self._config_file_name = None @@ -60,6 +57,17 @@ def __init__(self, config_filename): self.read_json(config_filename) + def set_logger(self, logger): + """ + Set the logger for the Config class. + + Parameters + ---------- + logger : object of logging + Logger defined from the standard package logging + """ + self._logger = logger + def read_json(self, config_filename): """ Reads JSON configuration file. diff --git a/micro_manager/micro_manager.py b/micro_manager/micro_manager.py index 84576151..5cb75212 100644 --- a/micro_manager/micro_manager.py +++ b/micro_manager/micro_manager.py @@ -51,11 +51,11 @@ def __init__(self, config_file: str) -> None: """ super().__init__(config_file) - self._logger = Logger( - "MicroManagerCoupling", "micro_manager_coupling.log", self._rank - ) + self._logger = Logger("MicroManagerCoupling", "micro-manager.log", self._rank) + self._config.set_logger(self._logger) self._config.read_json_micro_manager() + # Define the preCICE Participant self._participant = precice.Participant( "Micro-Manager", self._config.get_config_file_name(), self._rank, self._size @@ -104,6 +104,7 @@ def __init__(self, config_file: str) -> None: # Names of macro data to be used for adaptivity computation self._adaptivity_macro_data_names = dict() + # Names of micro data to be used for adaptivity computation self._adaptivity_micro_data_names = dict() for name, is_data_vector in self._adaptivity_data_names.items(): @@ -155,6 +156,9 @@ def solve(self) -> None: # If micro simulations have been initialized, compute adaptivity before starting the coupling if self._micro_sims_init: + self._logger.log_info_one_rank( + "Micro simulations have been initialized, so adaptivity will be computed before the coupling begins." + ) ( similarity_dists, is_sim_active, @@ -306,24 +310,27 @@ def solve(self) -> None: for sim in self._micro_sims: sim.output() - if self._is_adaptivity_on: - local_active_sims = np.where(is_sim_active)[0] - global_active_sims = self._comm.gather(local_active_sims) - - local_inactive_sims = np.where(is_sim_active == False)[0] - global_inactive_sims = self._comm.gather( - local_inactive_sims - ) + if self._is_adaptivity_on: + if self._adaptivity_type == "local": + # Gather is necessary as local adaptivity only stores local data + local_active_sims = np.count_nonzero(is_sim_active) + global_active_sims = self._comm.gather(local_active_sims) - self._adaptivity_logger.log_info_one_rank( - "{},{},{},{},{}".format( - t, - np.mean(global_active_sims), - np.mean(global_inactive_sims), - np.max(global_active_sims), - np.max(global_inactive_sims), - ) - ) + local_inactive_sims = np.count_nonzero(is_sim_active == False) + global_inactive_sims = self._comm.gather(local_inactive_sims) + elif self._adaptivity_type == "global": + global_active_sims = np.count_nonzero(is_sim_active) + global_inactive_sims = np.count_nonzero(is_sim_active == False) + + self._adaptivity_logger.log_info_one_rank( + "{},{},{},{},{}".format( + t, + np.mean(global_active_sims), + np.mean(global_inactive_sims), + np.max(global_active_sims), + np.max(global_inactive_sims), + ) + ) self._logger.log_info_one_rank("Time window {} converged.".format(n)) self._participant.finalize() @@ -390,7 +397,7 @@ def initialize(self) -> None: self._global_number_of_sims = np.sum(nms_all_ranks) self._logger.log_info_one_rank( - "Number of micro simulations: {}".format(self._global_number_of_sims) + "Total number of micro simulations: {}".format(self._global_number_of_sims) ) if self._is_adaptivity_on: diff --git a/tests/integration/test_unit_cube/clean-test.sh b/tests/integration/test_unit_cube/clean-test.sh index 8af464d8..60aa7a13 100755 --- a/tests/integration/test_unit_cube/clean-test.sh +++ b/tests/integration/test_unit_cube/clean-test.sh @@ -9,3 +9,4 @@ rm -fv *.err rm -fv output/*.vtu rm -fv output/*.pvtu rm -r -fv __pycache__ +rm -fv *.csv From 098ef87f4eba902ab9750cea9700f1eff75d4234 Mon Sep 17 00:00:00 2001 From: Ishaan Desai Date: Tue, 3 Dec 2024 15:07:11 +0100 Subject: [PATCH 07/17] Correctly defining _logger variable in the Config class --- micro_manager/config.py | 1 + micro_manager/micro_manager.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/micro_manager/config.py b/micro_manager/config.py index 71855cbc..e9ffa7a1 100644 --- a/micro_manager/config.py +++ b/micro_manager/config.py @@ -22,6 +22,7 @@ def __init__(self, config_filename): config_filename : string Name of the JSON configuration file """ + self._logger = None self._micro_file_name = None self._config_file_name = None diff --git a/micro_manager/micro_manager.py b/micro_manager/micro_manager.py index 5cb75212..5c400933 100644 --- a/micro_manager/micro_manager.py +++ b/micro_manager/micro_manager.py @@ -88,7 +88,7 @@ def __init__(self, config_file: str) -> None: if self._is_adaptivity_on: self._adaptivity_logger = Logger( - "Adaptivity", "adaptivity_metrics.csv", self._rank, csv_logger=True + "Adaptivity", "adaptivity-metrics.csv", self._rank, csv_logger=True ) self._adaptivity_logger.log_info_one_rank( @@ -312,7 +312,7 @@ def solve(self) -> None: if self._is_adaptivity_on: if self._adaptivity_type == "local": - # Gather is necessary as local adaptivity only stores local data + # MPI Gather is necessary as local adaptivity only stores local data local_active_sims = np.count_nonzero(is_sim_active) global_active_sims = self._comm.gather(local_active_sims) From 41e5523f2baaf4edd6c9df5146a18be31a1bb189 Mon Sep 17 00:00:00 2001 From: Ishaan Desai Date: Tue, 3 Dec 2024 18:18:48 +0100 Subject: [PATCH 08/17] Reformulate the way loggers are created and passed into functionality --- micro_manager/config.py | 36 +++++++++++-------- micro_manager/micro_manager.py | 26 +++++++++++--- micro_manager/micro_manager_base.py | 10 ------ micro_manager/snapshot/snapshot.py | 14 +++++++- ...ger-config-global-adaptivity-parallel.json | 2 +- ...icro-manager-config-global-adaptivity.json | 2 +- ...micro-manager-config-local-adaptivity.json | 2 +- .../micro-manager-config-parallel-1.json | 2 +- .../micro-manager-config-parallel-2.json | 2 +- tests/unit/micro-manager-config.json | 2 +- tests/unit/micro-manager-config_crash.json | 2 +- tests/unit/test_micro_manager.py | 5 ++- tests/unit/test_snapshot_computation.py | 1 + 13 files changed, 67 insertions(+), 39 deletions(-) diff --git a/micro_manager/config.py b/micro_manager/config.py index e9ffa7a1..98a96937 100644 --- a/micro_manager/config.py +++ b/micro_manager/config.py @@ -13,19 +13,20 @@ class Config: the config class in https://github.com/precice/fenics-adapter/tree/develop/fenicsadapter """ - def __init__(self, config_filename): + def __init__(self, config_file_name): """ Constructor of the Config class. Parameters ---------- - config_filename : string + config_file_name : string Name of the JSON configuration file """ + self._config_file_name = config_file_name self._logger = None self._micro_file_name = None - self._config_file_name = None + self._precice_config_file_name = None self._macro_mesh_name = None self._read_data_names = dict() self._write_data_names = dict() @@ -56,8 +57,6 @@ def __init__(self, config_filename): self._output_micro_sim_time = False - self.read_json(config_filename) - def set_logger(self, logger): """ Set the logger for the Config class. @@ -69,17 +68,17 @@ def set_logger(self, logger): """ self._logger = logger - def read_json(self, config_filename): + def _read_json(self, config_file_name): """ Reads JSON configuration file. Parameters ---------- - config_filename : string + config_file_name : string Name of the JSON configuration file """ - self._folder = os.path.dirname(os.path.join(os.getcwd(), config_filename)) - path = os.path.join(self._folder, os.path.basename(config_filename)) + self._folder = os.path.dirname(os.path.join(os.getcwd(), config_file_name)) + path = os.path.join(self._folder, os.path.basename(config_file_name)) with open(path, "r") as read_file: self._data = json.load(read_file) @@ -145,8 +144,10 @@ def read_json_micro_manager(self): Reads Micro Manager relevant information from JSON configuration file and saves the data to the respective instance attributes. """ - self._config_file_name = os.path.join( - self._folder, self._data["coupling_params"]["config_file_name"] + self._read_json(self._config_file_name) # Read base information + + self._precice_config_file_name = os.path.join( + self._folder, self._data["coupling_params"]["precice_config_file_name"] ) self._macro_mesh_name = self._data["coupling_params"]["macro_mesh_name"] @@ -276,6 +277,11 @@ def read_json_micro_manager(self): ) def read_json_snapshot(self): + """ + Reads Snapshot relevant information from JSON configuration file + """ + self._read_json(self._config_file_name) # Read base information + self._parameter_file_name = os.path.join( self._folder, self._data["coupling_params"]["parameter_file_name"] ) @@ -320,16 +326,16 @@ def read_json_snapshot(self): "For each snapshot a new micro simulation object will be created" ) - def get_config_file_name(self): + def get_precice_config_file_name(self): """ - Get the name of the JSON configuration file. + Get the name of the preCICE XML configuration file. Returns ------- config_file_name : string - Name of the JSON configuration file provided to the Config class. + Name of the preCICE XML configuration file. """ - return self._config_file_name + return self._precice_config_file_name def get_macro_mesh_name(self): """ diff --git a/micro_manager/micro_manager.py b/micro_manager/micro_manager.py index 5c400933..7bd80cd3 100644 --- a/micro_manager/micro_manager.py +++ b/micro_manager/micro_manager.py @@ -31,6 +31,7 @@ from .micro_simulation import create_simulation_class from .tools.logging_wrapper import Logger + try: from .interpolation import Interpolation except ImportError: @@ -51,15 +52,22 @@ def __init__(self, config_file: str) -> None: """ super().__init__(config_file) - self._logger = Logger("MicroManagerCoupling", "micro-manager.log", self._rank) + self._logger = Logger( + "MicroManagerCoupling", "micro-manager-coupling.log", self._rank + ) self._config.set_logger(self._logger) self._config.read_json_micro_manager() - # Define the preCICE Participant - self._participant = precice.Participant( - "Micro-Manager", self._config.get_config_file_name(), self._rank, self._size - ) + # Data names of data to output to the snapshot database + self._write_data_names = self._config.get_write_data_names() + + # Data names of data to read as input parameter to the simulations + self._read_data_names = self._config.get_read_data_names() + + self._micro_dt = self._config.get_micro_dt() + + self._is_micro_solve_time_required = self._config.write_micro_solve_time() self._macro_mesh_name = self._config.get_macro_mesh_name() @@ -118,6 +126,14 @@ def __init__(self, config_file: str) -> None: ) self._micro_sims_active_steps = None + # Define the preCICE Participant + self._participant = precice.Participant( + "Micro-Manager", + self._config.get_precice_config_file_name(), + self._rank, + self._size, + ) + # ************** # Public methods # ************** diff --git a/micro_manager/micro_manager_base.py b/micro_manager/micro_manager_base.py index b9c3f78b..0de8f829 100644 --- a/micro_manager/micro_manager_base.py +++ b/micro_manager/micro_manager_base.py @@ -60,16 +60,6 @@ def __init__(self, config_file): self._config = Config(config_file) - # Data names of data to output to the snapshot database - self._write_data_names = self._config.get_write_data_names() - - # Data names of data to read as input parameter to the simulations - self._read_data_names = self._config.get_read_data_names() - - self._micro_dt = self._config.get_micro_dt() - - self._is_micro_solve_time_required = self._config.write_micro_solve_time() - def initialize(self): """ Initialize micro simulations. Not implemented diff --git a/micro_manager/snapshot/snapshot.py b/micro_manager/snapshot/snapshot.py index a0a068b7..9cd5e883 100644 --- a/micro_manager/snapshot/snapshot.py +++ b/micro_manager/snapshot/snapshot.py @@ -21,6 +21,7 @@ from micro_manager.micro_simulation import create_simulation_class from micro_manager.tools.logging_wrapper import Logger + sys.path.append(os.getcwd()) @@ -37,11 +38,22 @@ def __init__(self, config_file: str) -> None: super().__init__(config_file) self._logger = Logger( - "MicroManagerSnapshot", "micro_manager_snapshot.log", self._rank + "MicroManagerSnapshot", "micro-manager-snapshot.log", self._rank ) + self._config.set_logger(self._logger) self._config.read_json_snapshot() + # Data names of data to output to the snapshot database + self._write_data_names = self._config.get_write_data_names() + + # Data names of data to read as input parameter to the simulations + self._read_data_names = self._config.get_read_data_names() + + self._micro_dt = self._config.get_micro_dt() + + self._is_micro_solve_time_required = self._config.write_micro_solve_time() + # Path to the parameter file containing input parameters for micro simulations self._parameter_file = self._config.get_parameter_file_name() # Get name of pos-processing script diff --git a/tests/integration/test_unit_cube/micro-manager-config-global-adaptivity-parallel.json b/tests/integration/test_unit_cube/micro-manager-config-global-adaptivity-parallel.json index 5938af6e..92be1d76 100644 --- a/tests/integration/test_unit_cube/micro-manager-config-global-adaptivity-parallel.json +++ b/tests/integration/test_unit_cube/micro-manager-config-global-adaptivity-parallel.json @@ -1,7 +1,7 @@ { "micro_file_name": "micro_dummy", "coupling_params": { - "config_file_name": "precice-config.xml", + "precice_config_file_name": "precice-config.xml", "macro_mesh_name": "macro-cube-mesh", "read_data_names": {"macro-scalar-data": "scalar", "macro-vector-data": "vector"}, "write_data_names": {"micro-scalar-data": "scalar", "micro-vector-data": "vector"} diff --git a/tests/integration/test_unit_cube/micro-manager-config-global-adaptivity.json b/tests/integration/test_unit_cube/micro-manager-config-global-adaptivity.json index f3c5361e..fb47972a 100644 --- a/tests/integration/test_unit_cube/micro-manager-config-global-adaptivity.json +++ b/tests/integration/test_unit_cube/micro-manager-config-global-adaptivity.json @@ -1,7 +1,7 @@ { "micro_file_name": "micro_dummy", "coupling_params": { - "config_file_name": "precice-config.xml", + "precice_config_file_name": "precice-config.xml", "macro_mesh_name": "macro-cube-mesh", "read_data_names": {"macro-scalar-data": "scalar", "macro-vector-data": "vector"}, "write_data_names": {"micro-scalar-data": "scalar", "micro-vector-data": "vector"} diff --git a/tests/integration/test_unit_cube/micro-manager-config-local-adaptivity.json b/tests/integration/test_unit_cube/micro-manager-config-local-adaptivity.json index ad1d2be1..a22f3213 100644 --- a/tests/integration/test_unit_cube/micro-manager-config-local-adaptivity.json +++ b/tests/integration/test_unit_cube/micro-manager-config-local-adaptivity.json @@ -1,7 +1,7 @@ { "micro_file_name": "micro_dummy", "coupling_params": { - "config_file_name": "precice-config.xml", + "precice_config_file_name": "precice-config.xml", "macro_mesh_name": "macro-cube-mesh", "read_data_names": {"macro-scalar-data": "scalar", "macro-vector-data": "vector"}, "write_data_names": {"micro-scalar-data": "scalar", "micro-vector-data": "vector"} diff --git a/tests/integration/test_unit_cube/micro-manager-config-parallel-1.json b/tests/integration/test_unit_cube/micro-manager-config-parallel-1.json index 2a56740b..49ff2fa1 100644 --- a/tests/integration/test_unit_cube/micro-manager-config-parallel-1.json +++ b/tests/integration/test_unit_cube/micro-manager-config-parallel-1.json @@ -1,7 +1,7 @@ { "micro_file_name": "micro_dummy", "coupling_params": { - "config_file_name": "precice-config.xml", + "precice_config_file_name": "precice-config.xml", "macro_mesh_name": "macro-cube-mesh", "read_data_names": {"macro-scalar-data": "scalar", "macro-vector-data": "vector"}, "write_data_names": {"micro-scalar-data": "scalar", "micro-vector-data": "vector"} diff --git a/tests/integration/test_unit_cube/micro-manager-config-parallel-2.json b/tests/integration/test_unit_cube/micro-manager-config-parallel-2.json index 478e0f0c..265b5b31 100644 --- a/tests/integration/test_unit_cube/micro-manager-config-parallel-2.json +++ b/tests/integration/test_unit_cube/micro-manager-config-parallel-2.json @@ -1,7 +1,7 @@ { "micro_file_name": "micro_dummy", "coupling_params": { - "config_file_name": "precice-config.xml", + "precice_config_file_name": "precice-config.xml", "macro_mesh_name": "macro-cube-mesh", "read_data_names": {"macro-scalar-data": "scalar", "macro-vector-data": "vector"}, "write_data_names": {"micro-scalar-data": "scalar", "micro-vector-data": "vector"} diff --git a/tests/unit/micro-manager-config.json b/tests/unit/micro-manager-config.json index c4d886c3..07e030c3 100644 --- a/tests/unit/micro-manager-config.json +++ b/tests/unit/micro-manager-config.json @@ -1,7 +1,7 @@ { "micro_file_name": "test_micro_manager", "coupling_params": { - "config_file_name": "dummy-config.xml", + "precice_config_file_name": "dummy-config.xml", "macro_mesh_name": "dummy-macro-mesh", "read_data_names": {"macro-scalar-data": "scalar", "macro-vector-data": "vector"}, "write_data_names": {"micro-scalar-data": "scalar", "micro-vector-data": "vector"} diff --git a/tests/unit/micro-manager-config_crash.json b/tests/unit/micro-manager-config_crash.json index 45305fa9..f95e14b1 100644 --- a/tests/unit/micro-manager-config_crash.json +++ b/tests/unit/micro-manager-config_crash.json @@ -1,7 +1,7 @@ { "micro_file_name": "test_micro_simulation_crash_handling", "coupling_params": { - "config_file_name": "dummy-config.xml", + "precice_config_file_name": "dummy-config.xml", "macro_mesh_name": "dummy-macro-mesh", "read_data_names": {"macro-scalar-data": "scalar", "macro-vector-data": "vector"}, "write_data_names": {"micro-scalar-data": "scalar", "micro-vector-data": "vector"} diff --git a/tests/unit/test_micro_manager.py b/tests/unit/test_micro_manager.py index b733069f..cbe8f306 100644 --- a/tests/unit/test_micro_manager.py +++ b/tests/unit/test_micro_manager.py @@ -120,8 +120,11 @@ def test_config(self): Test if the functions in the Config class work. """ config = micro_manager.Config("micro-manager-config.json") + config.set_logger(MagicMock()) config.read_json_micro_manager() - self.assertEqual(config._config_file_name.split("/")[-1], "dummy-config.xml") + self.assertEqual( + config._precice_config_file_name.split("/")[-1], "dummy-config.xml" + ) self.assertEqual(config._micro_file_name, "test_micro_manager") self.assertEqual(config._macro_mesh_name, "dummy-macro-mesh") self.assertEqual(config._micro_output_n, 10) diff --git a/tests/unit/test_snapshot_computation.py b/tests/unit/test_snapshot_computation.py index c62bef24..d72f4d47 100644 --- a/tests/unit/test_snapshot_computation.py +++ b/tests/unit/test_snapshot_computation.py @@ -137,6 +137,7 @@ def test_config(self): Test if the functions in the SnapshotConfig class work. """ config = Config("snapshot-config.json") + config.set_logger(MagicMock()) config.read_json_snapshot() self.assertEqual( From 4c255ac252603428f153d5991c4da3ba2d4143b7 Mon Sep 17 00:00:00 2001 From: Ishaan Desai Date: Tue, 3 Dec 2024 21:58:29 +0100 Subject: [PATCH 09/17] Change parameter in Micro Manager config files for the dummy --- examples/micro-manager-cpp-adaptivity-config.json | 2 +- examples/micro-manager-cpp-config.json | 2 +- examples/micro-manager-python-adaptivity-config.json | 2 +- examples/micro-manager-python-config.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/micro-manager-cpp-adaptivity-config.json b/examples/micro-manager-cpp-adaptivity-config.json index 89b424ec..713ed509 100644 --- a/examples/micro-manager-cpp-adaptivity-config.json +++ b/examples/micro-manager-cpp-adaptivity-config.json @@ -1,7 +1,7 @@ { "micro_file_name": "cpp-dummy/micro_dummy", "coupling_params": { - "config_file_name": "precice-config-adaptivity.xml", + "precice_config_file_name": "precice-config-adaptivity.xml", "macro_mesh_name": "macro-mesh", "read_data_names": {"macro-scalar-data": "scalar", "macro-vector-data": "vector"}, "write_data_names": {"micro-scalar-data": "scalar", "micro-vector-data": "vector"} diff --git a/examples/micro-manager-cpp-config.json b/examples/micro-manager-cpp-config.json index d44f70b4..cb6ce619 100644 --- a/examples/micro-manager-cpp-config.json +++ b/examples/micro-manager-cpp-config.json @@ -1,7 +1,7 @@ { "micro_file_name": "cpp-dummy/micro_dummy", "coupling_params": { - "config_file_name": "precice-config.xml", + "precice_config_file_name": "precice-config.xml", "macro_mesh_name": "macro-mesh", "read_data_names": {"macro-scalar-data": "scalar", "macro-vector-data": "vector"}, "write_data_names": {"micro-scalar-data": "scalar", "micro-vector-data": "vector"} diff --git a/examples/micro-manager-python-adaptivity-config.json b/examples/micro-manager-python-adaptivity-config.json index 14640faa..67926af3 100644 --- a/examples/micro-manager-python-adaptivity-config.json +++ b/examples/micro-manager-python-adaptivity-config.json @@ -1,7 +1,7 @@ { "micro_file_name": "python-dummy/micro_dummy", "coupling_params": { - "config_file_name": "precice-config-adaptivity.xml", + "precice_config_file_name": "precice-config-adaptivity.xml", "macro_mesh_name": "macro-mesh", "read_data_names": {"macro-scalar-data": "scalar", "macro-vector-data": "vector"}, "write_data_names": {"micro-scalar-data": "scalar", "micro-vector-data": "vector"} diff --git a/examples/micro-manager-python-config.json b/examples/micro-manager-python-config.json index 85f84931..136dc694 100644 --- a/examples/micro-manager-python-config.json +++ b/examples/micro-manager-python-config.json @@ -1,7 +1,7 @@ { "micro_file_name": "python-dummy/micro_dummy", "coupling_params": { - "config_file_name": "precice-config.xml", + "precice_config_file_name": "precice-config.xml", "macro_mesh_name": "macro-mesh", "read_data_names": {"macro-scalar-data": "scalar", "macro-vector-data": "vector"}, "write_data_names": {"micro-scalar-data": "scalar", "micro-vector-data": "vector"} From 2709d089c8e27c4e65766cf7b5d121951081b447 Mon Sep 17 00:00:00 2001 From: Ishaan Desai Date: Tue, 3 Dec 2024 23:21:43 +0100 Subject: [PATCH 10/17] Move logging of adaptivity metrics to only be done by rank 0 --- micro_manager/micro_manager.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/micro_manager/micro_manager.py b/micro_manager/micro_manager.py index 7bd80cd3..6b227e59 100644 --- a/micro_manager/micro_manager.py +++ b/micro_manager/micro_manager.py @@ -338,15 +338,16 @@ def solve(self) -> None: global_active_sims = np.count_nonzero(is_sim_active) global_inactive_sims = np.count_nonzero(is_sim_active == False) - self._adaptivity_logger.log_info_one_rank( - "{},{},{},{},{}".format( - t, - np.mean(global_active_sims), - np.mean(global_inactive_sims), - np.max(global_active_sims), - np.max(global_inactive_sims), + if self._rank == 0: + self._adaptivity_logger.log_info_one_rank( + "{},{},{},{},{}".format( + t, + np.mean(global_active_sims), + np.mean(global_inactive_sims), + np.max(global_active_sims), + np.max(global_inactive_sims), + ) ) - ) self._logger.log_info_one_rank("Time window {} converged.".format(n)) self._participant.finalize() From 89013383559c75357457e5b244531a50929de5e7 Mon Sep 17 00:00:00 2001 From: Ishaan Desai Date: Wed, 4 Dec 2024 10:28:48 +0100 Subject: [PATCH 11/17] Log time window instead of time step for adaptivity metric --- micro_manager/micro_manager.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/micro_manager/micro_manager.py b/micro_manager/micro_manager.py index 6b227e59..e4e3ab61 100644 --- a/micro_manager/micro_manager.py +++ b/micro_manager/micro_manager.py @@ -100,7 +100,7 @@ def __init__(self, config_file: str) -> None: ) self._adaptivity_logger.log_info_one_rank( - "Time,Avg Active Sims,Avg Inactive Sims,Max Active,Max Inactive" + "Time Window,Avg Active Sims,Avg Inactive Sims,Max Active,Max Inactive" ) self._number_of_sims_for_adaptivity = 0 @@ -300,6 +300,7 @@ def solve(self) -> None: t += dt # increase internal time when time step is done. n += 1 # increase counter + self._participant.advance( dt ) # notify preCICE that time step of size dt is complete @@ -341,7 +342,7 @@ def solve(self) -> None: if self._rank == 0: self._adaptivity_logger.log_info_one_rank( "{},{},{},{},{}".format( - t, + n, np.mean(global_active_sims), np.mean(global_inactive_sims), np.max(global_active_sims), From 25801ce87c6b456bbe71680aa735a108ac44b9ab Mon Sep 17 00:00:00 2001 From: Ishaan Desai Date: Mon, 9 Dec 2024 15:30:34 +0100 Subject: [PATCH 12/17] Add optional preCICE export output for adaptivity computation CPU time --- micro_manager/config.py | 46 +++++++++++++++++++++++- micro_manager/domain_decomposition.py | 7 +--- micro_manager/micro_manager.py | 52 ++++++++++++++++++++++++--- 3 files changed, 93 insertions(+), 12 deletions(-) diff --git a/micro_manager/config.py b/micro_manager/config.py index 98a96937..b05ddc59 100644 --- a/micro_manager/config.py +++ b/micro_manager/config.py @@ -49,6 +49,8 @@ def __init__(self, config_file_name): self._adaptivity_refining_constant = 0.5 self._adaptivity_every_implicit_iteration = False self._adaptivity_similarity_measure = "L1" + self._adaptivity_output_n = 1 + self._adaptivity_output_cpu_time = False # Snapshot information self._parameter_file_name = None @@ -199,12 +201,21 @@ def read_json_micro_manager(self): self._data_for_adaptivity[dname] = exchange_data[dname] if self._data_for_adaptivity.keys() == self._write_data_names.keys(): - warn( + self._logger.log_info_one_rank( "Only micro simulation data is used for similarity computation in adaptivity. This would lead to the" " same set of active and inactive simulations for the entire simulation time. If this is not intended," " please include macro simulation data as well." ) + try: + self._adaptivity_output_n = self._data["simulation_params"][ + "adaptivity_settings" + ]["output_n"] + except BaseException: + self._logger.log_info_one_rank( + "No output interval for adaptivity provided. Adaptivity metrics will be output every time window." + ) + self._adaptivity_history_param = self._data["simulation_params"][ "adaptivity_settings" ]["history_param"] @@ -245,6 +256,17 @@ def read_json_micro_manager(self): self._write_data_names["active_state"] = False self._write_data_names["active_steps"] = False + try: + if self.data["simulation_params"]["adaptivity_settings"][ + "output_cpu_time" + ]: + self._adaptivity_output_cpu_time = True + self._write_data_names["adaptivity_cpu_time"] = False + except BaseException: + self._logger.log_info_one_rank( + "Micro Manager will not output CPU time of the adaptivity computation." + ) + if "interpolate_crash" in self._data["simulation_params"]: if self._data["simulation_params"]["interpolate_crash"] == "True": self._interpolate_crash = True @@ -466,6 +488,17 @@ def get_data_for_adaptivity(self): """ return self._data_for_adaptivity + def get_adaptivity_output_n(self): + """ + Get the output frequency of adaptivity metrics. + + Returns + ------- + adaptivity_output_n : int + Output frequency of adaptivity metrics, so output every N timesteps + """ + return self._adaptivity_output_n + def get_adaptivity_hist_param(self): """ Get adaptivity history parameter. @@ -525,6 +558,17 @@ def is_adaptivity_required_in_every_implicit_iteration(self): """ return self._adaptivity_every_implicit_iteration + def output_adaptivity_cpu_time(self): + """ + Check if CPU time of the adaptivity computation needs to be output. + + Returns + ------- + adaptivity_cpu_time : bool + True if CPU time of the adaptivity computation needs to be output, False otherwise. + """ + return self._adaptivity_output_cpu_time + def get_micro_dt(self): """ Get the size of the micro time window. diff --git a/micro_manager/domain_decomposition.py b/micro_manager/domain_decomposition.py index 573eac07..b80b87d4 100644 --- a/micro_manager/domain_decomposition.py +++ b/micro_manager/domain_decomposition.py @@ -6,14 +6,12 @@ class DomainDecomposer: - def __init__(self, logger, dims, rank, size) -> None: + def __init__(self, dims, rank, size) -> None: """ Class constructor. Parameters ---------- - logger : object of logging - Logger defined from the standard package logging. dims : int Dimensions of the problem. rank : int @@ -21,7 +19,6 @@ def __init__(self, logger, dims, rank, size) -> None: size : int Total number of MPI processes. """ - self._logger = logger self._rank = rank self._size = size self._dims = dims @@ -98,6 +95,4 @@ def decompose_macro_domain(self, macro_bounds: list, ranks_per_axis: list) -> li if rank_in_axis[d] + 1 == ranks_per_axis[d]: mesh_bounds[d * 2 + 1] = macro_bounds[d * 2 + 1] - self._logger.log_info_any_rank("Bounding box limits are {}".format(mesh_bounds)) - return mesh_bounds diff --git a/micro_manager/micro_manager.py b/micro_manager/micro_manager.py index e4e3ab61..3c61af96 100644 --- a/micro_manager/micro_manager.py +++ b/micro_manager/micro_manager.py @@ -20,8 +20,9 @@ from copy import deepcopy from typing import Dict from warnings import warn - import numpy as np +import time + import precice from .micro_manager_base import MicroManager @@ -110,6 +111,8 @@ def __init__(self, config_file: str) -> None: self._adaptivity_data_names = self._config.get_data_for_adaptivity() + self._adaptivity_output_n = self._config.get_adaptivity_output_n() + # Names of macro data to be used for adaptivity computation self._adaptivity_macro_data_names = dict() @@ -125,6 +128,7 @@ def __init__(self, config_file: str) -> None: self._config.is_adaptivity_required_in_every_implicit_iteration() ) self._micro_sims_active_steps = None + self._output_adaptivity_cpu_time = self._config.output_adaptivity_cpu_time() # Define the preCICE Participant self._participant = precice.Participant( @@ -190,6 +194,8 @@ def solve(self) -> None: while self._participant.is_coupling_ongoing(): + adaptivity_cpu_time = 0.0 + dt = min(self._participant.get_max_time_step_size(), self._micro_dt) # Write a checkpoint @@ -201,6 +207,7 @@ def solve(self) -> None: if self._is_adaptivity_on: if not self._adaptivity_in_every_implicit_step: + start_time = time.process_time() ( similarity_dists, is_sim_active, @@ -213,6 +220,9 @@ def solve(self) -> None: sim_is_associated_to, self._data_for_adaptivity, ) + end_time = time.process_time() + + adaptivity_cpu_time = end_time - start_time # Only checkpoint the adaptivity configuration if adaptivity is computed # once in every time window @@ -239,6 +249,7 @@ def solve(self) -> None: if self._is_adaptivity_on: if self._adaptivity_in_every_implicit_step: + start_time = time.process_time() ( similarity_dists, is_sim_active, @@ -251,6 +262,8 @@ def solve(self) -> None: sim_is_associated_to, self._data_for_adaptivity, ) + end_time = time.process_time() + adaptivity_cpu_time = end_time - start_time if self._adaptivity_type == "local": active_sim_ids = np.where(is_sim_active)[0] @@ -270,6 +283,12 @@ def solve(self) -> None: micro_sims_output = self._solve_micro_simulations_with_adaptivity( micro_sims_input, is_sim_active, sim_is_associated_to, dt ) + + if self._output_adaptivity_cpu_time: + for i in range(self._local_number_of_sims): + micro_sims_output[i][ + "adaptivity_cpu_time" + ] = adaptivity_cpu_time else: micro_sims_output = self._solve_micro_simulations(micro_sims_input, dt) @@ -339,7 +358,7 @@ def solve(self) -> None: global_active_sims = np.count_nonzero(is_sim_active) global_inactive_sims = np.count_nonzero(is_sim_active == False) - if self._rank == 0: + if n % self._adaptivity_output_n == 0 and self._rank == 0: self._adaptivity_logger.log_info_one_rank( "{},{},{},{},{}".format( n, @@ -368,7 +387,6 @@ def initialize(self) -> None: ), "Provided macro mesh bounds are of incorrect dimension" if self._is_parallel: domain_decomposer = DomainDecomposer( - self._logger, self._participant.get_mesh_dimensions(self._macro_mesh_name), self._rank, self._size, @@ -411,6 +429,29 @@ def initialize(self) -> None: # the correct global IDs self._comm.Allgatherv(np.array(self._local_number_of_sims), nms_all_ranks) + max_nms = np.max(nms_all_ranks) + min_nms = np.min(nms_all_ranks) + + if ( + max_nms != min_nms + ): # if the number of maximum and minimum micro simulations per rank are different + self._logger.log_info_one_rank( + "The following ranks have the maximum number of micro simulations ({}): {}".format( + max_nms, np.where(nms_all_ranks == max_nms)[0] + ) + ) + self._logger.log_info_one_rank( + "The following ranks have the minimum number of micro simulations ({}): {}".format( + min_nms, np.where(nms_all_ranks == min_nms)[0] + ) + ) + else: # if the number of maximum and minimum micro simulations per rank are the same + self._logger.log_info_one_rank( + "All ranks have the same number of micro simulations: {}".format( + max_nms + ) + ) + # Get global number of micro simulations self._global_number_of_sims = np.sum(nms_all_ranks) @@ -811,11 +852,11 @@ def _solve_micro_simulations_with_adaptivity( if not self._has_sim_crashed[active_id]: # Attempt to solve the micro simulation try: - start_time = time.time() + start_time = time.process_time() micro_sims_output[active_id] = self._micro_sims[active_id].solve( micro_sims_input[active_id], dt ) - end_time = time.time() + end_time = time.process_time() # Write solve time of the macro simulation if required and the simulation has not crashed if self._is_micro_solve_time_required: micro_sims_output[active_id]["micro_sim_time"] = ( @@ -850,6 +891,7 @@ def _solve_micro_simulations_with_adaptivity( "Exiting simulation after micro simulation crash." ) sys.exit() + # Interpolate result for crashed simulation unset_sims = [] for active_id in active_sim_ids: From 4b1c16fa3dc60dcec30d91c2c44e75993eead14a Mon Sep 17 00:00:00 2001 From: Ishaan Desai Date: Mon, 9 Dec 2024 19:12:12 +0100 Subject: [PATCH 13/17] Fix domain decomposition unit test --- tests/unit/test_domain_decomposition.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/unit/test_domain_decomposition.py b/tests/unit/test_domain_decomposition.py index ffab9474..ef92a934 100644 --- a/tests/unit/test_domain_decomposition.py +++ b/tests/unit/test_domain_decomposition.py @@ -8,7 +8,6 @@ class TestDomainDecomposition(TestCase): def setUp(self) -> None: - self._logger = MagicMock() self._macro_bounds_3d = [ -1, 1, @@ -25,7 +24,7 @@ def test_rank5_outof_10_3d(self): rank = 5 size = 10 ranks_per_axis = [1, 2, 5] - domain_decomposer = DomainDecomposer(self._logger, 3, rank, size) + domain_decomposer = DomainDecomposer(3, rank, size) domain_decomposer._dims = 3 mesh_bounds = domain_decomposer.decompose_macro_domain( self._macro_bounds_3d, ranks_per_axis @@ -40,7 +39,7 @@ def test_rank10_out_of_32_3d(self): rank = 10 size = 32 ranks_per_axis = [4, 1, 8] - domain_decomposer = DomainDecomposer(self._logger, 3, rank, size) + domain_decomposer = DomainDecomposer(3, rank, size) domain_decomposer._dims = 3 mesh_bounds = domain_decomposer.decompose_macro_domain( self._macro_bounds_3d, ranks_per_axis @@ -55,7 +54,7 @@ def test_rank7_out_of_16_3d(self): rank = 7 size = 16 ranks_per_axis = [8, 2, 1] - domain_decomposer = DomainDecomposer(self._logger, 3, rank, size) + domain_decomposer = DomainDecomposer(3, rank, size) domain_decomposer._dims = 3 mesh_bounds = domain_decomposer.decompose_macro_domain( self._macro_bounds_3d, ranks_per_axis From 2da753e8554020605424bed7369f79b8719443db Mon Sep 17 00:00:00 2001 From: Ishaan Desai Date: Tue, 10 Dec 2024 13:02:21 +0100 Subject: [PATCH 14/17] Add option to export adaptivity computation CPU time, and change name of solve CPU time export --- docs/configuration.md | 13 ++++++++----- examples/micro-manager-cpp-adaptivity-config.json | 3 ++- .../micro-manager-python-adaptivity-config.json | 3 ++- examples/precice-config-adaptivity.xml | 9 ++++++--- examples/precice-config.xml | 6 +++--- micro_manager/config.py | 13 ++++++++----- micro_manager/micro_manager.py | 12 ++++++------ micro_manager/snapshot/snapshot.py | 6 +++--- ...o-manager-config-global-adaptivity-parallel.json | 6 +++++- tests/integration/test_unit_cube/precice-config.xml | 9 ++++++--- 10 files changed, 49 insertions(+), 31 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index aedbed1b..7b434236 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -13,7 +13,7 @@ The Micro Manager is configured with a JSON file. An example configuration file { "micro_file_name": "micro_solver", "coupling_params": { - "config_file_name": "precice-config.xml", + "precice_config_file_name": "precice-config.xml", "macro_mesh_name": "macro-mesh", "read_data_names": {"temperature": "scalar", "heat-flux": "vector"}, "write_data_names": {"porosity": "scalar", "conductivity": "vector"} @@ -38,7 +38,7 @@ There are three main sections in the configuration file, the `coupling_params`, Parameter | Description --- | --- -`config_file_name` | Path to the preCICE XML configuration file from the current working directory. +`precice_config_file_name` | Path to the preCICE XML configuration file from the current working directory. `macro_mesh_name` | Name of the macro mesh as stated in the preCICE configuration. `read_data_names` | A Python dictionary with the names of the data to be read from preCICE as keys and `"scalar"` or `"vector"` as values depending on the nature of the data. `write_data_names` | A Python dictionary with the names of the data to be written to preCICE as keys and `"scalar"` or `"vector"` as values depending on the nature of the data. @@ -74,14 +74,14 @@ If the parameter `data_from_micro_sims` is set, the data to be output needs to b ``` -If `output_micro_sim_solve_time` is set, add similar entries for the data `micro_sim_time` in the following way: +If `output_micro_sim_solve_time` is set, add similar entries for the data `solve_cpu_time` in the following way: ```xml - + ... - + ``` @@ -120,6 +120,7 @@ Parameter | Description `refining_constant` | Refining constant $$ C_r $$, set as $$ 0 =< C_r < 1 $$. `every_implicit_iteration` | If True, adaptivity is calculated in every implicit iteration.
If False, adaptivity is calculated once at the start of the time window and then reused in every implicit time iteration. `similarity_measure`| Similarity measure to be used for adaptivity. Can be either `L1`, `L2`, `L1rel` or `L2rel`. By default, `L1` is used. The `rel` variants calculate the respective relative norms. This parameter is *optional*. +`output_cpu_time` | Write CPU time of the adaptivity computation to preCICE, to be exported. This parameter is *optional*. The primary tuning parameters for adaptivity are the history parameter $$ \Lambda $$, the coarsening constant $$ C_c $$, and the refining constant $$ C_r $$. Their effects can be interpreted as: @@ -170,6 +171,8 @@ The Micro Manager uses the output functionality of preCICE, hence these data set ``` +If the parameter `output_cpu_time` in `adaptivity_settings` is set to `True`, a scalar data field `adaptivity_cpu_time` needs to be added in the same way as described above. + ## Interpolate a crashed micro simulation If the optional dependency `sklearn` is installed, the Micro Manager will derive the output of a crashed micro simulation by interpolating outputs from similar simulations. To enable this, set diff --git a/examples/micro-manager-cpp-adaptivity-config.json b/examples/micro-manager-cpp-adaptivity-config.json index 713ed509..d712cea2 100644 --- a/examples/micro-manager-cpp-adaptivity-config.json +++ b/examples/micro-manager-cpp-adaptivity-config.json @@ -16,7 +16,8 @@ "history_param": 0.5, "coarsening_constant": 0.3, "refining_constant": 0.4, - "every_implicit_iteration": "True" + "every_implicit_iteration": "True", + "output_cpu_time": "True" } }, "diagnostics": { diff --git a/examples/micro-manager-python-adaptivity-config.json b/examples/micro-manager-python-adaptivity-config.json index 67926af3..1f5b0f61 100644 --- a/examples/micro-manager-python-adaptivity-config.json +++ b/examples/micro-manager-python-adaptivity-config.json @@ -16,7 +16,8 @@ "history_param": 0.5, "coarsening_constant": 0.3, "refining_constant": 0.4, - "every_implicit_iteration": "True" + "every_implicit_iteration": "True", + "output_cpu_time": "True" } }, "diagnostics": { diff --git a/examples/precice-config-adaptivity.xml b/examples/precice-config-adaptivity.xml index 1e4180b9..eddc16e5 100644 --- a/examples/precice-config-adaptivity.xml +++ b/examples/precice-config-adaptivity.xml @@ -10,18 +10,20 @@ - + + - + + @@ -38,9 +40,10 @@ - + + diff --git a/examples/precice-config.xml b/examples/precice-config.xml index d89f648f..0faa764f 100644 --- a/examples/precice-config.xml +++ b/examples/precice-config.xml @@ -8,14 +8,14 @@ - + - + @@ -32,7 +32,7 @@ - + diff --git a/micro_manager/config.py b/micro_manager/config.py index b05ddc59..1a7ebed1 100644 --- a/micro_manager/config.py +++ b/micro_manager/config.py @@ -133,9 +133,9 @@ def _read_json(self, config_file_name): self._micro_dt = self._data["simulation_params"]["micro_dt"] try: - if self._data["diagnostics"]["output_micro_sim_solve_time"]: + if self._data["diagnostics"]["output_micro_sim_solve_time"] == "True": self._output_micro_sim_time = True - self._write_data_names["micro_sim_time"] = False + self._write_data_names["solve_cpu_time"] = False except BaseException: self._logger.log_info_one_rank( "Micro manager will not output time required to solve each micro simulation in each time step." @@ -257,9 +257,12 @@ def read_json_micro_manager(self): self._write_data_names["active_steps"] = False try: - if self.data["simulation_params"]["adaptivity_settings"][ - "output_cpu_time" - ]: + if ( + self._data["simulation_params"]["adaptivity_settings"][ + "output_cpu_time" + ] + == "True" + ): self._adaptivity_output_cpu_time = True self._write_data_names["adaptivity_cpu_time"] = False except BaseException: diff --git a/micro_manager/micro_manager.py b/micro_manager/micro_manager.py index 3c61af96..3e465314 100644 --- a/micro_manager/micro_manager.py +++ b/micro_manager/micro_manager.py @@ -748,7 +748,7 @@ def _solve_micro_simulations(self, micro_sims_input: list, dt: float) -> list: end_time = time.time() # Write solve time of the macro simulation if required and the simulation has not crashed if self._is_micro_solve_time_required: - micro_sims_output[count]["micro_sim_time"] = ( + micro_sims_output[count]["solve_cpu_time"] = ( end_time - start_time ) @@ -859,7 +859,7 @@ def _solve_micro_simulations_with_adaptivity( end_time = time.process_time() # Write solve time of the macro simulation if required and the simulation has not crashed if self._is_micro_solve_time_required: - micro_sims_output[active_id]["micro_sim_time"] = ( + micro_sims_output[active_id]["solve_cpu_time"] = ( end_time - start_time ) @@ -930,7 +930,7 @@ def _solve_micro_simulations_with_adaptivity( ] = self._micro_sims_active_steps[inactive_id] if self._is_micro_solve_time_required: - micro_sims_output[inactive_id]["micro_sim_time"] = 0 + micro_sims_output[inactive_id]["solve_cpu_time"] = 0 # Collect micro sim output for adaptivity calculation for i in range(self._local_number_of_sims): @@ -1017,13 +1017,13 @@ def _interpolate_output_for_crashed_sim( if self._is_adaptivity_on: interpol_space.append(micro_sims_active_input_lists[neighbor].copy()) interpol_values.append(micro_sims_active_values[neighbor].copy()) - interpol_values[-1].pop("micro_sim_time", None) + interpol_values[-1].pop("solve_cpu_time", None) interpol_values[-1].pop("active_state", None) interpol_values[-1].pop("active_steps", None) else: interpol_space.append(micro_sims_active_input_lists[neighbor].copy()) interpol_values.append(micro_sims_active_values[neighbor].copy()) - interpol_values[-1].pop("micro_sim_time", None) + interpol_values[-1].pop("solve_cpu_time", None) # Interpolate for each parameter output_interpol = dict() @@ -1037,7 +1037,7 @@ def _interpolate_output_for_crashed_sim( ) # Reintroduce removed information if self._is_micro_solve_time_required: - output_interpol["micro_sim_time"] = 0 + output_interpol["solve_cpu_time"] = 0 if self._is_adaptivity_on: output_interpol["active_state"] = 1 output_interpol["active_steps"] = self._micro_sims_active_steps[unset_sim] diff --git a/micro_manager/snapshot/snapshot.py b/micro_manager/snapshot/snapshot.py index 9cd5e883..7777873f 100644 --- a/micro_manager/snapshot/snapshot.py +++ b/micro_manager/snapshot/snapshot.py @@ -287,12 +287,12 @@ def _solve_micro_simulation(self, micro_sims_input: dict) -> dict | None: simulations. The return type is None if the simulation has crashed. """ try: - start_time = time.time() + start_time = time.process_time() micro_sims_output = self._micro_sims.solve(micro_sims_input, self._micro_dt) - end_time = time.time() + end_time = time.process_time() if self._is_micro_solve_time_required: - micro_sims_output["micro_sim_time"] = end_time - start_time + micro_sims_output["solve_cpu_time"] = end_time - start_time return micro_sims_output # Handle simulation crash diff --git a/tests/integration/test_unit_cube/micro-manager-config-global-adaptivity-parallel.json b/tests/integration/test_unit_cube/micro-manager-config-global-adaptivity-parallel.json index 92be1d76..bd2c738b 100644 --- a/tests/integration/test_unit_cube/micro-manager-config-global-adaptivity-parallel.json +++ b/tests/integration/test_unit_cube/micro-manager-config-global-adaptivity-parallel.json @@ -17,7 +17,11 @@ "history_param": 0.5, "coarsening_constant": 0.3, "refining_constant": 0.4, - "every_implicit_iteration": "True" + "every_implicit_iteration": "True", + "output_cpu_time": "True" } + }, + "diagnostics": { + "output_micro_sim_solve_time": "True" } } diff --git a/tests/integration/test_unit_cube/precice-config.xml b/tests/integration/test_unit_cube/precice-config.xml index 6e9f2c11..91395c01 100644 --- a/tests/integration/test_unit_cube/precice-config.xml +++ b/tests/integration/test_unit_cube/precice-config.xml @@ -8,18 +8,20 @@ - + + - + + @@ -36,9 +38,10 @@ - + + From f58fdba892d38a8cd869921c68eda9361a851767 Mon Sep 17 00:00:00 2001 From: Ishaan Desai Date: Tue, 10 Dec 2024 13:33:21 +0100 Subject: [PATCH 15/17] Fix unit tests --- tests/unit/test_micro_manager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/test_micro_manager.py b/tests/unit/test_micro_manager.py index cbe8f306..24afea87 100644 --- a/tests/unit/test_micro_manager.py +++ b/tests/unit/test_micro_manager.py @@ -34,7 +34,7 @@ def setUp(self): self.fake_write_data_names = { "micro-scalar-data": False, "micro-vector-data": True, - "micro_sim_time": False, + "solve_cpu_time": False, "active_state": False, "active_steps": False, } @@ -42,7 +42,7 @@ def setUp(self): { "micro-scalar-data": 1, "micro-vector-data": np.array([0, 1, 2]), - "micro_sim_time": 0, + "solve_cpu_time": 0, "active_state": 0, "active_steps": 0, } From 4e95270c6ddb4174068721f26eb347637eba8398 Mon Sep 17 00:00:00 2001 From: Ishaan Desai Date: Mon, 16 Dec 2024 11:46:10 +0100 Subject: [PATCH 16/17] Move adaptivity metrics logging into the respective adaptivity classes --- micro_manager/adaptivity/global_adaptivity.py | 16 ++++++++ micro_manager/adaptivity/local_adaptivity.py | 28 +++++++++++-- micro_manager/config.py | 2 +- micro_manager/micro_manager.py | 39 +++++++------------ 4 files changed, 55 insertions(+), 30 deletions(-) diff --git a/micro_manager/adaptivity/global_adaptivity.py b/micro_manager/adaptivity/global_adaptivity.py index 15bc1ca6..bf7b7001 100644 --- a/micro_manager/adaptivity/global_adaptivity.py +++ b/micro_manager/adaptivity/global_adaptivity.py @@ -190,6 +190,22 @@ def get_full_field_micro_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) + + logger.log_info_one_rank( + "{},{},{},{},{}".format( + n, + np.mean(global_active_sims), + np.mean(global_inactive_sims), + np.max(global_active_sims), + np.max(global_inactive_sims), + ) + ) + def _communicate_micro_output( self, adaptivity_data: list, diff --git a/micro_manager/adaptivity/local_adaptivity.py b/micro_manager/adaptivity/local_adaptivity.py index 04e4aeb6..c89f229f 100644 --- a/micro_manager/adaptivity/local_adaptivity.py +++ b/micro_manager/adaptivity/local_adaptivity.py @@ -10,7 +10,7 @@ class LocalAdaptivityCalculator(AdaptivityCalculator): - def __init__(self, configurator) -> None: + def __init__(self, configurator, comm) -> None: """ Class constructor. @@ -18,10 +18,11 @@ def __init__(self, configurator) -> None: ---------- configurator : object of class Config Object which has getter functions to get parameters defined in the configuration file. - logger : object of logging - Logger defined from the standard package logging + comm : MPI.COMM_WORLD + Global communicator of MPI. """ super().__init__(configurator) + self._comm = comm def compute_adaptivity( self, @@ -143,6 +144,27 @@ def get_full_field_micro_output( return micro_sims_output + def log_metrics(self, logger, adaptivity_list: list, n: int) -> None: + """ """ + is_sim_active = adaptivity_list[1] + + # MPI Gather is necessary as local adaptivity only stores local data + local_active_sims = np.count_nonzero(is_sim_active) + global_active_sims = self._comm.gather(local_active_sims) + + local_inactive_sims = np.count_nonzero(is_sim_active == False) + global_inactive_sims = self._comm.gather(local_inactive_sims) + + logger.log_info_one_rank( + "{},{},{},{},{}".format( + n, + np.mean(global_active_sims), + np.mean(global_inactive_sims), + np.max(global_active_sims), + np.max(global_inactive_sims), + ) + ) + def _update_inactive_sims( self, similarity_dists: np.ndarray, diff --git a/micro_manager/config.py b/micro_manager/config.py index 26f99b31..58a72be2 100644 --- a/micro_manager/config.py +++ b/micro_manager/config.py @@ -50,7 +50,7 @@ def __init__(self, config_file_name): self._adaptivity_refining_constant = 0.5 self._adaptivity_every_implicit_iteration = False self._adaptivity_similarity_measure = "L1" - self._adaptivity_output_n = 1 + self._adaptivity_output_n = -2 self._adaptivity_output_cpu_time = False self._adaptivity_output_mem_usage = False diff --git a/micro_manager/micro_manager.py b/micro_manager/micro_manager.py index 02fa3f0c..56b70239 100644 --- a/micro_manager/micro_manager.py +++ b/micro_manager/micro_manager.py @@ -121,8 +121,6 @@ def __init__(self, config_file: str) -> None: self._adaptivity_data_names = self._config.get_data_for_adaptivity() - self._adaptivity_output_n = self._config.get_adaptivity_output_n() - # Names of macro data to be used for adaptivity computation self._adaptivity_macro_data_names = dict() @@ -138,7 +136,9 @@ def __init__(self, config_file: str) -> None: self._config.is_adaptivity_required_in_every_implicit_iteration() ) self._micro_sims_active_steps = None - self._output_adaptivity_cpu_time = self._config.output_adaptivity_cpu_time() + + self._adaptivity_output_n = self._config.get_adaptivity_output_n() + self._output_adaptivity_cpu_time = self._config.output_adaptivity_cpu_time() # Define the preCICE Participant self._participant = precice.Participant( @@ -306,28 +306,15 @@ def solve(self) -> None: for sim in self._micro_sims: sim.output() - if self._is_adaptivity_on: - if self._adaptivity_type == "local": - # MPI Gather is necessary as local adaptivity only stores local data - local_active_sims = np.count_nonzero(is_sim_active) - global_active_sims = self._comm.gather(local_active_sims) - - local_inactive_sims = np.count_nonzero(is_sim_active == False) - global_inactive_sims = self._comm.gather(local_inactive_sims) - elif self._adaptivity_type == "global": - global_active_sims = np.count_nonzero(is_sim_active) - global_inactive_sims = np.count_nonzero(is_sim_active == False) - - if n % self._adaptivity_output_n == 0 and self._rank == 0: - self._adaptivity_logger.log_info_one_rank( - "{},{},{},{},{}".format( - n, - np.mean(global_active_sims), - np.mean(global_inactive_sims), - np.max(global_active_sims), - np.max(global_inactive_sims), - ) - ) + if ( + self._is_adaptivity_on + and n % self._adaptivity_output_n == 0 + and self._rank == 0 + ): + self._adaptivity_controller.log_metrics( + self._adaptivity_logger, adaptivity_data, n + ) + self._logger.log_info_one_rank("Time window {} converged.".format(n)) self._participant.finalize() @@ -466,7 +453,7 @@ def initialize(self) -> None: if self._is_adaptivity_on: if self._adaptivity_type == "local": self._adaptivity_controller: LocalAdaptivityCalculator = ( - LocalAdaptivityCalculator(self._config) + LocalAdaptivityCalculator(self._config, self._comm) ) self._number_of_sims_for_adaptivity = self._local_number_of_sims elif self._adaptivity_type == "global": From d380dc004c5ddfe1afbbea9302137ca02d5ff773 Mon Sep 17 00:00:00 2001 From: Ishaan Desai Date: Mon, 16 Dec 2024 12:09:21 +0100 Subject: [PATCH 17/17] Fix unit tests --- tests/unit/test_adaptivity_serial.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/test_adaptivity_serial.py b/tests/unit/test_adaptivity_serial.py index 72349f39..e2ac83d6 100644 --- a/tests/unit/test_adaptivity_serial.py +++ b/tests/unit/test_adaptivity_serial.py @@ -235,7 +235,7 @@ def test_update_inactive_sims_local_adaptivity(self): """ configurator = MagicMock() configurator.get_adaptivity_similarity_measure = MagicMock(return_value="L1") - adaptivity_controller = LocalAdaptivityCalculator(configurator) + adaptivity_controller = LocalAdaptivityCalculator(configurator, MagicMock()) adaptivity_controller._refine_const = self._refine_const adaptivity_controller._coarse_const = self._coarse_const adaptivity_controller._adaptivity_data_names = [