diff --git a/src/acom_music_box/music_box.py b/src/acom_music_box/music_box.py index be34c57..0caba33 100644 --- a/src/acom_music_box/music_box.py +++ b/src/acom_music_box/music_box.py @@ -89,6 +89,47 @@ def create_solver( solver_type, number_of_grid_cells) + def check_config(self, boxConfigPath): + """ + Verifies correct configuration of the MusicBox object. + There is intentionally no check for the presence of a solver; + this test function is for the loaded configuration only. + + Args: + boxConfigPath = filename and path of MusicBox configuration file + This filename is supplied only for the error message; + the configuration should already be loaded. + + Returns: + True if all checks passed + Throws error for the first check failed. + """ + + # look for duplicate reaction names + if (self.initial_conditions): + if (self.initial_conditions.reaction_rates): + reactionNames = [] + for rate in self.initial_conditions.reaction_rates: + # watch out for Nones in here + if not rate.reaction: + continue + if not rate.reaction.name: + continue + reactionNames.append(rate.reaction.name) + + # look for name already seen + seen = set() + dupNames = [name for name in reactionNames if name in seen or seen.add(name)] + + if (len(dupNames) > 0): + # inform user of the error and its remedy + errString = ("Error: Duplicate reaction names specified within {}: {}." + .format(boxConfigPath, dupNames)) + errString += " Please remove or rename the duplicates." + raise Exception(errString) + + return(True) + def solve(self, output_path=None, callback=None): """ Solves the box model simulation and optionally writes the output to a file. @@ -280,6 +321,9 @@ def readConditionsFromJson(self, path_to_json): self.evolving_conditions = EvolvingConditions.from_config_JSON( path_to_json, data, self.species_list, self.reaction_list) + self.check_config(os.path.join(os.getcwd(), path_to_json)) + + def speciesOrdering(self): """ Retrieves the ordering of species used in the solver. diff --git a/tests/unit/test_duplicate_reactions.py b/tests/unit/test_duplicate_reactions.py new file mode 100644 index 0000000..d5262e6 --- /dev/null +++ b/tests/unit/test_duplicate_reactions.py @@ -0,0 +1,33 @@ +from acom_music_box import MusicBox, Reaction, ReactionRate, Conditions +from acom_music_box.reaction_list import ReactionList + +import pytest + + +class TestDuplicateReactions: + def test_run(self): + # set up dummy reactions + abc123 = ReactionRate(Reaction("abc"), 12.3) + def456 = ReactionRate(Reaction("def"), 45.6) + abc789 = ReactionRate(Reaction("abc"), 78.9) + + # Pass: unique reaction names + pass_reactions = [abc123, def456] + pass_conditions = Conditions(reaction_rates=pass_reactions) + box_model = MusicBox(initial_conditions=pass_conditions) + assert box_model.check_config("Loaded from string.") + + # Fail: duplicate reaction names + fail_reactions = reactions=[abc123, abc789] + fail_conditions = Conditions(reaction_rates=fail_reactions) + box_model = MusicBox(initial_conditions=fail_conditions) # new instance + + # verify that the fail exception was properly raised + with pytest.raises(Exception): + box_model.check_config("Loaded from string.") + + +if __name__ == "__main__": + test = TestDuplicateReactions() + test.test_run() +