Skip to content

Commit

Permalink
Graphing property-level dependencies in disease modules (#1475)
Browse files Browse the repository at this point in the history
* Updated modules so that all INIT_DEPENDENCIES and OPTIONAL_INIT_DEPENDENCIES are declared

* Updated modules so that all INIT_DEPENDENCIES and OPTIONAL_INIT_DEPENDENCIES and ADDITIONAL_DEPENDENCIES are declared

* Updated modules so that all INIT_DEPENDENCIES and OPTIONAL_INIT_DEPENDENCIES and ADDITIONAL_DEPENDENCIES are declared

* Updated modules so that all INIT_DEPENDENCIES and OPTIONAL_INIT_DEPENDENCIES and ADDITIONAL_DEPENDENCIES are declared

* Updated modules so that all INIT_DEPENDENCIES and OPTIONAL_INIT_DEPENDENCIES and ADDITIONAL_DEPENDENCIES are declared

* Updated modules so that all INIT_DEPENDENCIES and OPTIONAL_INIT_DEPENDENCIES and ADDITIONAL_DEPENDENCIES are declared

* Updated modules so that all INIT_DEPENDENCIES and OPTIONAL_INIT_DEPENDENCIES and ADDITIONAL_DEPENDENCIES are declared

* Moved all new dependencies to OPTIONAL_INIT_DEPENDENCIES

* Type

* Typo

* Updated circular dependencies

* Updated circular dependencies

* Changed to additional dependencies

* Added subgraphs

* Graph of properties from disease modules that contribute to other disease modules.

Process:  collects properties from each disease (+lifestyle) module, and compares them to the scripts of the remaining disease (+lifestyle) module.

* Delete src/scripts/longterm_projections/property_dependency_graph.py

Duplicate graph

* isort

* isort

* Changed names of functions to avoid overlap

* Updated docstring

* Initial attempt at creating module-level property graphs

* Fixed typo

* Graphs for each individual module

* Aesthetics - still needs colour

* reduced duplication

* Removed all additional dependencies

* typo

* Moved to get_properties folder
  • Loading branch information
RachelMurray-Watson authored Dec 9, 2024
1 parent 2d76315 commit 92ed97a
Show file tree
Hide file tree
Showing 2 changed files with 436 additions and 0 deletions.
111 changes: 111 additions & 0 deletions src/scripts/get_properties/module_dependencies_graph.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
"""Construct a graph showing dependencies between modules."""

import argparse
from pathlib import Path
from typing import Dict, Set

from tlo.dependencies import DependencyGetter, get_all_dependencies, get_module_class_map
from tlo.methods import Metadata

try:
import pydot
except ImportError:
pydot = None


def construct_module_dependency_graph(
excluded_modules: Set[str],
disease_module_node_defaults: Dict,
other_module_node_defaults: Dict,
get_dependencies: DependencyGetter = get_all_dependencies,
):
"""Construct a pydot object representing module dependency graph.
:param excluded_modules: Set of ``Module`` subclass names to not included in graph.
:param disease_module_node_defaults: Any dot node attributes to apply to by default
to disease module nodes.
:param other_module_node_defaults: Any dot node attributes to apply to by default
to non-disease module nodes.
:param get_dependencies: Function which given a module gets the set of module
dependencies. Defaults to extracting all dependencies.
:return: Pydot directed graph representing module dependencies.
"""
if pydot is None:
raise RuntimeError("pydot package must be installed")

module_class_map = get_module_class_map(excluded_modules)
module_graph = pydot.Dot("modules", graph_type="digraph")
disease_module_subgraph = pydot.Subgraph("disease_modules")
module_graph.add_subgraph(disease_module_subgraph)
other_module_subgraph = pydot.Subgraph("other_modules")
module_graph.add_subgraph(other_module_subgraph)

# Set default styles for nodes
disease_module_node_defaults["style"] = "filled"
other_module_node_defaults["style"] = "filled"

for name, module_class in module_class_map.items():
# Determine attributes based on module type
node_attributes = {}

if Metadata.DISEASE_MODULE in module_class.METADATA and name.endswith("Cancer"):
node_attributes.update(disease_module_node_defaults)
node_attributes["color"] = "lightblue" # Color for disease modules and Cancer
else:
node_attributes.update(other_module_node_defaults)
node_attributes["color"] = "lightgreen" # Default color for other modules

# Create the node with determined attributes
node = pydot.Node(name, **node_attributes)

# Add the node to the appropriate subgraph
if Metadata.DISEASE_MODULE in module_class.METADATA or name.endswith("Cancer"):
disease_module_subgraph.add_node(node)
else:
other_module_subgraph.add_node(node)

for key, module in module_class_map.items():
for dependency in get_dependencies(module, module_class_map.keys()):
if dependency not in excluded_modules:
module_graph.add_edge(pydot.Edge(key, dependency))

return module_graph


if __name__ == "__main__":
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"output_file", type=Path, help=(
"Path to output graph to. File extension will determine output format - for example: dot, dia, png, svg"
)
)
args = parser.parse_args()

excluded_modules = {
"Mockitis",
"ChronicSyndrome",
"Skeleton",
"AlriPropertiesOfOtherModules",
"DiarrhoeaPropertiesOfOtherModules",
"DummyHivModule",
"SimplifiedBirths",
"Demography",
"HealthBurden",
"SymptomManager",
"DummyTbModule",
"ImprovedHealthSystemAndCareSeekingScenarioSwitcher",
"HealthSeekingBehaviour",
"HealthSystem",
"Deviance",
"SimplifiedPregnancyAndLabour"
}

module_graph = construct_module_dependency_graph(
excluded_modules,
disease_module_node_defaults={"fontname": "Arial", "shape": "box"},
other_module_node_defaults={"fontname": "Arial", "shape": "ellipse"},
)

format = (
args.output_file.suffix[1:] if args.output_file.suffix else "raw"
)
module_graph.write(args.output_file, format=format)
Loading

0 comments on commit 92ed97a

Please sign in to comment.