Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add logger #207

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
3 changes: 3 additions & 0 deletions config_files/main.toml
Original file line number Diff line number Diff line change
Expand Up @@ -181,3 +181,6 @@ protocol_compliance_lockdown_length_reduction = 0.5
Pfizer = 0.913
Moderna = 0.941
AZ = 0.76

[logging_info]
level = "INFO"
3 changes: 3 additions & 0 deletions cv19/logger/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .logger import Logger

__all__ = ['Logger']
81 changes: 81 additions & 0 deletions cv19/logger/logger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import logging
import sys
from logging import handlers


class Logger(object):
"""
Singleton Logger class. This class is only instantiated ONCE. It is to keep a consistent
criteria for the logger throughout the application if need be called upon.
It serves as the criteria for initiating logger for modules. It creates child loggers.
It's important to note these are child loggers as any changes made to the root logger
can be done.
"""

_instance = None

def __new__(cls):
"""Instantiates the logger or returns same instance.
Returns:
logging handler object : the log file
"""
if cls._instance is None:
cls._instance = super().__new__(cls)
cls.debug_mode = True
cls.formatter = logging.Formatter(
"%(asctime)s — %(name)s — %(levelname)s — %(message)s"
)
cls.log_file = "log_file.log"

return cls._instance

def get_console_handler(self):
"""Defines a console handler to come out on the console.
Returns:
logging handler object : the console handler
"""
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setFormatter(self.formatter)
console_handler.name = "consoleHandler"
return console_handler

def get_file_handler(self):
"""Defines a file handler to come out on the console.
Returns:
logging handler object : the console handler
"""
file_handler = handlers.RotatingFileHandler(
self.log_file, backupCount=1
)
file_handler.setFormatter(self.formatter)
file_handler.name = "fileHandler"
return file_handler

def add_handlers(self, logger, handler_list: list):
"""Adds handlers to the logger, checks first if handlers exist to avoid
duplication
Args:
logger: Logger to check handlers
handler_list: list of handlers to add
"""
existing_handler_names = []
for existing_handler in logger.handlers:
existing_handler_names.append(existing_handler.name)

for new_handler in handler_list:
if new_handler.name not in existing_handler_names:
logger.addHandler(new_handler)

def get_logger(self, logger_name: str):
"""Generates logger for use in the modules.
Args:
logger_name (string): name of the logger
Returns:
logger: returns logger for module
"""
logger = logging.getLogger(logger_name)
console_handler = self.get_console_handler()
file_handler = self.get_file_handler()
self.add_handlers(logger, [console_handler, file_handler])
logger.propagate = False
return logger
27 changes: 10 additions & 17 deletions cv19/parallel.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from .simulation import Simulation


def async_simulation(config_file, config_dir="", config_override_data=None, verbose=False):
def async_simulation(config_file, config_dir="", config_override_data=None):
"""Does a single run of the simulation with the supplied configuration details.

Parameters
Expand All @@ -23,8 +23,6 @@ def async_simulation(config_file, config_dir="", config_override_data=None, verb
config_override_data : dict of dict
Dictionary containing instances of configuration files that are used to override the
default parameters loaded.
verbose : bool, default False
Whether to output information from each day of the simulation.

Returns
-------
Expand All @@ -33,14 +31,13 @@ def async_simulation(config_file, config_dir="", config_override_data=None, verb
"""

sim = Simulation(config_file=config_file, config_dir=config_dir,
config_override_data=config_override_data, verbose=verbose)
config_override_data=config_override_data)

sim.run()
return sim.get_arrays()


