diff --git a/CHANGELOG.md b/CHANGELOG.md index e4474ac..8e2a27a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -112,4 +112,20 @@ Small bugfix to ensure the model file is opened with UTF-8 encoding which caused Removing numpy dependency from setup.py, action reading off of BNGL file. ## 0.3.8 -Moving imports around, removing unnecessary ones to speed up CLI performance. \ No newline at end of file +Moving imports around, removing unnecessary ones to speed up CLI performance. + +## 0.3.9 +A couple bugfixes to plotting, running the CLI on a model can now generate a log file with the option +-l/--log. Failing to run now raises a ValueError (will be changed with custom errors in the future). Added some input and output file checks to notebook subcommand. + +## 0.4.0 +Fixed a but where "0" species was being printed as "0()". Action block is now a list and not a dictionary which was disallowing multiple actions of the same type. + +## 0.4.1 +Changed `bionetgen.run` behavior when called with a `bngmodel` object. Now the model file is saved and if it exists, it's overwritten with a warning. Slightly better error reporting when the `run` call fails. + +## 0.4.2 +Changed `bionetgen.run` behavior again, how calling the method with an `out` argument doesn't leave you in the output folder when it's done executing and it will return you back to the folder you started with. Bugfix where parsing a model without actions failed. + +## 0.4.3 +Bugfix where the libroadrunner simulator object was not handled correctly. \ No newline at end of file diff --git a/README.md b/README.md index 473e433..05f29db 100644 --- a/README.md +++ b/README.md @@ -52,10 +52,10 @@ with open("new_model.bngl", "w") as f: f.write(str(model)) # writes the changed model to new_model file # this will give you a libRoadRunner instance of the model -librr_sim = model.setup_simulator()._simulator +librr_sim = model.setup_simulator() ``` -More documentation and tutorials are in progress. +You can find more tutorials [here](https://pybionetgen.readthedocs.io/en/latest/tutorials.html). ### Environment Setup diff --git a/bionetgen/assets/VERSION b/bionetgen/assets/VERSION index 86267b7..a41f568 100644 --- a/bionetgen/assets/VERSION +++ b/bionetgen/assets/VERSION @@ -1 +1 @@ -0 3 8 alpha 0 \ No newline at end of file +0 4 3 alpha 0 \ No newline at end of file diff --git a/bionetgen/core/main.py b/bionetgen/core/main.py index e1e955b..ff07386 100644 --- a/bionetgen/core/main.py +++ b/bionetgen/core/main.py @@ -145,6 +145,9 @@ def _set_output(self, output): os.chdir(output) def run(self): + import ipdb + + ipdb.set_trace() try: stdout_loc = getattr(subprocess, self.stdout) except: @@ -198,7 +201,7 @@ def run(self): # print(rc.stdout.decode('utf-8')) # if rc.stderr is not None: # print(rc.stderr.decode('utf-8')) - import ipdb;ipdb.set_trace() + # import ipdb;ipdb.set_trace() if rc == 0: # load in the result self.result = BNGResult(os.getcwd()) @@ -213,4 +216,4 @@ def run(self): os.environ["BNGPATH"] = self.old_bngpath raise ValueError( "Failed to run your BNGL file, there might be an issue with your model!" - ) \ No newline at end of file + ) diff --git a/bionetgen/core/plot.py b/bionetgen/core/plot.py index df60398..9fe65ba 100644 --- a/bionetgen/core/plot.py +++ b/bionetgen/core/plot.py @@ -83,8 +83,11 @@ def _datplot(self): ymin = self.kwargs.get("ymin", False) or oymin ymax = self.kwargs.get("ymax", False) or oymax - fax.set_xlim(xmin, xmax) - fax.set_ylim(ymin, ymax) + assert xmax > xmin, "--xmin is bigger than --xmax!" + assert ymax > ymin, "--ymin is bigger than --ymax!" + + fax.set_xlim(left=xmin, right=xmax) + fax.set_ylim(bottom=ymin, top=ymax) # labels and title _ = plt.xlabel(self.kwargs.get("xlabel") or x_name) _ = plt.ylabel(self.kwargs.get("ylabel") or "concentration") diff --git a/bionetgen/main.py b/bionetgen/main.py index 4e0e597..b8aad47 100644 --- a/bionetgen/main.py +++ b/bionetgen/main.py @@ -1,6 +1,6 @@ from bionetgen.modelapi.utils import run_command import cement -import subprocess +import subprocess, os import bionetgen as bng from cement.core.exc import CaughtSignal from .core.exc import BioNetGenError @@ -74,6 +74,15 @@ class Meta: "type": str, }, ), + ( + ["-l", "--log"], + { + "help": "saves BNG2.pl log to a file given (default: None)", + "default": None, + "type": str, + "dest": "log_file", + }, + ), ], ) def run(self): @@ -132,6 +141,16 @@ def notebook(self): args = self.app.pargs if args.input is not None: # we want to use the template to write a custom notebok + assert args.input.endswith( + ".bngl" + ), f"File {args.input} doesn't have bngl extension!" + try: + import bionetgen + + m = bionetgen.bngmodel(args.input) + str(m) + except: + raise RuntimeError(f"Couldn't import given model: {args.input}!") notebook = BNGNotebook( CONFIG["bionetgen"]["notebook"]["template"], INPUT_ARG=args.input ) @@ -144,13 +163,22 @@ def notebook(self): else: fname = args.output # write the notebook out + if os.path.isdir(fname): + if args.input is not None: + basename = os.path.basename(args.input) + mname = basename.replace(".bngl", "") + fname = mname + ".ipynb" + else: + mname = CONFIG["bionetgen"]["notebook"]["name"] + fname = os.path.join(args.output, mname) + notebook.write(fname) # open the notebook with nbopen stdout = getattr(subprocess, CONFIG["bionetgen"]["stdout"]) stderr = getattr(subprocess, CONFIG["bionetgen"]["stderr"]) if args.open: command = ["nbopen", fname] - rc = run_command(command) + rc, _ = run_command(command) @cement.ex( help="Rudimentary plotting of gdat/cdat/scan files", @@ -186,6 +214,7 @@ def notebook(self): { "help": "x-axis minimum (default: determined from data)", "default": None, + "type": float, }, ), ( @@ -193,6 +222,7 @@ def notebook(self): { "help": "x-axis maximum (default: determined from data)", "default": False, + "type": float, }, ), ( @@ -200,6 +230,7 @@ def notebook(self): { "help": "y-axis minimum (default: determined from data)", "default": False, + "type": float, }, ), ( @@ -207,6 +238,7 @@ def notebook(self): { "help": "y-axis maximum (default: determined from data)", "default": False, + "type": float, }, ), (["--xlabel"], {"help": "x-axis label (default: time)", "default": False}), diff --git a/bionetgen/modelapi/blocks.py b/bionetgen/modelapi/blocks.py index b63db8e..d42babb 100644 --- a/bionetgen/modelapi/blocks.py +++ b/bionetgen/modelapi/blocks.py @@ -543,10 +543,44 @@ def __init__(self) -> None: self.name = "actions" AList = ActionList() self._action_list = AList.possible_types + self.items = [] def __setattr__(self, name, value) -> None: self.__dict__[name] = value + def add_item(self, item_tpl) -> None: + name, value = item_tpl + # set the line + self.items.append(value) + + def __repr__(self) -> str: + # overwrites what the class representation + # shows the items in the model block in + # say ipython + repr_str = "{} block with {} item(s): {}".format( + self.name, len(self.items), self.items + ) + return repr_str + + def __getitem__(self, key): + return self.items[key] + + def __setitem__(self, key, value) -> None: + self.items[key] = value + + def __delitem__(self, key) -> None: + try: + return self.items.pop(key) + # TODO: more specific except statements + except: + print("Item {} not found".format(key)) + + def __iter__(self): + return self.items.__iter__() + + def __contains__(self, key) -> bool: + return key in self.items + def add_action(self, action_type, action_args) -> None: """ adds action, needs type as string and args as list of tuples @@ -564,7 +598,7 @@ def clear_actions(self) -> None: def gen_string(self) -> str: block_lines = [] # we just loop over lines for actions - for item in self.items.keys(): - block_lines.append(self.items[item].print_line()) + for item in self.items: + block_lines.append(item.print_line()) # join everything with new lines return "\n".join(block_lines) diff --git a/bionetgen/modelapi/bngfile.py b/bionetgen/modelapi/bngfile.py index f4f8ead..639a3a1 100644 --- a/bionetgen/modelapi/bngfile.py +++ b/bionetgen/modelapi/bngfile.py @@ -67,8 +67,7 @@ def generate_xml(self, xml_file, model_file=None) -> bool: # run with --xml os.chdir(temp_folder) # TODO: take stdout option from app instead - # rc = subprocess.run(["perl",self.bngexec, "--xml", stripped_bngl], stdout=bng.defaults.stdout) - rc = run_command(["perl", self.bngexec, "--xml", stripped_bngl]) + rc, _ = run_command(["perl", self.bngexec, "--xml", stripped_bngl]) if rc == 1: # if we fail, print out what we have to # let the user know what BNG2.pl says @@ -145,7 +144,7 @@ def write_xml(self, open_file, xml_type="bngxml", bngl_str=None) -> bool: # run with --xml # TODO: Make output supression an option somewhere if xml_type == "bngxml": - rc = run_command(["perl", self.bngexec, "--xml", "temp.bngl"]) + rc, _ = run_command(["perl", self.bngexec, "--xml", "temp.bngl"]) if rc == 1: print("XML generation failed") # go back to our original location @@ -162,7 +161,7 @@ def write_xml(self, open_file, xml_type="bngxml", bngl_str=None) -> bool: return True elif xml_type == "sbml": command = ["perl", self.bngexec, "temp.bngl"] - rc = run_command(command) + rc, _ = run_command(command) if rc == 1: print("SBML generation failed") # go back to our original location @@ -179,4 +178,3 @@ def write_xml(self, open_file, xml_type="bngxml", bngl_str=None) -> bool: else: print("XML type {} not recognized".format(xml_type)) return False - return False diff --git a/bionetgen/modelapi/bngparser.py b/bionetgen/modelapi/bngparser.py index 31a9bff..a341e0e 100644 --- a/bionetgen/modelapi/bngparser.py +++ b/bionetgen/modelapi/bngparser.py @@ -121,7 +121,7 @@ def parse_actions(self, model_obj): ablock.add_action(atype, arg_tuples) else: ablock.add_action(atype, []) - model_obj.add_block(ablock) + model_obj.add_block(ablock) def parse_xml(self, xml_str, model_obj) -> None: xml_dict = xmltodict.parse(xml_str) diff --git a/bionetgen/modelapi/model.py b/bionetgen/modelapi/model.py index 110765d..4cd890a 100644 --- a/bionetgen/modelapi/model.py +++ b/bionetgen/modelapi/model.py @@ -256,9 +256,12 @@ def setup_simulator(self, sim_type="libRR"): # temporary file with TemporaryFile(mode="w+") as tpath: # write the sbml - self.bngparser.bngfile.write_xml( - tpath, xml_type="sbml", bngl_str=str(self) - ) + if not ( + self.bngparser.bngfile.write_xml( + tpath, xml_type="sbml", bngl_str=str(self) + ) + ): + raise ValueError("SBML couldn't be generated for libRR simulator") # TODO: Only clear the writeSBML action # by adding a mechanism to do so self.actions.clear_actions() @@ -279,7 +282,8 @@ def setup_simulator(self, sim_type="libRR"): ) ) return None - return self.simulator + # for now we return the underlying simulator + return self.simulator.simulator ###### CORE OBJECT AND PARSING FRONT-END ###### diff --git a/bionetgen/modelapi/pattern.py b/bionetgen/modelapi/pattern.py index acfa6c2..0205453 100644 --- a/bionetgen/modelapi/pattern.py +++ b/bionetgen/modelapi/pattern.py @@ -131,13 +131,20 @@ def __str__(self): mol_str = self.name if self.label is not None: mol_str += "%{}".format(self.label) - mol_str += "(" + # we have a null species + if not self.name == "0": + mol_str += "(" + # we _could_ just not do () if components + # don't exist but that has other issues, + # especially for extension highlighting if len(self.components) > 0: for icomp, comp in enumerate(self.components): if icomp > 0: mol_str += "," mol_str += str(comp) - mol_str += ")" + # we have a null species + if not self.name == "0": + mol_str += ")" if self.compartment is not None: mol_str += "@{}".format(self.compartment) return mol_str diff --git a/bionetgen/modelapi/runner.py b/bionetgen/modelapi/runner.py index b212417..4a510a6 100644 --- a/bionetgen/modelapi/runner.py +++ b/bionetgen/modelapi/runner.py @@ -24,8 +24,8 @@ def run(inp, out=None, suppress=False): into. If it doesn't exist, it will be created. """ # if out is None we make a temp directory + cur_dir = os.getcwd() if out is None: - cur_dir = os.getcwd() with TemporaryDirectory() as out: # instantiate a CLI object with the info cli = BNGCLI(inp, out, conf["bngpath"], suppress=suppress) diff --git a/bionetgen/modelapi/utils.py b/bionetgen/modelapi/utils.py index 74d8945..87473d7 100644 --- a/bionetgen/modelapi/utils.py +++ b/bionetgen/modelapi/utils.py @@ -100,7 +100,7 @@ def test_bngexec(bngexec): path to BNG2.pl to test """ command = ["perl", bngexec] - rc = run_command(command, suppress=True) + rc, _ = run_command(command, suppress=True) if rc == 0: return True else: @@ -112,14 +112,17 @@ def run_command(command, suppress=False): process = subprocess.Popen( command, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, bufsize=-1 ) - return process.poll() + return process.poll(), None else: process = subprocess.Popen(command, stdout=subprocess.PIPE, encoding="utf8") + out = [] while True: output = process.stdout.readline() if output == "" and process.poll() is not None: break if output: - print(output.strip()) + o = output.strip() + out.append(o) + print(o) rc = process.poll() - return rc + return rc, out diff --git a/bionetgen/simulator/simulators.py b/bionetgen/simulator/simulators.py index 0317132..cec035b 100644 --- a/bionetgen/simulator/simulators.py +++ b/bionetgen/simulator/simulators.py @@ -26,12 +26,12 @@ def sim_getter(model_file=None, model_str=None, sim_type="libRR"): """ if model_file is not None: if sim_type == "libRR": - return libRRSimulator(model_file=model_file).simulator + return libRRSimulator(model_file=model_file) else: print("simulator type {} not supported".format(sim_type)) elif model_str is not None: if sim_type == "libRR": - return libRRSimulator(model_str=model_str).simulator + return libRRSimulator(model_str=model_str) else: print("simulator type {} not supported".format(sim_type)) diff --git a/docs/source/index.rst b/docs/source/index.rst index c9a3ff2..191c1a5 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -30,8 +30,8 @@ Indices and Tables: * :ref:`modindex` * :ref:`search` -BioNetGen Home & GitHub Links -============================= +BioNetGen Home & GitHub Links: +============================== * `BioNetGen `_ * `BNG VSCode Extension GitHub `_ diff --git a/docs/source/lib_tutorial.rst b/docs/source/lib_tutorial.rst index 51a2fc5..9dfb4fd 100644 --- a/docs/source/lib_tutorial.rst +++ b/docs/source/lib_tutorial.rst @@ -98,5 +98,5 @@ Jupyter Notebooks Interactive Jupyter notebooks versions of these tutorials can be found here: -* `Running and Plotting `_ -* `bngmodel `_ \ No newline at end of file +* :download:`Running and Plotting <./assets/library_tutorial.ipynb>` +* :download:`bngmodel <./assets/lib_bngmodel_tutorial.ipynb>` \ No newline at end of file diff --git a/docs/source/tutorials.rst b/docs/source/tutorials.rst index 1609bd2..1ce9d19 100644 --- a/docs/source/tutorials.rst +++ b/docs/source/tutorials.rst @@ -10,7 +10,7 @@ Sample Model ------------ These tutorials use a simple BNGL model as an example. The "SIR.bngl" file can be found -`here <./assets/SIR.bngl>`_. +:download:`here <./assets/SIR.bngl>`. Tutorials ========= diff --git a/tests/test_bionetgen.py b/tests/test_bionetgen.py index 9025c16..b20abc9 100644 --- a/tests/test_bionetgen.py +++ b/tests/test_bionetgen.py @@ -73,17 +73,18 @@ # def test_model_running_CLI(): # # test running a few models using the CLI -# models = ["test_MM.bngl","motor.bngl","simple_system.bngl"] +# models = ["test_MM.bngl", "motor.bngl", "simple_system.bngl"] # succ = [] # fail = [] # success = 0 # fails = 0 # for model in models: +# fpath = os.path.join(*[tfold, "models", model]) +# fpath = os.path.abspath(fpath) # try: -# # how to track "successes"? # fpath = os.path.join(*[tfold, "models", model]) # fpath = os.path.abspath(fpath) -# argv = ["run", "-i", fpath] +# argv = ["run", "-i", fpath, "-o", "cli_test_runs"] # with BioNetGenTest(argv=argv) as app: # app.run() # assert app.exit_code == 0 @@ -93,6 +94,7 @@ # print("can't run model {}".format(model)) # fails += 1 # fail.append(model) +# del model, fpath # print("succ: {}".format(success)) # print(sorted(succ)) # print("fail: {}".format(fails)) @@ -102,7 +104,8 @@ def test_model_running_lib(): # test running a few models using the library - models = ["test_MM.bngl","motor.bngl","simple_system.bngl"] + # models = ["test_MM.bngl", "motor.bngl", "simple_system.bngl"] + models = ["test_MM.bngl"] succ = [] fail = [] success = 0 @@ -111,7 +114,10 @@ def test_model_running_lib(): fpath = os.path.join(*[tfold, "models", model]) fpath = os.path.abspath(fpath) try: - result = bng.run(fpath) + # result = bng.run(fpath, out="lib_test_runs") + # ONLY works if out folder is specified -- WHY? + # seems like an issue with try-except - works fine in separate .ipynb + bng.run(fpath) success += 1 succ.append(model) except: @@ -123,4 +129,4 @@ def test_model_running_lib(): print(sorted(succ)) print("fail: {}".format(fails)) print(sorted(fail)) - assert fails == 0 \ No newline at end of file + assert fails == 0