diff --git a/micro_manager/adaptivity/adaptivity.py b/micro_manager/adaptivity/adaptivity.py index b124050..bf0fa3c 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 90690ec..0e30c7a 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 a2defb8..54f5eb7 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 b6eb7cd..11884db 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 cd5a5e8..a438228 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 9e8d54b..b9c3f78 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 0000000..f77817b --- /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)