def run_async(num_runs, config_file, save_name=None, num_cores=-1, config_dir="", config_override_data=None,
verbose=False):
def run_async(num_runs, config_file, save_name=None, num_cores=-1, config_dir="", config_override_data=None):
"""Runs multiple simulations in parallel using the supplied configuration settings.

Parameters
Expand All @@ -58,8 +55,6 @@ def run_async(num_runs, config_file, save_name=None, num_cores=-1, config_dir=""
A dictionary of configuration file instances that can be used to override the files
specified in the main configuration file. Designed to allow tabular mode to edit parameters
in configuration files other than main.
verbose : bool, default False
Whether to output information from each day of the simulation.

Returns
-------
Expand All @@ -73,7 +68,8 @@ def run_async(num_runs, config_file, save_name=None, num_cores=-1, config_dir=""
# Run all of the simulations
multiprocessing.freeze_support()
with multiprocessing.Pool(processes=num_cores) as pool:
results = pool.starmap(async_simulation, ((config_file, config_dir, config_override_data, verbose)

results = pool.starmap(async_simulation, ((config_file, config_dir, config_override_data)
for _ in range(num_runs)))

df = pd.DataFrame(results)
Expand Down Expand Up @@ -137,7 +133,7 @@ def _config_editor(main_config, disease_config, param_name, value):
raise ValueError(f"The supplied param_name {param_name} is not in any configuration file")


def tabular_mode(base_config_file, independent, dependent, num_runs=8, num_cores=8, save_name=None, verbose=False):
def tabular_mode(base_config_file, independent, dependent, num_runs=8, num_cores=8, save_name=None):
"""Automatically measures the impact of various public health measures on different metrics.

