From 0e657cf428b4d0cca037f170fa5d435fa8fd9977 Mon Sep 17 00:00:00 2001 From: Tyler Coles Date: Wed, 18 Dec 2024 17:23:03 -0700 Subject: [PATCH] Warn if IPM contains unused compartments or requirements. --- epymorph/compartment_model.py | 23 +++++++++++ epymorph/test/compartment_model_test.py | 53 ++++++++++++++++++++++++- 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/epymorph/compartment_model.py b/epymorph/compartment_model.py index 8c04b4d8..a8d783d1 100644 --- a/epymorph/compartment_model.py +++ b/epymorph/compartment_model.py @@ -23,6 +23,7 @@ TypeVar, final, ) +from warnings import warn import numpy as np from numpy.typing import NDArray @@ -477,6 +478,17 @@ def validate_compartment_model(model: BaseCompartmentModel) -> None: ) raise IpmValidationException(err) + # declared compartments minus used compartments is ideally empty, + # otherwise raise a warning + extra_comps = set(model.symbols.all_compartments).difference(trx_comps) + if len(extra_comps) > 0: + msg = ( + f"Possible issue in {name}: " + "not all declared compartments are being used in transitions.\n" + f"Extra compartments: {', '.join(map(str, extra_comps))}" + ) + warn(msg) + # transition requirements minus declared requirements should be empty missing_reqs = trx_reqs.difference(model.symbols.all_requirements) if len(missing_reqs) > 0: @@ -487,6 +499,17 @@ def validate_compartment_model(model: BaseCompartmentModel) -> None: ) raise IpmValidationException(err) + # declared requirements minus used requirements is ideally empty, + # otherwise raise a warning + extra_reqs = set(model.symbols.all_requirements).difference(trx_reqs) + if len(extra_reqs) > 0: + msg = ( + f"Possible issue in {name}: " + "not all declared requirements are being used in transitions.\n" + f"Extra requirements: {', '.join(map(str, extra_reqs))}" + ) + warn(msg) + #################################### # Single-strata Compartment Models # diff --git a/epymorph/test/compartment_model_test.py b/epymorph/test/compartment_model_test.py index 4bb0965f..14e20e57 100644 --- a/epymorph/test/compartment_model_test.py +++ b/epymorph/test/compartment_model_test.py @@ -1,4 +1,3 @@ -# pylint: disable=missing-docstring,unused-variable import unittest from typing import Sequence @@ -214,6 +213,58 @@ def edges(self, symbols): self.assertIn("invalid compartments", str(e.exception).lower()) + def test_create_07(self): + # Test for warning: unused requirements. + with self.assertWarns(Warning) as w: + + class MyIpm(CompartmentModel): + compartments = [ + compartment("S", tags=["test_tag"]), + compartment("I"), + compartment("R"), + ] + requirements = [ + AttributeDef("beta", float, Shapes.N), + AttributeDef("gamma", float, Shapes.N), + AttributeDef("extraneous", float, Shapes.N), + ] + + def edges(self, symbols): + S, I, R = symbols.all_compartments + beta, gamma, extraneous = symbols.all_requirements + return [ + edge(S, I, rate=beta * S * I), + edge(I, R, rate=gamma * I), + ] + + self.assertIn("extra requirements: extraneous", str(w.warning).lower()) + + def test_create_08(self): + # Test for warning: unused compartments. + with self.assertWarns(Warning) as w: + + class MyIpm(CompartmentModel): + compartments = [ + compartment("S", tags=["test_tag"]), + compartment("I"), + compartment("R"), + compartment("Q"), + ] + requirements = [ + AttributeDef("beta", float, Shapes.N), + AttributeDef("gamma", float, Shapes.N), + ] + + def edges(self, symbols): + S, I, R, Q = symbols.all_compartments + beta, gamma = symbols.all_requirements + return [ + edge(S, I, rate=beta * S * I), + edge(I, R, rate=gamma * I), + ] + + self.assertIn("extra compartments: q", str(w.warning).lower()) + def test_compartment_name(self): # Test for compartment names that include spaces. with self.assertRaises(ValueError):