From dc3675e04dc4cfa2e945ed5fc7539ee7be899d88 Mon Sep 17 00:00:00 2001 From: Dhruv Sondhi <66117751+DhruvSondhi@users.noreply.github.com> Date: Thu, 17 Jun 2021 21:18:03 +0530 Subject: [PATCH] Formatting Logging Output for Simulation (#1632) * Formatting Logging Output for Simulation runs via Notebook * Reverting removed import * Fixing decimal precision for luminosity values * Formatting changes for the Plasma Stratification values displayed * Allowing pandas dataframe to be displayed in Notebook as well as Terminal * Removing redundant pandas setting * Adding import for get_ipython() * Moving simulation environment checking to another PR & made appropriate changes * Added some more formatting changes to other logging messages for consistency * Renamed check_simulation_env() to is_notebook(), Added better conditional checks for the environment using isinstance() * Formatting changed for Simulation message about no of iterations & time taken * Added Tests for checking the logging done while running the simulation --- tardis/io/atom_data/base.py | 4 +- tardis/io/atom_data/util.py | 2 +- tardis/plasma/standard_plasmas.py | 2 +- tardis/simulation/base.py | 43 +++++++++++++++------- tardis/simulation/tests/test_simulation.py | 19 ++++++++++ tardis/util/base.py | 35 ++++++++++++++---- 6 files changed, 80 insertions(+), 25 deletions(-) diff --git a/tardis/io/atom_data/base.py b/tardis/io/atom_data/base.py index a9e099b80fa..d9bae601f8e 100644 --- a/tardis/io/atom_data/base.py +++ b/tardis/io/atom_data/base.py @@ -186,11 +186,11 @@ def from_hdf(cls, fname=None): # ToDo: strore data sources as attributes in carsus logger.info( - f"Read Atom Data with UUID={atom_data.uuid1} and MD5={atom_data.md5}." + f"\n\tReading Atom Data with:\n\tUUID = {atom_data.uuid1}\n\tMD5 = {atom_data.md5} " ) if nonavailable: logger.info( - f'Non provided atomic data: {", ".join(nonavailable)}' + f'\n\tNon provided atomic data:\n\t{", ".join(nonavailable)}' ) return atom_data diff --git a/tardis/io/atom_data/util.py b/tardis/io/atom_data/util.py index c4a79879802..ad47659ca21 100644 --- a/tardis/io/atom_data/util.py +++ b/tardis/io/atom_data/util.py @@ -31,7 +31,7 @@ def resolve_atom_data_fname(fname): fpath = os.path.join(os.path.join(get_data_dir(), fname)) if os.path.exists(fpath): logger.info( - f"Atom Data {fname} not found in local path. Exists in TARDIS Data repo {fpath}" + f"\n\tAtom Data {fname} not found in local path.\n\tExists in TARDIS Data repo {fpath}" ) return fpath diff --git a/tardis/plasma/standard_plasmas.py b/tardis/plasma/standard_plasmas.py index a1ea294e79a..f3600cb666b 100644 --- a/tardis/plasma/standard_plasmas.py +++ b/tardis/plasma/standard_plasmas.py @@ -88,7 +88,7 @@ def assemble_plasma(config, model, atom_data=None): else: raise ValueError("No atom_data option found in the configuration.") - logger.info("Reading Atomic Data from %s", atom_data_fname) + logger.info(f"\n\tReading Atomic Data from {atom_data_fname}") try: atom_data = AtomData.from_hdf(atom_data_fname) diff --git a/tardis/simulation/base.py b/tardis/simulation/base.py index 977d03739a7..61ca133c3e2 100644 --- a/tardis/simulation/base.py +++ b/tardis/simulation/base.py @@ -10,7 +10,9 @@ from tardis.plasma.standard_plasmas import assemble_plasma from tardis.io.util import HDFWriterMixin from tardis.io.config_reader import ConfigurationError +from tardis.util.base import is_notebook from tardis.montecarlo import montecarlo_configuration as mc_config_module +from IPython.display import display # Adding logging support logger = logging.getLogger(__name__) @@ -320,7 +322,7 @@ def advance_state(self): def iterate(self, no_of_packets, no_of_virtual_packets=0, last_run=False): logger.info( - f"Starting iteration {(self.iterations_executed + 1):d}/{self.iterations:d}" + f"\n\tStarting iteration {(self.iterations_executed + 1):d} of {self.iterations:d}" ) self.runner.run( self.model, @@ -348,6 +350,7 @@ def run(self): """ run the simulation """ + start_time = time.time() while self.iterations_executed < self.iterations - 1: self.store_plasma_state( @@ -378,8 +381,8 @@ def run(self): self.reshape_plasma_state_store(self.iterations_executed) logger.info( - f"Simulation finished in {self.iterations_executed:d} iterations " - f"and took {(time.time() - start_time):.2f} s" + f"\n\tSimulation finished in {self.iterations_executed:d} iterations " + f"\n\tSimulation took {(time.time() - start_time):.2f} s\n" ) self._call_back() @@ -421,23 +424,35 @@ def log_plasma_state( plasma_state_log["next_t_rad"] = next_t_rad plasma_state_log["w"] = w plasma_state_log["next_w"] = next_w + plasma_state_log.columns.name = "Shell No." - plasma_state_log.index.name = "Shell" - - plasma_state_log = str(plasma_state_log[::log_sampling]) + if is_notebook(): + logger.info("\n\tPlasma stratification:") + logger.info( + display( + plasma_state_log.iloc[::log_sampling].style.format("{:.3g}") + ) + ) + else: + output_df = "" + plasma_output = plasma_state_log.iloc[::log_sampling].to_string( + float_format=lambda x: "{:.3g}".format(x), + justify="center", + ) + for value in plasma_output.split("\n"): + output_df = output_df + "\t{}\n".format(value) + logger.info("\n\tPlasma stratification:") + logger.info(f"\n{output_df}") - plasma_state_log = "".join( - ["\t%s\n" % item for item in plasma_state_log.split("\n")] + logger.info( + f"\n\tCurrent t_inner = {t_inner:.3f}\n\tExpected t_inner for next iteration = {next_t_inner:.3f}\n" ) - logger.info("Plasma stratification:\n%s\n", plasma_state_log) - logger.info(f"t_inner {t_inner:.3f} -- next t_inner {next_t_inner:.3f}") - def log_run_results(self, emitted_luminosity, absorbed_luminosity): logger.info( - f"Luminosity emitted = {emitted_luminosity:.5e} " - f"Luminosity absorbed = {absorbed_luminosity:.5e} " - f"Luminosity requested = {self.luminosity_requested:.5e}" + f"\n\tLuminosity emitted = {emitted_luminosity:.3e}\n" + f"\tLuminosity absorbed = {absorbed_luminosity:.3e}\n" + f"\tLuminosity requested = {self.luminosity_requested:.3e}\n" ) def _call_back(self): diff --git a/tardis/simulation/tests/test_simulation.py b/tardis/simulation/tests/test_simulation.py index 9c473ccabd7..567be2f5aba 100644 --- a/tardis/simulation/tests/test_simulation.py +++ b/tardis/simulation/tests/test_simulation.py @@ -1,6 +1,8 @@ import os import pytest +import logging + from tardis.io.config_reader import Configuration from tardis.simulation import Simulation @@ -153,3 +155,20 @@ def test_plasma_state_storer_reshape( # assert_quantity_allclose( # t_rad, simulation_compare_data['test1/t_rad'] * u.Unit('K'), atol=0.0 * u.Unit('K')) + + +def test_logging_simulation(atomic_data_fname, caplog): + """ + Testing the logs for simulations runs + """ + config = Configuration.from_yaml( + "tardis/io/tests/data/tardis_configv1_verysimple.yml" + ) + config["atom_data"] = atomic_data_fname + + simulation = Simulation.from_config(config) + + simulation.run() + + for record in caplog.records: + assert record.levelno >= logging.INFO diff --git a/tardis/util/base.py b/tardis/util/base.py index 537ff5aa36a..1a7616acceb 100644 --- a/tardis/util/base.py +++ b/tardis/util/base.py @@ -542,23 +542,44 @@ def convert_abundances_format(fname, delimiter=r"\s+"): return df -def check_simulation_env(): +def is_notebook(): """ - Checking the environment where the simulation is run + Checking the shell environment where the simulation is run is Jupyter based Returns ------- - True : if the environment is IPython Based - False : if the environment is Terminal or anything else + True : if the shell environment is IPython Based + False : if the shell environment is Terminal or anything else """ try: - shell = get_ipython().__class__.__name__ + # Trying to import the ZMQInteractiveShell for Jupyter based environments + from ipykernel.zmqshell import ZMQInteractiveShell except NameError: + # If the class cannot be imported then we are automatically return False Value + # Raised due to Name Error with the imported Class return False - if shell == "ZMQInteractiveShell": + try: + # Trying to import Interactive Terminal based IPython shell + from IPython.core.interactiveshell import InteractiveShell + except NameError: + # If the class cannot be imported then we are automatically return False Value + # Raised due to Name Error with the imported Class + return False + + try: + # Trying to get the value of the shell via the get_ipython() method + shell = get_ipython() + except NameError: + # Returns False if the shell name cannot be inferred correctly + return False + + # Checking if the shell instance is Jupyter based & if True, returning True + if isinstance(shell, ZMQInteractiveShell): return True - elif shell == "TerminalInteractiveShell": + # Checking if the shell instance is Terminal IPython based & if True, returning False + elif isinstance(shell, InteractiveShell): return False + # All other shell instances are returned False else: return False