Parameters
Expand Down Expand Up @@ -174,8 +170,6 @@ def tabular_mode(base_config_file, independent, dependent, num_runs=8, num_cores
If using a list, then the list must be exactly as long as the number of values
for the independent variable, and each scenario will be saved under its
corresponding filename. If None, then don't save any results.
verbose : bool, default False
Whether to output information from each day of the simulation.

Returns
-------
Expand Down Expand Up @@ -236,8 +230,9 @@ def tabular_mode(base_config_file, independent, dependent, num_runs=8, num_cores
scenario_save_name = save_name[i]
elif isinstance(save_name, str):
scenario_save_name = save_name + f"{i:02}"

data = run_async(num_runs, temp_main_config, num_cores=num_cores,
save_name=scenario_save_name, config_dir=config_dir, verbose=verbose,
save_name=scenario_save_name, config_dir=config_dir,
config_override_data=config_override_data)

# Processing the results to get the dependent measurements, add to results
Expand Down Expand Up @@ -269,7 +264,7 @@ def tabular_mode(base_config_file, independent, dependent, num_runs=8, num_cores
return results


def confidence_interval(config, parameterstoplot, num_runs=8, confidence=0.80, num_cores=-1, save_name=None, verbose=False):
def confidence_interval(config, parameterstoplot, num_runs=8, confidence=0.80, num_cores=-1, save_name=None):
"""Plots the results of multiple simulations with confidence bands
to give a better understanding of the trend of a given scenario.
Displays a plot of the results.
Expand All @@ -292,11 +287,9 @@ def confidence_interval(config, parameterstoplot, num_runs=8, confidence=0.80, n
save_name: str or None, default None
Name to save the results under. Default None, which means don't save
the results.
verbose : bool, default False
Whether to output information from each day of the simulation.
"""

result = run_async(num_runs, config, num_cores=num_cores, save_name=save_name, verbose=verbose)
result = run_async(num_runs, config, num_cores=num_cores, save_name=save_name)

fig_ci, ax_ci = plt.subplots()
z_score = st.norm.ppf(confidence)
Expand Down
107 changes: 51 additions & 56 deletions cv19/simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import subprocess
from timeit import default_timer as timer
from pathlib import Path
import logging
import tomli

import numpy as np
Expand All @@ -12,6 +13,7 @@
from .population import Population
from .policy import Policy
from .interaction_sites import InteractionSites
from .logger import Logger


class Simulation():
Expand All @@ -23,8 +25,6 @@ class Simulation():

Attributes
----------
verbose : bool
A variable indicating whether to print updates with simulation information while running.
tracking_df : pd.DataFrame
A pandas DataFrame object that stores all the tracking arrays for a given simulation. Each
array is of length nDays.
Expand All @@ -36,7 +36,7 @@ class Simulation():
A variable indicating if this object has run a simulaiton yet.
"""

def __init__(self, config_file, config_dir="", config_override_data=None, verbose=False):
def __init__(self, config_file, config_dir="", config_override_data=None):
""" __init__ method docstring.

Parameters
Expand All @@ -50,8 +50,6 @@ def __init__(self, config_file, config_dir="", config_override_data=None, verbos
A dictionary of configuration file instances that can be used to override the files
specified in the main configuration file. Designed to allow tabular mode to edit parameters
in configuration files other than main.
verbose : bool
A variable indicating whether to print updates with simulation information while running.
"""

self.config_dir = config_dir
Expand All @@ -60,8 +58,6 @@ def __init__(self, config_file, config_dir="", config_override_data=None, verbos

self.init_classes() # Have to initalize the classes after we have all of the parameters

self.verbose = verbose # Whether or not to print daily simulation information.

self.set_code_version() # Set the version of the code being used to run simulation.

self.make_tracking_df()
Expand Down Expand Up @@ -272,6 +268,20 @@ def run(self, fail_on_rerun=True):
run multiple times.
"""

log_level_info = {"DEBUG": logging.DEBUG,
"INFO": logging.INFO,
"WARNING": logging.WARNING,
"ERROR": logging.ERROR}
log_level_config = self.parameters["logging_info"]["level"]
log_level = log_level_info.get(log_level_config)

# Starts logger for file
log = Logger().get_logger(__name__)
logging.root.setLevel(log_level)
logging.captureWarnings(True)
log.info(" %s", '-' * 70)
log.info("Starting simulation...")

# Check whether the simulation has already been run.
if fail_on_rerun:
information = ("When running again, previous results will be overwritten. "
Expand All @@ -280,8 +290,7 @@ def run(self, fail_on_rerun=True):
"fail_on_rerun argument to False.")
self.check_has_run(check=False, information=information, fail=True)

if self.verbose:
print(f"Simulation code version (from git): {self.code_id}\n")
log.info("Simulation code version (from git): %s", self.code_id)

# Get current time for measuring elapsed time of simulation.
beg_time = timer()
Expand All @@ -304,23 +313,23 @@ def run(self, fail_on_rerun=True):

# UPDATE POLICY
mask_mandate = self.policy.update_mask_mandate(day=day)
if mask_mandate != old_mask_mandate and self.verbose:
print(f"Day: {day}, Mask Mandate: {mask_mandate}")
if mask_mandate != old_mask_mandate:
log.info("Day: %i, Mask Mandate: %i", day, mask_mandate)
old_mask_mandate = mask_mandate

lockdown = self.policy.update_lockdown(day=day)
if lockdown != old_lockdown_mandate and self.verbose:
print(f"Day: {day}, Lockdown: {lockdown}")
if lockdown != old_lockdown_mandate:
log.info("Day: %i, Lockdown: %i", day, lockdown)
old_lockdown_mandate = lockdown

testing_ON = self.policy.update_testing(day)
if testing_ON != old_testing_mandate and self.verbose:
print(f"Day: {day}, Testing: {testing_ON}")
if testing_ON != old_testing_mandate:
log.info("Day: %i, Testing: %i", day, testing_ON)
old_testing_mandate = testing_ON

students_go = self.policy.check_students(day=day)
if students_go != old_student_mandate and self.verbose:
print(f"Day: {day}, Uni Mandate: {students_go}")
if students_go != old_student_mandate:
log.info("Day: %i, Uni Mandate: %i", day, students_go)
old_student_mandate = students_go

# infect random students on the day they come in
Expand Down Expand Up @@ -406,45 +415,31 @@ def run(self, fail_on_rerun=True):

self.tracking_df.at[day, "time"] = timer() - beg_time

if self.verbose:
print((f"Day: {day}, "
f"infected: {self.tracking_df.at[day, 'infected']}, "
f"recovered: {self.tracking_df.at[day, 'recovered']}, "
f"susceptible: {self.tracking_df.at[day, 'susceptible']}, "
f"dead: {self.tracking_df.at[day, 'dead']}, "
f"hospitalized: {self.tracking_df.at[day, 'hospitalized']}, "
f"ICU: {self.tracking_df.at[day, 'ICU']}, "
f"tested: {self.tracking_df.at[day, 'tested']}, "
f"total quarantined: {self.tracking_df.at[day, 'quarantined']}, "
f"infected students: {self.tracking_df.at[day, 'inf_students']}, "
f"vaccinated: {self.tracking_df.at[day, 'vaccinated']}"))

# Print variants
print("Variants", end=": ")
for key, val in self.track_virus_types.items():
print(f"{key}:{val[day]}", end=", ")
print("\n")

if self.verbose:
time_seconds = timer() - beg_time
m, s = divmod(time_seconds, 60)
h, m = divmod(m, 60)
print(f"{'':-<80}")
print("Simulation summary:")
print(f" Time elapsed: {h:02.0f}:{m:02.0f}:{s:02.0f}")
print(f" {self.tracking_df['susceptible'].iloc[-1]} never got it")
print(f" {self.tracking_df['dead'].iloc[-1]} died")
print(f" {self.tracking_df['infected'].max()} had it at the peak")
print(f" {self.tracking_df.at[day, 'tested']} were tested")
print(f" {self.tracking_df['quarantined'].max()} were in quarantine at the peak")
print(f" {self.tracking_df['hospitalized'].max()} at peak hospitalizations")
print(f" {self.tracking_df['dead'].max()} at peak deaths")
print(" The breakdown of the variants is", end=": ")
for key, val in self.track_virus_types.items():
print(f"{key}-{np.max(val)}", end=", ")
print("")
print(f" {self.tracking_df.at[day, 'vaccinated']} people were vaccinated")
print(f" {self.tracking_df.at[day, 'vaccinated']/self.nPop*100:.2f}% of population was vaccinated.")
log.debug("Day: %i, infected: %i, recovered: %i, susceptible: %i, dead: %i, hospitalized: %i, ICU: %i, tested: %i, total quarantined: %i, infected students: %i, vaccinated: %i",
day, self.tracking_df.at[day, 'infected'], self.tracking_df.at[day, 'recovered'],
self.tracking_df.at[day, 'susceptible'], self.tracking_df.at[day, 'dead'],
self.tracking_df.at[day, 'hospitalized'], self.tracking_df.at[day, 'ICU'],
self.tracking_df.at[day, 'tested'], self.tracking_df.at[day, 'quarantined'],
self.tracking_df.at[day, 'inf_students'], self.tracking_df.at[day, 'vaccinated'])

time_seconds = timer() - beg_time
m, s = divmod(time_seconds, 60)
h, m = divmod(m, 60)

log.info("Simulation summary:")
log.info(" Time elapsed : %02.0f:%02.0f:%02.0f", h, m, s)
log.info(" %i never got it", self.tracking_df['susceptible'].iloc[-1])
log.info(" %i died", self.tracking_df['dead'].iloc[-1])
log.info(" %i had it at the peak", self.tracking_df['infected'].max())
log.info(" %i were tested", self.tracking_df.at[day, 'tested'])
log.info(" %i were in quarantine at the peak", self.tracking_df['quarantined'].max())
log.info(" %i at peak hospitalizations", self.tracking_df['hospitalized'].max())
log.info(" %i at peak deaths", self.tracking_df['dead'].max())
log.info("The breakdown of the variants is:")
for key, val in self.track_virus_types.items():
log.info(" %s - %i", key, np.max(val))
log.info(" %i people were vaccinated", self.tracking_df.at[day, 'vaccinated'])
log.info(" %0.2f percent of population was vaccinated.", self.tracking_df.at[day, 'vaccinated'] / self.nPop * 100)

# Unpack the virus types into the dataframe
for virus_type, virus_type_arr in self.track_virus_types.items():
Expand Down
4 changes: 2 additions & 2 deletions test/test_Simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@ def setUp(self):
simulation objects used for testing.
"""
quarantine_config_file_1 = str(Path(Path(__file__).parent, "testing_config_files/main_quarantine_1.toml").resolve())
self.quarantine_obj_1 = Simulation(quarantine_config_file_1, verbose=False)
self.quarantine_obj_1 = Simulation(quarantine_config_file_1)
self.quarantine_obj_1.run()

quarantine_config_file_2 = str(Path(Path(__file__).parent, "testing_config_files/main_quarantine_2.toml").resolve())
self.quarantine_obj_2 = Simulation(quarantine_config_file_2, verbose=False)
self.quarantine_obj_2 = Simulation(quarantine_config_file_2)
self.quarantine_obj_2.run()

def tearDown(self):
Expand Down
Loading