From 28ec2dcf7e1272b90c1018019091413636a44c60 Mon Sep 17 00:00:00 2001 From: willGraham01 <1willgraham@gmail.com> Date: Mon, 26 Jun 2023 10:59:41 +0100 Subject: [PATCH 01/11] Add main() and __name__=="__main__" to scale_run.py - Allows us to import the scale_run as a function for profiling. - Allows us to record the input values to the scale run in a script for repeatability --- src/scripts/profiling/benchmark_scale_run.py | 85 ++++ src/scripts/profiling/scale_run.py | 432 ++++++++++--------- 2 files changed, 325 insertions(+), 192 deletions(-) create mode 100644 src/scripts/profiling/benchmark_scale_run.py diff --git a/src/scripts/profiling/benchmark_scale_run.py b/src/scripts/profiling/benchmark_scale_run.py new file mode 100644 index 0000000000..75aa1a8cb1 --- /dev/null +++ b/src/scripts/profiling/benchmark_scale_run.py @@ -0,0 +1,85 @@ +""" +Benchmarking script for scale_run.py. +""" +import datetime +import os +from pathlib import Path +import warnings + +from pyinstrument import Profiler +from pyinstrument.renderers import HTMLRenderer + +from scale_run import main as sc_run + + +def main() -> None: + warnings.filterwarnings("ignore") + + LOCATION_OF_THIS_FILE = os.path.dirname(os.path.abspath(__file__)) + TLO_ROOT = (Path(LOCATION_OF_THIS_FILE) / ".." / ".." / "..").resolve() + + # Setup the profiler, to record the stack every interval seconds + p = Profiler(interval=1e-3) + + # Decide on the parameters to pass to scale_run + # p has not been started, so these are not part of the profiling output + years = 0 + months = 1 + initial_population = 50000 + tlo_dir = TLO_ROOT + output_dir = (TLO_ROOT / "outputs").resolve() + log_filename = "for_profiling" + log_level = "DEBUG" + parse_log_file = False + show_progress_bar = True + seed = 0 + disable_health_system = False + disable_spurious_symptoms = False + capabilities_coefficient = None + mode_appt_constraints = 2 + save_final_population = False + record_hsi_event_details = False + + # Start the profiler and perform the run + p.start() + sc_run( + years, + months, + initial_population, + tlo_dir, + output_dir, + log_filename, + log_level, + parse_log_file, + show_progress_bar, + seed, + disable_health_system, + disable_spurious_symptoms, + capabilities_coefficient, + mode_appt_constraints, + save_final_population, + record_hsi_event_details, + ) + profiled_session = p.stop() + # Remove __main__ call from this script, from the output stack + profiled_session.root_frame(trim_stem=True) + + # Parse results into HTML + # show_all: removes library calls where identifiable + # timeline: if true, samples are left in chronological order rather than total time + html_renderer = HTMLRenderer(show_all=False, timeline=False) + + # Parse output and write to file + output_html_file = output_dir / ( + datetime.datetime.utcnow().strftime("%Y-%m-%d_%H%M") + "_scale_run_profile.html" + ) + print(f"Writing output to: {output_html_file}", end="...", flush=True) + with open(output_html_file, "w") as f: + f.write(html_renderer.render(profiled_session)) + print("done") + + return + + +if __name__ == "__main__": + main() diff --git a/src/scripts/profiling/scale_run.py b/src/scripts/profiling/scale_run.py index 0e02bb70b2..e7a17aa810 100644 --- a/src/scripts/profiling/scale_run.py +++ b/src/scripts/profiling/scale_run.py @@ -14,6 +14,7 @@ import os import warnings from pathlib import Path +from typing import Literal import pandas as pd import shared @@ -22,197 +23,244 @@ from tlo.analysis.utils import parse_log_file from tlo.methods.fullmodel import fullmodel -# Parse arguments defining run options -parser = argparse.ArgumentParser(description="Run model at scale") -parser.add_argument( - "--years", - type=int, - help="Number of years to simulate for (plus any months specified by --months)", - default=20 -) -parser.add_argument( - "--months", - type=int, - help="Number of months to simulate for (plus any years specified by --years)", - default=0 -) -parser.add_argument( - "--initial-population", - type=int, - help="Initial population size", - default=50000 -) -parser.add_argument( - "--tlo-dir", - type=Path, - help="Root TLOmodel directory", - default="." -) -parser.add_argument( - "--output-dir", - type=Path, - help="Directory to write output to", - default="./outputs" -) -parser.add_argument( - "--log-filename", type=str, help="Filename to use for log", default="for_profiling" -) -parser.add_argument( - "--log-level", - type=str, - help="Level to log at", - choices=("CRITICAL", "DEBUG", "FATAL", "WARNING", "INFO"), - default="WARNING" -) -parser.add_argument( - "--parse-log-file", - help=( - "Parse log file to create log dataframe at end of simulation (only useful with " - "interactive -i runs)" - ), - action="store_true", -) -parser.add_argument( - "--show-progress-bar", - help="Show progress bar during simulation rather than log output", - action="store_true", -) -parser.add_argument( - "--seed", - help="Seed for base pseudo-random number generator", - type=int, - default=0, -) -parser.add_argument( - "--disable-health-system", - help=( - "Disable health system - i.e. no processing happens by the health system but " - "all HSI Events run" - ), - action="store_true" -) -parser.add_argument( - "--disable-spurious-symptoms", - help="Disable the generation of spurious symptoms in SymptomManager", - action="store_true" -) -parser.add_argument( - "--capabilities-coefficient", - help=( - "Capabilities coefficient to use in HealthSystem. If not specified the ratio of" - " the initial population to the estimated 2010 population will be used." - ), - type=float, - default=None, -) -parser.add_argument( - "--mode-appt-constraints", - help=( - "Mode of constraints to use in HealthSystem (0: no constraints - all events " - "run with no squeeze factor, 1: elastic, all events run with squeeze factor, " - "2: hard, only events with no squeeze factor run" - ), - choices=(0, 1, 2), - type=int, - default=2, -) -parser.add_argument( - "--ignore-warnings", - help=( - "Ignore any warnings (prevents warning messages being printed). Useful when " - "combined with --show-progress-bar to avoid disruption of progress bar display" - ), - action="store_true" -) -parser.add_argument( - "--save-args-json", - help="Save the parsed arguments to a JSON file", - action="store_true" -) -parser.add_argument( - "--save-final-population", - help="Save the final population dataframe to a pickle file", - action="store_true" -) -parser.add_argument( - "--record-hsi-event-details", - help=( - "Keep a record of set of non-target specific details of HSI events that are " - "run and output to a JSON file 'hsi_event_details.json' in output directory." - ), - action="store_true" -) -args = parser.parse_args() - -if args.ignore_warnings: - warnings.filterwarnings("ignore") - -if not os.path.exists(args.output_dir): - os.makedirs(args.output_dir) - -if args.save_args_json: - # Save arguments to a JSON file - with open(args.output_dir / "arguments.json", "w") as f: - args_dict = { - k: str(v) if isinstance(v, Path) else v for k, v in vars(args).items() - } - json.dump(args_dict, f, indent=4) - -# Simulation period -start_date = Date(2010, 1, 1) -end_date = start_date + pd.DateOffset(years=args.years, months=args.months) - -# The resource files -resourcefilepath = Path(args.tlo_dir / "resources") - -log_config = { - "filename": args.log_filename, - "directory": args.output_dir, - "custom_levels": {"*": getattr(logging, args.log_level)} -} - -sim = Simulation( - start_date=start_date, - seed=args.seed, - log_config=log_config, - show_progress_bar=args.show_progress_bar -) - -# Register the appropriate modules with the arguments passed through -sim.register( - *fullmodel( - resourcefilepath=resourcefilepath, - use_simplified_births=False, - module_kwargs={ - "HealthSystem": { - "disable": args.disable_health_system, - "mode_appt_constraints": args.mode_appt_constraints, - "capabilities_coefficient": args.capabilities_coefficient, - "hsi_event_count_log_period": "simulation" if args.record_hsi_event_details else None + +def main( + years: int, + months: int, + initial_population: int, + tlo_dir: Path, + output_dir: Path, + log_filename: str, + log_level: Literal["CRITICAL", "DEBUG", "FATAL", "WARNING", "INFO"], + parse_log_file: bool, + show_progress_bar: bool, + seed: int, + disable_health_system: bool, + disable_spurious_symptoms: bool, + capabilities_coefficient: float, + mode_appt_constraints: Literal[0, 1, 2], + save_final_population: bool, + record_hsi_event_details: bool, +) -> None: + """ + A run of the full model at scale using all disease modules considered complete and all + modules for birth / labour / newborn outcome. + """ + + # Simulation period + start_date = Date(2010, 1, 1) + end_date = start_date + pd.DateOffset(years=years, months=months) + + # The resource files + resourcefilepath = Path(tlo_dir / "resources") + + log_config = { + "filename": log_filename, + "directory": output_dir, + "custom_levels": {"*": getattr(logging, log_level)}, + } + + sim = Simulation( + start_date=start_date, + seed=seed, + log_config=log_config, + show_progress_bar=show_progress_bar, + ) + + # Register the appropriate modules with the arguments passed through + sim.register( + *fullmodel( + resourcefilepath=resourcefilepath, + use_simplified_births=False, + module_kwargs={ + "HealthSystem": { + "disable": disable_health_system, + "mode_appt_constraints": mode_appt_constraints, + "capabilities_coefficient": capabilities_coefficient, + "hsi_event_count_log_period": "simulation" + if record_hsi_event_details + else None, + }, + "SymptomManager": {"spurious_symptoms": not disable_spurious_symptoms}, }, - "SymptomManager": {"spurious_symptoms": not args.disable_spurious_symptoms}, - } - ) -) - -# Run the simulation -sim.make_initial_population(n=args.initial_population) -shared.schedule_profile_log(sim) -sim.simulate(end_date=end_date) -shared.print_checksum(sim) - -if args.save_final_population: - sim.population.props.to_pickle(args.output_dir / "final_population.pkl") - -if args.parse_log_file: - log_df = parse_log_file(sim.log_filepath) - -if args.record_hsi_event_details: - with open(args.output_dir / "hsi_event_details.json", "w") as json_file: - json.dump( - [ - event_details._asdict() - for event_details in sim.modules['HealthSystem'].hsi_event_counts.keys() - ], - json_file ) + ) + + # Run the simulation + sim.make_initial_population(n=initial_population) + shared.schedule_profile_log(sim) + sim.simulate(end_date=end_date) + shared.print_checksum(sim) + + if save_final_population: + sim.population.props.to_pickle(output_dir / "final_population.pkl") + + if parse_log_file: + log_df = parse_log_file(sim.log_filepath) + + if record_hsi_event_details: + with open(output_dir / "hsi_event_details.json", "w") as json_file: + json.dump( + [ + event_details._asdict() + for event_details in sim.modules[ + "HealthSystem" + ].hsi_event_counts.keys() + ], + json_file, + ) + return + + +if __name__ == "__main__": + # Parse arguments defining run options + parser = argparse.ArgumentParser(description="Run model at scale") + parser.add_argument( + "--years", + type=int, + help="Number of years to simulate for (plus any months specified by --months)", + default=20, + ) + parser.add_argument( + "--months", + type=int, + help="Number of months to simulate for (plus any years specified by --years)", + default=0, + ) + parser.add_argument( + "--initial-population", type=int, help="Initial population size", default=50000 + ) + parser.add_argument( + "--tlo-dir", type=Path, help="Root TLOmodel directory", default="." + ) + parser.add_argument( + "--output-dir", + type=Path, + help="Directory to write output to", + default="./outputs", + ) + parser.add_argument( + "--log-filename", + type=str, + help="Filename to use for log", + default="for_profiling", + ) + parser.add_argument( + "--log-level", + type=str, + help="Level to log at", + choices=("CRITICAL", "DEBUG", "FATAL", "WARNING", "INFO"), + default="WARNING", + ) + parser.add_argument( + "--parse-log-file", + help=( + "Parse log file to create log dataframe at end of simulation (only useful with " + "interactive -i runs)" + ), + action="store_true", + ) + parser.add_argument( + "--show-progress-bar", + help="Show progress bar during simulation rather than log output", + action="store_true", + ) + parser.add_argument( + "--seed", + help="Seed for base pseudo-random number generator", + type=int, + default=0, + ) + parser.add_argument( + "--disable-health-system", + help=( + "Disable health system - i.e. no processing happens by the health system but " + "all HSI Events run" + ), + action="store_true", + ) + parser.add_argument( + "--disable-spurious-symptoms", + help="Disable the generation of spurious symptoms in SymptomManager", + action="store_true", + ) + parser.add_argument( + "--capabilities-coefficient", + help=( + "Capabilities coefficient to use in HealthSystem. If not specified the ratio of" + " the initial population to the estimated 2010 population will be used." + ), + type=float, + default=None, + ) + parser.add_argument( + "--mode-appt-constraints", + help=( + "Mode of constraints to use in HealthSystem (0: no constraints - all events " + "run with no squeeze factor, 1: elastic, all events run with squeeze factor, " + "2: hard, only events with no squeeze factor run" + ), + choices=(0, 1, 2), + type=int, + default=2, + ) + parser.add_argument( + "--ignore-warnings", + help=( + "Ignore any warnings (prevents warning messages being printed). Useful when " + "combined with --show-progress-bar to avoid disruption of progress bar display" + ), + action="store_true", + ) + parser.add_argument( + "--save-args-json", + help="Save the parsed arguments to a JSON file", + action="store_true", + ) + parser.add_argument( + "--save-final-population", + help="Save the final population dataframe to a pickle file", + action="store_true", + ) + parser.add_argument( + "--record-hsi-event-details", + help=( + "Keep a record of set of non-target specific details of HSI events that are " + "run and output to a JSON file 'hsi_event_details.json' in output directory." + ), + action="store_true", + ) + args = parser.parse_args() + + if args.ignore_warnings: + warnings.filterwarnings("ignore") + + if not os.path.exists(args.output_dir): + os.makedirs(args.output_dir) + + if args.save_args_json: + # Save arguments to a JSON file + with open(args.output_dir / "arguments.json", "w") as f: + args_dict = { + k: str(v) if isinstance(v, Path) else v for k, v in vars(args).items() + } + json.dump(args_dict, f, indent=4) + + main( + args.years, + args.months, + args.initial_population, + args.tlo_dir, + args.output_dir, + args.log_filename, + args.log_level, + args.parse_log_file, + args.show_progress_bar, + args.seed, + args.disable_health_system, + args.disable_spurious_symptoms, + args.capabilities_coefficient, + args.mode_appt_constraints, + args.save_final_population, + args.record_hsi_event_details, + ) From 7cb2c5f1e580d1bfc243e79d8ab67d0eaf1a4ffd Mon Sep 17 00:00:00 2001 From: willGraham01 <1willgraham@gmail.com> Date: Mon, 26 Jun 2023 14:17:17 +0100 Subject: [PATCH 02/11] Write benchmarking script for scale_run - Benchmarking script can be called via Python and will write the profiling results to a HTML file --- src/scripts/profiling/benchmark_scale_run.py | 18 +++++++++--------- src/scripts/profiling/scale_run.py | 7 +++++++ 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/scripts/profiling/benchmark_scale_run.py b/src/scripts/profiling/benchmark_scale_run.py index 75aa1a8cb1..bb2a6d9dbb 100644 --- a/src/scripts/profiling/benchmark_scale_run.py +++ b/src/scripts/profiling/benchmark_scale_run.py @@ -18,9 +18,6 @@ def main() -> None: LOCATION_OF_THIS_FILE = os.path.dirname(os.path.abspath(__file__)) TLO_ROOT = (Path(LOCATION_OF_THIS_FILE) / ".." / ".." / "..").resolve() - # Setup the profiler, to record the stack every interval seconds - p = Profiler(interval=1e-3) - # Decide on the parameters to pass to scale_run # p has not been started, so these are not part of the profiling output years = 0 @@ -28,7 +25,7 @@ def main() -> None: initial_population = 50000 tlo_dir = TLO_ROOT output_dir = (TLO_ROOT / "outputs").resolve() - log_filename = "for_profiling" + log_filename = "scale_run_benchmark" log_level = "DEBUG" parse_log_file = False show_progress_bar = True @@ -40,8 +37,10 @@ def main() -> None: save_final_population = False record_hsi_event_details = False + # Setup the profiler, to record the stack every interval seconds + p = Profiler(interval=1e-3) # Start the profiler and perform the run - p.start() + # p.start() sc_run( years, months, @@ -59,20 +58,21 @@ def main() -> None: mode_appt_constraints, save_final_population, record_hsi_event_details, + p, ) - profiled_session = p.stop() - # Remove __main__ call from this script, from the output stack - profiled_session.root_frame(trim_stem=True) + # profiled_session = p.stop() + profiled_session = p.last_session # Parse results into HTML # show_all: removes library calls where identifiable # timeline: if true, samples are left in chronological order rather than total time html_renderer = HTMLRenderer(show_all=False, timeline=False) - # Parse output and write to file output_html_file = output_dir / ( datetime.datetime.utcnow().strftime("%Y-%m-%d_%H%M") + "_scale_run_profile.html" ) + + # Write HTML file print(f"Writing output to: {output_html_file}", end="...", flush=True) with open(output_html_file, "w") as f: f.write(html_renderer.render(profiled_session)) diff --git a/src/scripts/profiling/scale_run.py b/src/scripts/profiling/scale_run.py index e7a17aa810..3580a3c86f 100644 --- a/src/scripts/profiling/scale_run.py +++ b/src/scripts/profiling/scale_run.py @@ -41,12 +41,16 @@ def main( mode_appt_constraints: Literal[0, 1, 2], save_final_population: bool, record_hsi_event_details: bool, + profiler=None, ) -> None: """ A run of the full model at scale using all disease modules considered complete and all modules for birth / labour / newborn outcome. """ + if profiler is not None: + profiler.start() + # Simulation period start_date = Date(2010, 1, 1) end_date = start_date + pd.DateOffset(years=years, months=months) @@ -109,6 +113,9 @@ def main( ], json_file, ) + + if profiler is not None: + profiler.stop() return From c4bf9461572c8f95013b38881bfeac73a8ca124d Mon Sep 17 00:00:00 2001 From: willGraham01 <1willgraham@gmail.com> Date: Mon, 26 Jun 2023 14:24:00 +0100 Subject: [PATCH 03/11] Allow CLI to overwrite default names of outputs --- src/scripts/profiling/benchmark_scale_run.py | 30 ++++++++++++++------ 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/src/scripts/profiling/benchmark_scale_run.py b/src/scripts/profiling/benchmark_scale_run.py index bb2a6d9dbb..47d9a5f362 100644 --- a/src/scripts/profiling/benchmark_scale_run.py +++ b/src/scripts/profiling/benchmark_scale_run.py @@ -1,6 +1,7 @@ """ Benchmarking script for scale_run.py. """ +import argparse import datetime import os from pathlib import Path @@ -12,9 +13,15 @@ from scale_run import main as sc_run -def main() -> None: +def main(output_html_fname: str = None) -> None: warnings.filterwarnings("ignore") + if output_html_fname is None: + output_html_fname = output_dir / ( + datetime.datetime.utcnow().strftime("%Y-%m-%d_%H%M") + + "_scale_run_profile.html" + ) + LOCATION_OF_THIS_FILE = os.path.dirname(os.path.abspath(__file__)) TLO_ROOT = (Path(LOCATION_OF_THIS_FILE) / ".." / ".." / "..").resolve() @@ -68,13 +75,9 @@ def main() -> None: # timeline: if true, samples are left in chronological order rather than total time html_renderer = HTMLRenderer(show_all=False, timeline=False) - output_html_file = output_dir / ( - datetime.datetime.utcnow().strftime("%Y-%m-%d_%H%M") + "_scale_run_profile.html" - ) - # Write HTML file - print(f"Writing output to: {output_html_file}", end="...", flush=True) - with open(output_html_file, "w") as f: + print(f"Writing output to: {output_html_fname}", end="...", flush=True) + with open(output_html_fname, "w") as f: f.write(html_renderer.render(profiled_session)) print("done") @@ -82,4 +85,15 @@ def main() -> None: if __name__ == "__main__": - main() + parser = argparse.ArgumentParser(description="Benchmark scale_run.py script") + parser.add_argument( + "output-html-fname", + nargs="?", + type=str, + default=None, + help="Filename for the output HTML file containing the profiling results." + " Generates a default name using the call timestamp if not set.", + ) + + parser.parse_args() + main(parser.output_html_fname) From 206346e0eb8de22a85856a2fca86e6a788f1cc1b Mon Sep 17 00:00:00 2001 From: willGraham01 <1willgraham@gmail.com> Date: Mon, 26 Jun 2023 14:36:06 +0100 Subject: [PATCH 04/11] Tidy up comments --- src/scripts/profiling/benchmark_scale_run.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/scripts/profiling/benchmark_scale_run.py b/src/scripts/profiling/benchmark_scale_run.py index 47d9a5f362..c6026e5eb5 100644 --- a/src/scripts/profiling/benchmark_scale_run.py +++ b/src/scripts/profiling/benchmark_scale_run.py @@ -25,8 +25,7 @@ def main(output_html_fname: str = None) -> None: LOCATION_OF_THIS_FILE = os.path.dirname(os.path.abspath(__file__)) TLO_ROOT = (Path(LOCATION_OF_THIS_FILE) / ".." / ".." / "..").resolve() - # Decide on the parameters to pass to scale_run - # p has not been started, so these are not part of the profiling output + # Parameters to pass to scale_run years = 0 months = 1 initial_population = 50000 @@ -46,8 +45,7 @@ def main(output_html_fname: str = None) -> None: # Setup the profiler, to record the stack every interval seconds p = Profiler(interval=1e-3) - # Start the profiler and perform the run - # p.start() + # Perform the run, passing in the profiler so it can be started sc_run( years, months, @@ -67,7 +65,6 @@ def main(output_html_fname: str = None) -> None: record_hsi_event_details, p, ) - # profiled_session = p.stop() profiled_session = p.last_session # Parse results into HTML From 50c10f188d32fcfacd5fbaeab2f33d61e70d8a89 Mon Sep 17 00:00:00 2001 From: willGraham01 <1willgraham@gmail.com> Date: Mon, 26 Jun 2023 14:45:33 +0100 Subject: [PATCH 05/11] Add pyinstrument to dev requirements --- requirements/dev.in | 1 + requirements/dev.txt | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/requirements/dev.in b/requirements/dev.in index ee827f5070..5983b1b35d 100644 --- a/requirements/dev.in +++ b/requirements/dev.in @@ -1,6 +1,7 @@ -r base.in # Running tests +pyinstrument pytest virtualenv tox diff --git a/requirements/dev.txt b/requirements/dev.txt index 995f8112d8..4d0772dc85 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -1,6 +1,6 @@ # -# This file is autogenerated by pip-compile -# To update, run: +# This file is autogenerated by pip-compile with Python 3.8 +# by the following command: # # pip-compile --output-file=requirements/dev.txt requirements/dev.in # @@ -27,14 +27,14 @@ azure-core==1.11.0 # azure-storage-file-share azure-identity==1.5.0 # via -r requirements/base.in +azure-keyvault==4.1.0 + # via -r requirements/base.in azure-keyvault-certificates==4.2.1 # via azure-keyvault azure-keyvault-keys==4.3.1 # via azure-keyvault azure-keyvault-secrets==4.2.0 # via azure-keyvault -azure-keyvault==4.1.0 - # via -r requirements/base.in azure-storage-file-share==12.4.1 # via -r requirements/base.in certifi==2020.12.5 @@ -83,12 +83,12 @@ kiwisolver==1.3.1 # via matplotlib matplotlib==3.3.4 # via -r requirements/base.in -msal-extensions==0.3.0 - # via azure-identity msal==1.9.0 # via # azure-identity # msal-extensions +msal-extensions==0.3.0 + # via azure-identity msrest==0.6.21 # via # azure-batch @@ -131,6 +131,8 @@ py==1.10.0 # tox pycparser==2.20 # via cffi +pyinstrument==4.5.0 + # via -r requirements/dev.in pyjwt[crypto]==2.0.1 # via # adal @@ -150,8 +152,6 @@ python-dateutil==2.8.1 # pandas pytz==2021.1 # via pandas -requests-oauthlib==1.3.0 - # via msrest requests==2.25.1 # via # adal @@ -159,6 +159,8 @@ requests==2.25.1 # msal # msrest # requests-oauthlib +requests-oauthlib==1.3.0 + # via msrest scipy==1.6.1 # via -r requirements/base.in six==1.15.0 From 8fe425f1c6e87d08530dc520b0c2d6650ac539f7 Mon Sep 17 00:00:00 2001 From: willGraham01 <1willgraham@gmail.com> Date: Wed, 28 Jun 2023 10:56:35 +0100 Subject: [PATCH 06/11] Reorganise profiling into one place --- profiling/__init__.py | 0 profiling/_paths.py | 8 ++ profiling/parameters.py | 21 ++++ profiling/profile.py | 65 +++++++++++++ .../profiling => profiling}/scale_run.py | 75 +++++++-------- profiling/shared.py | 47 +++++++++ src/scripts/profiling/benchmark_scale_run.py | 96 ------------------- 7 files changed, 174 insertions(+), 138 deletions(-) create mode 100644 profiling/__init__.py create mode 100644 profiling/_paths.py create mode 100644 profiling/parameters.py create mode 100644 profiling/profile.py rename {src/scripts/profiling => profiling}/scale_run.py (84%) create mode 100644 profiling/shared.py delete mode 100644 src/scripts/profiling/benchmark_scale_run.py diff --git a/profiling/__init__.py b/profiling/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/profiling/_paths.py b/profiling/_paths.py new file mode 100644 index 0000000000..0c72dc8f90 --- /dev/null +++ b/profiling/_paths.py @@ -0,0 +1,8 @@ +import os +from pathlib import Path + +PROFILING_DIR = Path(os.path.abspath(os.path.dirname(__file__))) +PROFILING_HTML_DIR = (PROFILING_DIR / "html").resolve() + +TLO_ROOT = (PROFILING_DIR / "..").resolve() +TLO_OUTPUT_DIR = (TLO_ROOT / "outputs").resolve() diff --git a/profiling/parameters.py b/profiling/parameters.py new file mode 100644 index 0000000000..65107191f1 --- /dev/null +++ b/profiling/parameters.py @@ -0,0 +1,21 @@ +from _paths import TLO_ROOT, TLO_OUTPUT_DIR + +# Parameters to pass to scale_run +scale_run_parameters = { + "years": 0, + "months": 1, + "initial_population": 50000, + "tlo_dir": TLO_ROOT, + "output_dir": TLO_OUTPUT_DIR, + "log_filename": "scale_run_benchmark", + "log_level": "DEBUG", + "parse_log_file": False, + "show_progress_bar": True, + "seed": 0, + "disable_health_system": False, + "disable_spurious_symptoms": False, + "capabilities_coefficient": None, + "mode_appt_constraints": 2, + "save_final_population": False, + "record_hsi_event_details": False, +} diff --git a/profiling/profile.py b/profiling/profile.py new file mode 100644 index 0000000000..29b9157ad5 --- /dev/null +++ b/profiling/profile.py @@ -0,0 +1,65 @@ +import argparse +from datetime import datetime + +import os +from pathlib import Path +import warnings + +from pyinstrument import Profiler +from pyinstrument.renderers import HTMLRenderer + +from _paths import PROFILING_HTML_DIR +from parameters import scale_run_parameters +from scale_run import scale_run + + +def current_time() -> str: + """Produces a string of the current time, in YYYY-mm-dd_HHMM format""" + return datetime.utcnow().strftime("%Y-%m-%d_%H%M") + + +def profile_all(output_html_dir: str = None) -> None: + warnings.filterwarnings("ignore") + + # Setup the output file and directory + if output_html_dir is None: + output_html_dir = PROFILING_HTML_DIR + if not os.path.exists(PROFILING_HTML_DIR): + os.mkdir(PROFILING_HTML_DIR) + output_html_file = PROFILING_HTML_DIR / (current_time() + ".html") + + # Setup the profiler, to record the stack every interval seconds + p = Profiler(interval=1e-3) + + # Perform all profiling runs, passing in the profiler so it can be started within each run and halted between for more accurate results + scale_run(**scale_run_parameters, profiler=p) + + # Recorded sessions are combined, so last_session should fetch the combination of all profiling runs conducted + profiled_session = p.last_session + + # Parse results into HTML + # show_all: removes library calls where identifiable + # timeline: if true, samples are left in chronological order rather than total time + html_renderer = HTMLRenderer(show_all=False, timeline=False) + + # Write HTML file + print(f"Writing output to: {output_html_file}", end="...", flush=True) + with open(output_html_file, "w") as f: + f.write(html_renderer.render(profiled_session)) + print("done") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="Run all profiling scripts and save the results." + ) + parser.add_argument( + "output_html_dir", + nargs="?", + type=str, + default=None, + help="Directory into which to write profiling results as HTML files.", + ) + + args = parser.parse_args() + profile_all(**vars(args)) diff --git a/src/scripts/profiling/scale_run.py b/profiling/scale_run.py similarity index 84% rename from src/scripts/profiling/scale_run.py rename to profiling/scale_run.py index 3580a3c86f..5757027156 100644 --- a/src/scripts/profiling/scale_run.py +++ b/profiling/scale_run.py @@ -14,17 +14,22 @@ import os import warnings from pathlib import Path -from typing import Literal +from typing import Literal, Optional, Type, TYPE_CHECKING import pandas as pd -import shared + +if TYPE_CHECKING: + import pyinstrument from tlo import Date, Simulation, logging -from tlo.analysis.utils import parse_log_file +from tlo.analysis.utils import parse_log_file as log_parse_fn # avoid name conflicts from tlo.methods.fullmodel import fullmodel +from shared import print_checksum, schedule_profile_log +from _paths import TLO_ROOT, TLO_OUTPUT_DIR + -def main( +def scale_run( years: int, months: int, initial_population: int, @@ -41,7 +46,7 @@ def main( mode_appt_constraints: Literal[0, 1, 2], save_final_population: bool, record_hsi_event_details: bool, - profiler=None, + profiler: Optional[Type["pyinstrument.Profiler"]] = None, ) -> None: """ A run of the full model at scale using all disease modules considered complete and all @@ -92,15 +97,15 @@ def main( # Run the simulation sim.make_initial_population(n=initial_population) - shared.schedule_profile_log(sim) + schedule_profile_log(sim) sim.simulate(end_date=end_date) - shared.print_checksum(sim) + print_checksum(sim) if save_final_population: sim.population.props.to_pickle(output_dir / "final_population.pkl") if parse_log_file: - log_df = parse_log_file(sim.log_filepath) + log_df = log_parse_fn(sim.log_filepath) if record_hsi_event_details: with open(output_dir / "hsi_event_details.json", "w") as json_file: @@ -135,32 +140,32 @@ def main( default=0, ) parser.add_argument( - "--initial-population", type=int, help="Initial population size", default=50000 + "--initial_population", type=int, help="Initial population size", default=50000 ) parser.add_argument( - "--tlo-dir", type=Path, help="Root TLOmodel directory", default="." + "--tlo_dir", type=Path, help="Root TLOmodel directory", default=TLO_ROOT ) parser.add_argument( - "--output-dir", + "--output_dir", type=Path, help="Directory to write output to", - default="./outputs", + default=TLO_OUTPUT_DIR, ) parser.add_argument( - "--log-filename", + "--log_filename", type=str, help="Filename to use for log", default="for_profiling", ) parser.add_argument( - "--log-level", + "--log_level", type=str, help="Level to log at", choices=("CRITICAL", "DEBUG", "FATAL", "WARNING", "INFO"), default="WARNING", ) parser.add_argument( - "--parse-log-file", + "--parse_log_file", help=( "Parse log file to create log dataframe at end of simulation (only useful with " "interactive -i runs)" @@ -168,7 +173,7 @@ def main( action="store_true", ) parser.add_argument( - "--show-progress-bar", + "--show_progress_bar", help="Show progress bar during simulation rather than log output", action="store_true", ) @@ -179,7 +184,7 @@ def main( default=0, ) parser.add_argument( - "--disable-health-system", + "--disable_health_system", help=( "Disable health system - i.e. no processing happens by the health system but " "all HSI Events run" @@ -187,12 +192,12 @@ def main( action="store_true", ) parser.add_argument( - "--disable-spurious-symptoms", + "--disable_spurious_symptoms", help="Disable the generation of spurious symptoms in SymptomManager", action="store_true", ) parser.add_argument( - "--capabilities-coefficient", + "--capabilities_coefficient", help=( "Capabilities coefficient to use in HealthSystem. If not specified the ratio of" " the initial population to the estimated 2010 population will be used." @@ -201,7 +206,7 @@ def main( default=None, ) parser.add_argument( - "--mode-appt-constraints", + "--mode_appt_constraints", help=( "Mode of constraints to use in HealthSystem (0: no constraints - all events " "run with no squeeze factor, 1: elastic, all events run with squeeze factor, " @@ -212,7 +217,7 @@ def main( default=2, ) parser.add_argument( - "--ignore-warnings", + "--ignore_warnings", help=( "Ignore any warnings (prevents warning messages being printed). Useful when " "combined with --show-progress-bar to avoid disruption of progress bar display" @@ -220,17 +225,17 @@ def main( action="store_true", ) parser.add_argument( - "--save-args-json", + "--save_args_json", help="Save the parsed arguments to a JSON file", action="store_true", ) parser.add_argument( - "--save-final-population", + "--save_final_population", help="Save the final population dataframe to a pickle file", action="store_true", ) parser.add_argument( - "--record-hsi-event-details", + "--record_hsi_event_details", help=( "Keep a record of set of non-target specific details of HSI events that are " "run and output to a JSON file 'hsi_event_details.json' in output directory." @@ -253,21 +258,7 @@ def main( } json.dump(args_dict, f, indent=4) - main( - args.years, - args.months, - args.initial_population, - args.tlo_dir, - args.output_dir, - args.log_filename, - args.log_level, - args.parse_log_file, - args.show_progress_bar, - args.seed, - args.disable_health_system, - args.disable_spurious_symptoms, - args.capabilities_coefficient, - args.mode_appt_constraints, - args.save_final_population, - args.record_hsi_event_details, - ) + inputs = vars(args) + inputs.pop("save_args_json") + inputs.pop("ignore_warnings") + scale_run(**inputs) diff --git a/profiling/shared.py b/profiling/shared.py new file mode 100644 index 0000000000..1582062cc6 --- /dev/null +++ b/profiling/shared.py @@ -0,0 +1,47 @@ +import datetime +import random +import time + +import pandas as pd + +from tlo import DateOffset, Simulation, logging +from tlo.events import PopulationScopeEventMixin, RegularEvent +from tlo.util import hash_dataframe + +logger = logging.getLogger('tlo.profiling') +logger.setLevel(logging.INFO) + + +class LogProgress(RegularEvent, PopulationScopeEventMixin): + def __init__(self, module): + super().__init__(module, frequency=DateOffset(months=3)) + self.time = time.time() + + def apply(self, population): + df = population.props + now = time.time() + duration = (now - self.time) / 60 # minutes + self.time = now + logger.info(key="stats", data={ + "time": datetime.datetime.now().isoformat(), + "duration": duration, + "alive": df.is_alive.sum(), + "total": len(df) + }) + + +def schedule_profile_log(sim: Simulation) -> None: + """Schedules the log progress event, used only for profiling""" + sim.schedule_event(LogProgress(sim.modules["Demography"]), sim.start_date) + + +def print_checksum(sim: Simulation) -> None: + """Output checksum of dataframe to screen""" + logger.info(key="msg", data=f"Population checksum: {hash_dataframe(sim.population.props)}") + + +def save_population(sim: Simulation) -> None: + df: pd.DataFrame = sim.population.props + filename = 'profiling_population_%010x.pickle' % random.randrange(16**10) + df.to_pickle(filename) + logger.info(key="msg", data=f"Pickled population dataframe: {filename}") diff --git a/src/scripts/profiling/benchmark_scale_run.py b/src/scripts/profiling/benchmark_scale_run.py deleted file mode 100644 index c6026e5eb5..0000000000 --- a/src/scripts/profiling/benchmark_scale_run.py +++ /dev/null @@ -1,96 +0,0 @@ -""" -Benchmarking script for scale_run.py. -""" -import argparse -import datetime -import os -from pathlib import Path -import warnings - -from pyinstrument import Profiler -from pyinstrument.renderers import HTMLRenderer - -from scale_run import main as sc_run - - -def main(output_html_fname: str = None) -> None: - warnings.filterwarnings("ignore") - - if output_html_fname is None: - output_html_fname = output_dir / ( - datetime.datetime.utcnow().strftime("%Y-%m-%d_%H%M") - + "_scale_run_profile.html" - ) - - LOCATION_OF_THIS_FILE = os.path.dirname(os.path.abspath(__file__)) - TLO_ROOT = (Path(LOCATION_OF_THIS_FILE) / ".." / ".." / "..").resolve() - - # Parameters to pass to scale_run - years = 0 - months = 1 - initial_population = 50000 - tlo_dir = TLO_ROOT - output_dir = (TLO_ROOT / "outputs").resolve() - log_filename = "scale_run_benchmark" - log_level = "DEBUG" - parse_log_file = False - show_progress_bar = True - seed = 0 - disable_health_system = False - disable_spurious_symptoms = False - capabilities_coefficient = None - mode_appt_constraints = 2 - save_final_population = False - record_hsi_event_details = False - - # Setup the profiler, to record the stack every interval seconds - p = Profiler(interval=1e-3) - # Perform the run, passing in the profiler so it can be started - sc_run( - years, - months, - initial_population, - tlo_dir, - output_dir, - log_filename, - log_level, - parse_log_file, - show_progress_bar, - seed, - disable_health_system, - disable_spurious_symptoms, - capabilities_coefficient, - mode_appt_constraints, - save_final_population, - record_hsi_event_details, - p, - ) - profiled_session = p.last_session - - # Parse results into HTML - # show_all: removes library calls where identifiable - # timeline: if true, samples are left in chronological order rather than total time - html_renderer = HTMLRenderer(show_all=False, timeline=False) - - # Write HTML file - print(f"Writing output to: {output_html_fname}", end="...", flush=True) - with open(output_html_fname, "w") as f: - f.write(html_renderer.render(profiled_session)) - print("done") - - return - - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description="Benchmark scale_run.py script") - parser.add_argument( - "output-html-fname", - nargs="?", - type=str, - default=None, - help="Filename for the output HTML file containing the profiling results." - " Generates a default name using the call timestamp if not set.", - ) - - parser.parse_args() - main(parser.output_html_fname) From a32e386ac2c82b11a775e758d780ac15aa6492ed Mon Sep 17 00:00:00 2001 From: willGraham01 <1willgraham@gmail.com> Date: Wed, 28 Jun 2023 11:00:05 +0100 Subject: [PATCH 07/11] Update dev requirements and .gitignore for profiling --- .gitignore | 3 +++ requirements/dev.in | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 616ff42acf..a1525e8eba 100644 --- a/.gitignore +++ b/.gitignore @@ -47,6 +47,9 @@ coverage.xml .hypothesis/ .pytest_cache/ +# Profiling +profiling/html/ + # Translations *.mo *.pot diff --git a/requirements/dev.in b/requirements/dev.in index 5983b1b35d..ba610b0d2d 100644 --- a/requirements/dev.in +++ b/requirements/dev.in @@ -1,10 +1,12 @@ -r base.in # Running tests -pyinstrument pytest virtualenv tox +# Profiling +pyinstrument + # Building requirements files pip-tools From 77a6be11da3bb8d901876512c27bfa9bfa2108f8 Mon Sep 17 00:00:00 2001 From: willGraham01 <1willgraham@gmail.com> Date: Wed, 28 Jun 2023 11:09:54 +0100 Subject: [PATCH 08/11] Add short readme for usage --- profiling/README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 profiling/README.md diff --git a/profiling/README.md b/profiling/README.md new file mode 100644 index 0000000000..9e973dc00b --- /dev/null +++ b/profiling/README.md @@ -0,0 +1,20 @@ +# Profiling with `pyinstrument` + +Activate your developer environment, and navigate to the root of the TLOModel repository. +Run +```sh +python profiling/profile.py HMTL_OUTPUT_LOCATION +``` +to run the profiling script (currently only supports `scale_run.py`). +You can also request command-line help using the `-h` or `--help` flags. +If you do not provide the `HTML_OUTPUT_LOCATION`, the script will write the outputs to the default location (`profiling/html`). + +## Files within `profiling/` + +- `parameters.py`: Parameters for each of the models that the profiler should run, stored as dictionaries. +- `profile.py`: Main profiling script; runs all models that need to be profiled and outputs results. +- `shared.py`: Logging and other processes that are shared across multiple files. + +The following files contain models which are run by the profiler: +- `scale_run.py`: A run of the full model at scale using all disease modules considered complete and all +modules for birth / labour / newborn outcome. \ No newline at end of file From 98029c1b72b8ad86afdcaf12096810656af670c7 Mon Sep 17 00:00:00 2001 From: willGraham01 <1willgraham@gmail.com> Date: Wed, 28 Jun 2023 11:17:10 +0100 Subject: [PATCH 09/11] Add some status messages --- profiling/profile.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/profiling/profile.py b/profiling/profile.py index 29b9157ad5..e7d56d2bc3 100644 --- a/profiling/profile.py +++ b/profiling/profile.py @@ -13,9 +13,9 @@ from scale_run import scale_run -def current_time() -> str: - """Produces a string of the current time, in YYYY-mm-dd_HHMM format""" - return datetime.utcnow().strftime("%Y-%m-%d_%H%M") +def current_time(formatstr: str = "%Y-%m-%d_%H%M") -> str: + """Produces a string of the current time in the specified format""" + return datetime.utcnow().strftime(formatstr) def profile_all(output_html_dir: str = None) -> None: @@ -31,9 +31,11 @@ def profile_all(output_html_dir: str = None) -> None: # Setup the profiler, to record the stack every interval seconds p = Profiler(interval=1e-3) + print(f"[{current_time('%H:%M:%S')}:INFO] Starting profiling runs") # Perform all profiling runs, passing in the profiler so it can be started within each run and halted between for more accurate results scale_run(**scale_run_parameters, profiler=p) + print(f"[{current_time('%H:%M:%S')}:INFO] Profiling runs complete") # Recorded sessions are combined, so last_session should fetch the combination of all profiling runs conducted profiled_session = p.last_session From 40a658c83786a5d8580c075201bfabb4ed736bd9 Mon Sep 17 00:00:00 2001 From: willGraham01 <1willgraham@gmail.com> Date: Wed, 28 Jun 2023 13:26:03 +0100 Subject: [PATCH 10/11] Exclude profiling from minimal package build --- MANIFEST.in | 3 +++ 1 file changed, 3 insertions(+) diff --git a/MANIFEST.in b/MANIFEST.in index 7b3ddb310e..0c3359b1d3 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -23,3 +23,6 @@ exclude docs/_*.rst exclude docs/hsi_events.csv global-exclude *.py[cod] __pycache__ *.so *.dylib .ipynb_checkpoints/** *~ + +# exclude the benchmarking scripts from minimal build +recursive-exclude profiling * \ No newline at end of file From 5f2d5a5b9fca054b13fcc012b997981f45dfeb49 Mon Sep 17 00:00:00 2001 From: willGraham01 <1willgraham@gmail.com> Date: Wed, 28 Jun 2023 13:43:55 +0100 Subject: [PATCH 11/11] Relocate to src/scripts/profiling to be consistent with existing test framework. --- .gitignore | 2 +- MANIFEST.in | 3 -- profiling/__init__.py | 0 profiling/shared.py | 47 ------------------- .../scripts/profiling}/README.md | 19 ++++++-- .../scripts/profiling}/_paths.py | 2 +- .../scripts/profiling}/parameters.py | 0 .../scripts/profiling}/profile.py | 0 .../scripts/profiling}/scale_run.py | 0 9 files changed, 17 insertions(+), 56 deletions(-) delete mode 100644 profiling/__init__.py delete mode 100644 profiling/shared.py rename {profiling => src/scripts/profiling}/README.md (54%) rename {profiling => src/scripts/profiling}/_paths.py (78%) rename {profiling => src/scripts/profiling}/parameters.py (100%) rename {profiling => src/scripts/profiling}/profile.py (100%) rename {profiling => src/scripts/profiling}/scale_run.py (100%) diff --git a/.gitignore b/.gitignore index a1525e8eba..c01ebc2f05 100644 --- a/.gitignore +++ b/.gitignore @@ -48,7 +48,7 @@ coverage.xml .pytest_cache/ # Profiling -profiling/html/ +src/scripts/profiling/html/ # Translations *.mo diff --git a/MANIFEST.in b/MANIFEST.in index 0c3359b1d3..7b3ddb310e 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -23,6 +23,3 @@ exclude docs/_*.rst exclude docs/hsi_events.csv global-exclude *.py[cod] __pycache__ *.so *.dylib .ipynb_checkpoints/** *~ - -# exclude the benchmarking scripts from minimal build -recursive-exclude profiling * \ No newline at end of file diff --git a/profiling/__init__.py b/profiling/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/profiling/shared.py b/profiling/shared.py deleted file mode 100644 index 1582062cc6..0000000000 --- a/profiling/shared.py +++ /dev/null @@ -1,47 +0,0 @@ -import datetime -import random -import time - -import pandas as pd - -from tlo import DateOffset, Simulation, logging -from tlo.events import PopulationScopeEventMixin, RegularEvent -from tlo.util import hash_dataframe - -logger = logging.getLogger('tlo.profiling') -logger.setLevel(logging.INFO) - - -class LogProgress(RegularEvent, PopulationScopeEventMixin): - def __init__(self, module): - super().__init__(module, frequency=DateOffset(months=3)) - self.time = time.time() - - def apply(self, population): - df = population.props - now = time.time() - duration = (now - self.time) / 60 # minutes - self.time = now - logger.info(key="stats", data={ - "time": datetime.datetime.now().isoformat(), - "duration": duration, - "alive": df.is_alive.sum(), - "total": len(df) - }) - - -def schedule_profile_log(sim: Simulation) -> None: - """Schedules the log progress event, used only for profiling""" - sim.schedule_event(LogProgress(sim.modules["Demography"]), sim.start_date) - - -def print_checksum(sim: Simulation) -> None: - """Output checksum of dataframe to screen""" - logger.info(key="msg", data=f"Population checksum: {hash_dataframe(sim.population.props)}") - - -def save_population(sim: Simulation) -> None: - df: pd.DataFrame = sim.population.props - filename = 'profiling_population_%010x.pickle' % random.randrange(16**10) - df.to_pickle(filename) - logger.info(key="msg", data=f"Pickled population dataframe: {filename}") diff --git a/profiling/README.md b/src/scripts/profiling/README.md similarity index 54% rename from profiling/README.md rename to src/scripts/profiling/README.md index 9e973dc00b..b90dddd3e4 100644 --- a/profiling/README.md +++ b/src/scripts/profiling/README.md @@ -3,7 +3,7 @@ Activate your developer environment, and navigate to the root of the TLOModel repository. Run ```sh -python profiling/profile.py HMTL_OUTPUT_LOCATION +python src/scripts/profiling/profile.py HMTL_OUTPUT_LOCATION ``` to run the profiling script (currently only supports `scale_run.py`). You can also request command-line help using the `-h` or `--help` flags. @@ -11,10 +11,21 @@ If you do not provide the `HTML_OUTPUT_LOCATION`, the script will write the outp ## Files within `profiling/` +Utility files: +- `_paths.py`: Defines some absolute paths to ensure that the profiler writes outputs to the correct locations and the script is robust against being run in different working directories. +- `shared.py`: Logging and other processes that are shared across multiple files. + +Files that are used to wrap the automatic profiling run: - `parameters.py`: Parameters for each of the models that the profiler should run, stored as dictionaries. - `profile.py`: Main profiling script; runs all models that need to be profiled and outputs results. -- `shared.py`: Logging and other processes that are shared across multiple files. -The following files contain models which are run by the profiler: +Models which are run by the profiler: - `scale_run.py`: A run of the full model at scale using all disease modules considered complete and all -modules for birth / labour / newborn outcome. \ No newline at end of file +modules for birth / labour / newborn outcome. + +Models which are not presently used by the profiler, but can be run locally: +- `batch_test.py` +- `heavy_use_of_bed_days.py` +- `heavy_use_of_spurious_symptoms.py` +- `run_full_model_with_hard_constraints_in_healthsystem.py` +- `run_with_high_intensity_of_HSI_and_simplified_births.py` \ No newline at end of file diff --git a/profiling/_paths.py b/src/scripts/profiling/_paths.py similarity index 78% rename from profiling/_paths.py rename to src/scripts/profiling/_paths.py index 0c72dc8f90..556b6f94e2 100644 --- a/profiling/_paths.py +++ b/src/scripts/profiling/_paths.py @@ -4,5 +4,5 @@ PROFILING_DIR = Path(os.path.abspath(os.path.dirname(__file__))) PROFILING_HTML_DIR = (PROFILING_DIR / "html").resolve() -TLO_ROOT = (PROFILING_DIR / "..").resolve() +TLO_ROOT = (PROFILING_DIR / ".." / ".." / "..").resolve() TLO_OUTPUT_DIR = (TLO_ROOT / "outputs").resolve() diff --git a/profiling/parameters.py b/src/scripts/profiling/parameters.py similarity index 100% rename from profiling/parameters.py rename to src/scripts/profiling/parameters.py diff --git a/profiling/profile.py b/src/scripts/profiling/profile.py similarity index 100% rename from profiling/profile.py rename to src/scripts/profiling/profile.py diff --git a/profiling/scale_run.py b/src/scripts/profiling/scale_run.py similarity index 100% rename from profiling/scale_run.py rename to src/scripts/profiling/scale_run.py