diff --git a/client/models/sweep-parameter.js b/client/models/sweep-parameter.js
index ca35393999..e9efdf0a38 100644
--- a/client/models/sweep-parameter.js
+++ b/client/models/sweep-parameter.js
@@ -1,14 +1,17 @@
/*
StochSS is a platform for simulating biochemical systems
Copyright (C) 2019-2020 StochSS developers.
+
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
+
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
+
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
diff --git a/client/models/sweep-parameters.js b/client/models/sweep-parameters.js
index c9f08e3340..eb60b7f7c0 100644
--- a/client/models/sweep-parameters.js
+++ b/client/models/sweep-parameters.js
@@ -1,14 +1,17 @@
/*
StochSS is a platform for simulating biochemical systems
Copyright (C) 2019-2020 StochSS developers.
+
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
+
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
+
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
diff --git a/client/pages/file-browser.js b/client/pages/file-browser.js
index 1cf0296c34..3ebc068764 100644
--- a/client/pages/file-browser.js
+++ b/client/pages/file-browser.js
@@ -842,6 +842,24 @@ let FileBrowser = PageView.extend({
}
});
},
+ extractAll: function (o) {
+ let self = this;
+ let queryStr = "?path=" + o.original._path;
+ let endpoint = path.join(app.getApiPath(), "file/unzip") + queryStr;
+ app.getXHR(endpoint, {
+ success: function (err, response, body) {
+ let node = $('#models-jstree').jstree().get_node(o.parent);
+ if(node.type === "root"){
+ self.refreshJSTree();
+ }else{
+ $('#models-jstree').jstree().refresh_node(node);
+ }
+ },
+ error: function (err, response, body) {
+ let modal = $(modals.newProjectModelErrorHtml(body.Reason, body.message)).modal();
+ }
+ });
+ },
setupJstree: function () {
var self = this;
$.jstree.defaults.contextmenu.items = (o, cb) => {
@@ -1235,6 +1253,18 @@ let FileBrowser = PageView.extend({
}
}
}
+ //Specific to zip archives
+ let extractAll = {
+ "extractAll" : {
+ "label" : "Extract All",
+ "_disabled" : false,
+ "separator_before" : false,
+ "separator_after" : false,
+ "action" : function (data) {
+ self.extractAll(o);
+ }
+ }
+ }
if (o.type === 'root'){
return folder
}
@@ -1253,6 +1283,9 @@ let FileBrowser = PageView.extend({
if (o.type === 'workflow') {
return $.extend(open, workflow, common)
}
+ if (o.text.endsWith(".zip")) {
+ return $.extend(open, extractAll, common)
+ }
if (o.type === 'notebook' || o.type === "other") {
return $.extend(open, common)
}
diff --git a/client/styles/styles.css b/client/styles/styles.css
index 5c1f35ea3a..29403e2122 100644
--- a/client/styles/styles.css
+++ b/client/styles/styles.css
@@ -1,3 +1,21 @@
+/*
+StochSS is a platform for simulating biochemical systems
+Copyright (C) 2019-2020 StochSS developers.
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+
body {
min-width: 650px
}
diff --git a/client/templates/includes/reactionsEditor.pug b/client/templates/includes/reactionsEditor.pug
index 0ec990c9c6..b27791913b 100644
--- a/client/templates/includes/reactionsEditor.pug
+++ b/client/templates/includes/reactionsEditor.pug
@@ -35,6 +35,8 @@ div#reactions-editor.card.card-body
tbody(data-hook="reaction-list")
div.col-md-5.container-part(data-hook="reaction-details-container")
+
+ div(data-hook="massaction-message"): p.text-info To add a mass action reaction the model must have at least one parameter
div(data-hook="process-component-error"): p.text-danger A model must have at least one reaction, event, or rule
diff --git a/client/views/file-browser-view.js b/client/views/file-browser-view.js
index aff25ff023..6a317fbb74 100644
--- a/client/views/file-browser-view.js
+++ b/client/views/file-browser-view.js
@@ -924,6 +924,24 @@ module.exports = View.extend({
}
});
},
+ extractAll: function (o) {
+ let self = this;
+ let queryStr = "?path=" + o.original._path;
+ let endpoint = path.join(app.getApiPath(), "file/unzip") + queryStr;
+ app.getXHR(endpoint, {
+ success: function (err, response, body) {
+ let node = $('#models-jstree-view').jstree().get_node(o.parent);
+ if(node.type === "root"){
+ self.refreshJSTree();
+ }else{
+ $('#models-jstree-view').jstree().refresh_node(node);
+ }
+ },
+ error: function (err, response, body) {
+ let modal = $(modals.newProjectModelErrorHtml(body.Reason, body.message)).modal();
+ }
+ });
+ },
setupJstree: function () {
var self = this;
$.jstree.defaults.contextmenu.items = (o, cb) => {
@@ -1343,6 +1361,18 @@ module.exports = View.extend({
}
}
}
+ //Specific to zip archives
+ let extractAll = {
+ "extractAll" : {
+ "label" : "Extract All",
+ "_disabled" : false,
+ "separator_before" : false,
+ "separator_after" : false,
+ "action" : function (data) {
+ self.extractAll(o);
+ }
+ }
+ }
if (o.type === 'root'){
return $.extend(refresh, project, commonFolder, downloadWCombine, {"Rename": common.Rename})
}
@@ -1367,6 +1397,9 @@ module.exports = View.extend({
if (o.type === 'workflow') {
return $.extend(open, workflow, downloadWCombine, common)
}
+ if (o.text.endsWith(".zip")) {
+ return $.extend(open, extractAll, download, common)
+ }
if (o.type === 'notebook' || o.type === "other") {
return $.extend(open, download, common)
}
diff --git a/client/views/reactions-editor.js b/client/views/reactions-editor.js
index fe3bbd03ef..10647b431e 100644
--- a/client/views/reactions-editor.js
+++ b/client/views/reactions-editor.js
@@ -133,9 +133,11 @@ module.exports = View.extend({
},
toggleReactionTypes: function (e, prev, curr) {
if(curr && curr.add && this.collection.parent.parameters.length === 1){
+ $(this.queryByHook('massaction-message')).prop('hidden', true);
$(this.queryByHook('add-reaction-full')).prop('hidden', false);
$(this.queryByHook('add-reaction-partial')).prop('hidden', true);
}else if(curr && !curr.add && this.collection.parent.parameters.length === 0){
+ $(this.queryByHook('massaction-message')).prop('hidden', false);
$(this.queryByHook('add-reaction-full')).prop('hidden', true);
$(this.queryByHook('add-reaction-partial')).prop('hidden', false);
}
diff --git a/stochss/handlers/__init__.py b/stochss/handlers/__init__.py
index 1703f4b6b4..f52ecc0747 100644
--- a/stochss/handlers/__init__.py
+++ b/stochss/handlers/__init__.py
@@ -63,6 +63,7 @@ def get_page_handlers(route_start):
(r"/stochss/api/file/download-zip\/?", DownloadZipFileAPIHandler),
(r"/stochss/api/file/json-data\/?", JsonFileAPIHandler),
(r"/stochss/api/file/duplicate\/?", DuplicateModelHandler),
+ (r"/stochss/api/file/unzip\/?", UnzipFileAPIHandler),
(r"/stochss/api/directory/duplicate\/?", DuplicateDirectoryHandler),
(r"/stochss/api/directory/create\/?", CreateDirectoryHandler),
(r"/stochss/api/spatial/to-model\/?", ConvertToModelAPIHandler),
diff --git a/stochss/handlers/file_browser.py b/stochss/handlers/file_browser.py
index 11fce21679..7d35d52423 100644
--- a/stochss/handlers/file_browser.py
+++ b/stochss/handlers/file_browser.py
@@ -674,3 +674,30 @@ async def get(self):
log.debug("Response: %s", resp)
self.write(resp)
self.finish()
+
+
+class UnzipFileAPIHandler(APIHandler):
+ '''
+ ################################################################################################
+ Handler for unzipping zip archives.
+ ################################################################################################
+ '''
+
+ async def get(self):
+ '''
+ Unzip a zip archive.
+
+ Attributes
+ ----------
+ '''
+ self.set_header('Content-Type', 'application/json')
+ path = self.get_query_argument(name="path")
+ log.debug("The path to the zip archive: %s", path)
+ try:
+ file = StochSSFile(path=path)
+ resp = file.unzip(from_upload=False)
+ log.debug("Response Message: %s", resp)
+ self.write(resp)
+ except StochSSAPIError as err:
+ report_error(self, log, err)
+ self.finish()
diff --git a/stochss/handlers/util/ensemble_simulation.py b/stochss/handlers/util/ensemble_simulation.py
index e68bea8560..925922e2c2 100644
--- a/stochss/handlers/util/ensemble_simulation.py
+++ b/stochss/handlers/util/ensemble_simulation.py
@@ -21,13 +21,14 @@
import json
import pickle
import logging
+import traceback
import numpy
import plotly
-from gillespy2 import TauLeapingSolver, TauHybridSolver, SSACSolver, ODESolver
+from gillespy2 import TauHybridSolver
from .stochss_job import StochSSJob
-from .stochss_errors import StochSSAPIError
+from .stochss_errors import StochSSAPIError, StochSSJobResultsError
log = logging.getLogger("stochss")
@@ -59,36 +60,57 @@ def __init__(self, path, preview=False):
def __get_run_settings(self):
- solver_map = {"SSA":SSACSolver, "Tau-Leaping":TauLeapingSolver,
- "ODE":ODESolver, "Hybrid-Tau-Leaping":TauHybridSolver}
+ solver_map = {"ODE":self.g_model.get_best_solver_algo("ODE"),
+ "SSA":self.g_model.get_best_solver_algo("SSA"),
+ "Tau-Leaping":self.g_model.get_best_solver_algo("Tau-Leaping"),
+ "Hybrid-Tau-Leaping":TauHybridSolver}
run_settings = self.get_run_settings(settings=self.settings, solver_map=solver_map)
- if run_settings['solver'].name == "SSACSolver":
+ instance_solvers = ["SSACSolver", "TauLeapingCSolver", "ODECSolver"]
+ if run_settings['solver'].name in instance_solvers :
run_settings['solver'] = run_settings['solver'](model=self.g_model)
return run_settings
+ def __store_csv_results(self, results):
+ try:
+ results.to_csv(path="results", nametag="results_csv", stamp=self.get_time_stamp())
+ except Exception as err:
+ log.error("Error storing csv results: %s\n%s",
+ str(err), traceback.format_exc())
+
+
@classmethod
- def __plot_results(cls, results):
- plots = {"trajectories":results.plotplotly(return_plotly_figure=True)}
- if len(results) > 1:
- plots['stddevran'] = results.plotplotly_std_dev_range(return_plotly_figure=True)
- std_res = results.stddev_ensemble()
- plots['stddev'] = std_res.plotplotly(return_plotly_figure=True)
- avg_res = results.average_ensemble()
- plots['avg'] = avg_res.plotplotly(return_plotly_figure=True)
- for _, plot in plots.items():
- plot["config"] = {"responsive":True}
- with open('results/plots.json', 'w') as plots_file:
- json.dump(plots, plots_file, cls=plotly.utils.PlotlyJSONEncoder,
- indent=4, sort_keys=True)
-
-
- def __store_results(self, results):
- if not 'results' in os.listdir():
- os.mkdir('results')
- with open('results/results.p', 'wb') as results_file:
- pickle.dump(results, results_file)
- results.to_csv(path="results", nametag="results_csv", stamp=self.get_time_stamp())
+ def __store_pickled_results(cls, results):
+ try:
+ with open('results/results.p', 'wb') as results_file:
+ pickle.dump(results, results_file)
+ except Exception as err:
+ message = f"Error storing pickled results: {err}\n{traceback.format_exc()}"
+ log.error(message)
+ return message
+ return False
+
+
+ @classmethod
+ def __store_result_plots(cls, results):
+ try:
+ plots = {"trajectories":results.plotplotly(return_plotly_figure=True)}
+ if len(results) > 1:
+ plots['stddevran'] = results.plotplotly_std_dev_range(return_plotly_figure=True)
+ std_res = results.stddev_ensemble()
+ plots['stddev'] = std_res.plotplotly(return_plotly_figure=True)
+ avg_res = results.average_ensemble()
+ plots['avg'] = avg_res.plotplotly(return_plotly_figure=True)
+ for _, plot in plots.items():
+ plot["config"] = {"responsive":True}
+ with open('results/plots.json', 'w') as plots_file:
+ json.dump(plots, plots_file, cls=plotly.utils.PlotlyJSONEncoder,
+ indent=4, sort_keys=True)
+ except Exception as err:
+ message = f"Error storing result plots: {err}\n{traceback.format_exc()}"
+ log.error(message)
+ return message
+ return False
def __update_timespan(self):
@@ -126,7 +148,7 @@ def run(self, preview=False, verbose=True):
log.info("Running the ensemble simulation")
if self.settings['simulationSettings']['isAutomatic']:
self.__update_timespan()
- is_ode = self.g_model.get_best_solver().name == "ODESolver"
+ is_ode = self.g_model.get_best_solver().name in ["ODESolver", "ODECSolver"]
results = self.g_model.run(number_of_trajectories=1 if is_ode else 100)
else:
kwargs = self.__get_run_settings()
@@ -135,8 +157,15 @@ def run(self, preview=False, verbose=True):
if verbose:
log.info("The ensemble simulation has completed")
log.info("Storing the results as pickle and csv")
- self.__store_results(results=results)
+ if not 'results' in os.listdir():
+ os.mkdir('results')
+ self.__store_csv_results(results=results)
+ pkl_err = self.__store_pickled_results(results=results)
if verbose:
log.info("Storing the polts of the results")
- self.__plot_results(results=results)
+ plt_err = self.__store_result_plots(results=results)
+ if pkl_err and plt_err:
+ message = "An unexpected error occured with the result object"
+ trace = f"{pkl_err}\n{plt_err}"
+ raise StochSSJobResultsError(message, trace)
return None
diff --git a/stochss/handlers/util/parameter_sweep.py b/stochss/handlers/util/parameter_sweep.py
index 7fafa79ce7..2b2c1dca85 100644
--- a/stochss/handlers/util/parameter_sweep.py
+++ b/stochss/handlers/util/parameter_sweep.py
@@ -22,16 +22,18 @@
import pickle
import logging
import itertools
+import traceback
import numpy
import plotly
-from gillespy2 import TauLeapingSolver, TauHybridSolver, SSACSolver, ODESolver
+from gillespy2 import TauHybridSolver
from .stochss_job import StochSSJob
from .parameter_sweep_1d import ParameterSweep1D
from .parameter_sweep_2d import ParameterSweep2D
from .parameter_scan import ParameterScan
+from .stochss_errors import StochSSJobResultsError
log = logging.getLogger("stochss")
@@ -71,77 +73,115 @@ def __init__(self, path):
def __get_run_settings(self):
- solver_map = {"SSA":SSACSolver, "Tau-Leaping":TauLeapingSolver,
- "ODE":ODESolver, "Hybrid-Tau-Leaping":TauHybridSolver}
+ instance_solvers = ["SSACSolver", "TauLeapingCSolver", "ODECSolver"]
if self.settings['simulationSettings']['isAutomatic']:
- solver_name = self.g_model.get_best_solver().name
- kwargs = {"number_of_trajectories":1 if solver_name == "ODESolver" else 20}
- if solver_name != "SSACSolver":
+ solver = self.g_model.get_best_solver()
+ kwargs = {"number_of_trajectories":1 if "ODE" in solver.name else 20}
+ if solver.name not in instance_solvers:
return kwargs
- kwargs['solver'] = solver_map['SSA'](model=self.g_model)
+ kwargs['solver'] = solver(model=self.g_model)
return kwargs
+ solver_map = {"SSA":self.g_model.get_best_solver_algo("SSA"),
+ "Tau-Leaping":self.g_model.get_best_solver_algo("Tau-Leaping"),
+ "ODE":self.g_model.get_best_solver_algo("ODE"),
+ "Hybrid-Tau-Leaping":TauHybridSolver}
run_settings = self.get_run_settings(settings=self.settings, solver_map=solver_map)
- if run_settings['solver'].name == "SSACSolver":
+ if run_settings['solver'].name in instance_solvers:
run_settings['solver'] = run_settings['solver'](model=self.g_model)
return run_settings
- def __store_csv_results(self, wkfl):
- if "ODE" not in wkfl.settings['solver'].name and \
- wkfl.settings['number_of_trajectories'] > 1:
- csv_keys = list(itertools.product(["min", "max", "avg", "var", "final"],
+ @classmethod
+ def __report_result_error(cls, trace):
+ message = "An unexpected error occured with the result object"
+ raise StochSSJobResultsError(message, trace)
+
+
+ def __store_csv_results(self, job):
+ try:
+ if "solver" in job.settings.keys():
+ solver_name = job.settings['solver'].name
+ else:
+ solver_name = job.model.get_best_solver().name
+ if "ODE" not in solver_name and job.settings['number_of_trajectories'] > 1:
+ csv_keys = list(itertools.product(["min", "max", "avg", "var", "final"],
+ ["min", "max", "avg", "var"]))
+ else:
+ csv_keys = [["min"], ["max"], ["avg"], ["var"], ["final"]]
+ stamp = self.get_time_stamp()
+ dirname = f"results/results_csv{stamp}"
+ if not os.path.exists(dirname):
+ os.mkdir(dirname)
+ for key in csv_keys:
+ if not isinstance(key, list):
+ key = list(key)
+ path = os.path.join(dirname, f"{'-'.join(key)}.csv")
+ with open(path, "w", newline="") as csv_file:
+ csv_writer = csv.writer(csv_file)
+ job.to_csv(keys=key, csv_writer=csv_writer)
+ except Exception as err:
+ log.error("Error storing csv results: %s\n%s",
+ str(err), traceback.format_exc())
+
+
+ @classmethod
+ def __store_pickled_results(cls, job):
+ try:
+ with open('results/results.p', 'wb') as results_file:
+ pickle.dump(job.ts_results, results_file)
+ except Exception as err:
+ message = f"Error storing pickled results: {err}\n{traceback.format_exc()}"
+ log.error(message)
+ return message
+ return False
+
+
+ @classmethod
+ def __store_result_plots(cls, job):
+ try:
+ mappers = ["min", "max", "avg", "var", "final"]
+ if "solver" in job.settings.keys():
+ solver_name = job.settings['solver'].name
+ else:
+ solver_name = job.model.get_best_solver().name
+ if "ODE" not in solver_name and job.settings['number_of_trajectories'] > 1:
+ keys = list(itertools.product(job.list_of_species, mappers,
["min", "max", "avg", "var"]))
- else:
- csv_keys = [["min"], ["max"], ["avg"], ["var"], ["final"]]
- stamp = self.get_time_stamp()
- dirname = f"results/results_csv{stamp}"
- if not os.path.exists(dirname):
- os.mkdir(dirname)
- for key in csv_keys:
- if not isinstance(key, list):
+ else:
+ keys = list(itertools.product(job.list_of_species, mappers))
+ plot_figs = {}
+ for key in keys:
key = list(key)
- path = os.path.join(dirname, f"{'-'.join(key)}.csv")
- with open(path, "w", newline="") as csv_file:
- csv_writer = csv.writer(csv_file)
- wkfl.to_csv(keys=key, csv_writer=csv_writer)
+ trace_list = job.get_plotly_traces(keys=key)
+ plt_data = {'title':f"Parameter Sweep - Variable: {key[0]}"}
+ job.get_plotly_layout_data(plt_data=plt_data)
+ layout = plotly.graph_objs.Layout(title=dict(text=plt_data['title'], x=0.5),
+ xaxis=dict(title=plt_data['xaxis_label']),
+ yaxis=dict(title=plt_data['yaxis_label']))
+
+ fig = dict(data=trace_list, layout=layout, config={"responsive": True})
+ plot_figs['-'.join(key)] = fig
+
+ with open('results/plots.json', 'w') as plots_file:
+ json.dump(plot_figs, plots_file, cls=plotly.utils.PlotlyJSONEncoder,
+ indent=4, sort_keys=True)
+ except Exception as err:
+ message = f"Error storing result plots: {err}\n{traceback.format_exc()}"
+ log.error(message)
+ return message
+ return False
@classmethod
- def __store_plots(cls, wkfl):
- mappers = ["min", "max", "avg", "var", "final"]
- if "ODE" not in wkfl.settings['solver'].name and \
- wkfl.settings['number_of_trajectories'] > 1:
- keys = list(itertools.product(wkfl.list_of_species, mappers,
- ["min", "max", "avg", "var"]))
- else:
- keys = list(itertools.product(wkfl.list_of_species, mappers))
- plot_figs = {}
- for key in keys:
- key = list(key)
- trace_list = wkfl.get_plotly_traces(keys=key)
- plt_data = {'title':f"Parameter Sweep - Variable: {key[0]}"}
- wkfl.get_plotly_layout_data(plt_data=plt_data)
- layout = plotly.graph_objs.Layout(title=dict(text=plt_data['title'], x=0.5),
- xaxis=dict(title=plt_data['xaxis_label']),
- yaxis=dict(title=plt_data['yaxis_label']))
-
- fig = dict(data=trace_list, layout=layout, config={"responsive": True})
- plot_figs['-'.join(key)] = fig
-
- with open('results/plots.json', 'w') as plots_file:
- json.dump(plot_figs, plots_file, cls=plotly.utils.PlotlyJSONEncoder,
- indent=4, sort_keys=True)
-
-
- def __store_results(self, wkfl):
- if not 'results' in os.listdir():
- os.mkdir('results')
- with open('results/results.p', 'wb') as results_file:
- pickle.dump(wkfl.ts_results, results_file)
- if wkfl.name != "ParameterScan":
+ def __store_results(cls, job):
+ try:
with open('results/results.json', 'w') as json_file:
- json.dump(wkfl.results, json_file, indent=4, sort_keys=True, cls=NumpyEncoder)
- self.__store_csv_results(wkfl)
+ json.dump(job.results, json_file, indent=4, sort_keys=True, cls=NumpyEncoder)
+ except Exception as err:
+ message = f"Error storing results dictionary: {err}\n{traceback.format_exc()}"
+ log.err(message)
+ return message
+ return False
def configure(self):
@@ -181,22 +221,30 @@ def run(self, verbose=True):
'''
kwargs = self.configure()
if "param" in kwargs.keys():
- wkfl = ParameterSweep1D(**kwargs)
+ job = ParameterSweep1D(**kwargs)
sim_type = "1D parameter sweep"
elif len(kwargs['params']) > 2:
sim_type = "parameter scan"
- wkfl = ParameterScan(**kwargs)
+ job = ParameterScan(**kwargs)
else:
sim_type = "2D parameter sweep"
- wkfl = ParameterSweep2D(**kwargs)
+ job = ParameterSweep2D(**kwargs)
if verbose:
log.info("Running the %s", sim_type)
- wkfl.run(job_id=self.get_file(), verbose=verbose)
+ job.run(job_id=self.get_file(), verbose=verbose)
if verbose:
log.info("The %s has completed", sim_type)
log.info("Storing the results as pickle and csv")
- self.__store_results(wkfl=wkfl)
- if wkfl.name != "ParameterScan":
+ if not 'results' in os.listdir():
+ os.mkdir('results')
+ pkl_err = self.__store_pickled_results(job=job)
+ if job.name != "ParameterScan":
if verbose:
log.info("Storing the polts of the results")
- self.__store_plots(wkfl=wkfl)
+ res_err = self.__store_results(job=job)
+ self.__store_csv_results(job=job)
+ plt_err = self.__store_result_plots(job=job)
+ if pkl_err and res_err and plt_err:
+ self.__report_result_error(trace=f"{res_err}\n{pkl_err}\n{plt_err}")
+ elif pkl_err:
+ self.__report_result_error(trace=pkl_err)
diff --git a/stochss/handlers/util/parameter_sweep_1d.py b/stochss/handlers/util/parameter_sweep_1d.py
index 14fb8a4596..33c580e115 100644
--- a/stochss/handlers/util/parameter_sweep_1d.py
+++ b/stochss/handlers/util/parameter_sweep_1d.py
@@ -91,11 +91,10 @@ def __feature_extraction(self, results, index, verbose=False):
log.debug(' %s population %s=%s', key, species, data)
- def __setup_results(self):
+ def __setup_results(self, solver_name):
for species in self.list_of_species:
spec_res = {}
- if "ODE" not in self.settings['solver'].name and \
- self.settings['number_of_trajectories'] > 1:
+ if "ODE" not in solver_name and self.settings['number_of_trajectories'] > 1:
for m_key in self.MAPPER_KEYS:
spec_res[m_key] = {}
for r_key in self.REDUCER_KEYS:
@@ -135,7 +134,8 @@ def get_plotly_traces(self, keys):
else:
results = self.results[keys[0]][keys[1]]
- visible = self.settings['number_of_trajectories'] > 1
+ visible = "number_of_trajectories" in self.settings.keys() and \
+ self.settings['number_of_trajectories'] > 1
error_y = dict(type="data", array=results[:, 1], visible=visible)
trace_list = [plotly.graph_objs.Scatter(x=self.param['range'],
@@ -172,10 +172,13 @@ def run(self, job_id, verbose=False):
Attributes
----------
'''
- self.__setup_results()
+ if "solver" in self.settings.keys():
+ solver_name = self.settings['solver'].name
+ else:
+ solver_name = self.model.get_best_solver().name
+ self.__setup_results(solver_name=solver_name)
for i, val in enumerate(self.param['range']):
- if "solver" in self.settings.keys() and \
- self.settings['solver'].name == "SSACSolver":
+ if solver_name in ["SSACSolver", "TauLeapingCSolver", "ODECSolver"]:
tmp_mdl = self.model
self.settings['variables'] = {self.param['parameter']:val}
else:
@@ -190,8 +193,7 @@ def run(self, job_id, verbose=False):
else:
key = f"{self.param['parameter']}:{val}"
self.ts_results[key] = tmp_res
- if "ODE" not in self.settings['solver'].name and \
- self.settings['number_of_trajectories'] > 1:
+ if "ODE" not in solver_name and self.settings['number_of_trajectories'] > 1:
self.__ensemble_feature_extraction(results=tmp_res, index=i, verbose=verbose)
else:
self.__feature_extraction(results=tmp_res, index=i, verbose=verbose)
diff --git a/stochss/handlers/util/parameter_sweep_2d.py b/stochss/handlers/util/parameter_sweep_2d.py
index 9f0cca708d..0c909181de 100644
--- a/stochss/handlers/util/parameter_sweep_2d.py
+++ b/stochss/handlers/util/parameter_sweep_2d.py
@@ -90,11 +90,10 @@ def __feature_extraction(self, results, i_ndx, j_ndx, verbose=False):
log.debug(' %s population %s=%s', key, species, data)
- def __setup_results(self):
+ def __setup_results(self, solver_name):
for species in self.list_of_species:
spec_res = {}
- if "ODE" not in self.settings['solver'].name and \
- self.settings['number_of_trajectories'] > 1:
+ if "ODE" not in solver_name and self.settings['number_of_trajectories'] > 1:
for m_key in self.MAPPER_KEYS:
spec_res[m_key] = {}
for r_key in self.REDUCER_KEYS:
@@ -176,11 +175,14 @@ def run(self, job_id, verbose=False):
Attributes
----------
'''
- self.__setup_results()
+ if "solver" in self.settings.keys():
+ solver_name = self.settings['solver'].name
+ else:
+ solver_name = self.model.get_best_solver().name
+ self.__setup_results(solver_name=solver_name)
for i, val1 in enumerate(self.params[0]['range']):
for j, val2 in enumerate(self.params[1]['range']):
- if "solver" in self.settings.keys() and \
- self.settings['solver'].name == "SSACSolver":
+ if solver_name in ["SSACSolver", "TauLeapingCSolver", "ODECSolver"]:
tmp_mdl = self.model
variables = {self.params[0]['parameter']:val1, self.params[1]['parameter']:val2}
self.settings['variables'] = variables
@@ -200,8 +202,7 @@ def run(self, job_id, verbose=False):
key = f"{self.params[0]['parameter']}:{val1},"
key += f"{self.params[1]['parameter']}:{val2}"
self.ts_results[key] = tmp_res
- if "ODE" not in self.settings['solver'].name and \
- self.settings['number_of_trajectories'] > 1:
+ if "ODE" not in solver_name and self.settings['number_of_trajectories'] > 1:
self.__ensemble_feature_extraction(results=tmp_res, i_ndx=i, j_ndx=j,
verbose=verbose)
else:
diff --git a/stochss/handlers/util/scripts/start_job.py b/stochss/handlers/util/scripts/start_job.py
index 3f93fac855..0b52ebd1d8 100755
--- a/stochss/handlers/util/scripts/start_job.py
+++ b/stochss/handlers/util/scripts/start_job.py
@@ -108,9 +108,9 @@ def setup_logger(log_path):
jobs = {"gillespy":EnsembleSimulation, "parameterSweep":ParameterSweep}
try:
os.chdir(args.path)
- wkfl = jobs[args.type](path=args.path)
+ job = jobs[args.type](path=args.path)
job_handler, user_handler = setup_logger("logs.txt")
- wkfl.run(verbose=args.verbose)
+ job.run(verbose=args.verbose)
except Exception as error:
report_error(err=error)
else:
diff --git a/stochss/handlers/util/stochss_errors.py b/stochss/handlers/util/stochss_errors.py
index 66b6eb8257..3ee1cbd411 100644
--- a/stochss/handlers/util/stochss_errors.py
+++ b/stochss/handlers/util/stochss_errors.py
@@ -140,6 +140,27 @@ def __init__(self, msg, trace=None):
'''
super().__init__(403, "Permission Denied", msg, trace)
+
+class StochSSUnzipError(StochSSAPIError):
+ '''
+ ################################################################################################
+ StochSS Un-Zip Zip Archive API Handler Error
+ ################################################################################################
+ '''
+
+ def __init__(self, msg, trace=None):
+ '''
+ Indicates that a problem occured during the extraction process for the zip archive
+
+ Attributes
+ ----------
+ msg : str
+ Details on what caused the error
+ trace : str
+ Error traceback for the error
+ '''
+ super().__init__(403, "Unable to Extract Contents", msg, trace)
+
####################################################################################################
# Model Errors
####################################################################################################
@@ -271,3 +292,24 @@ def __init__(self, msg, trace=None):
Error traceback for the error
'''
super().__init__(406, "Plot Figure Not Available", msg, trace)
+
+
+class StochSSJobResultsError(StochSSAPIError):
+ '''
+ ################################################################################################
+ StochSS Job Results Error
+ ################################################################################################
+ '''
+
+ def __init__(self, msg, trace=None):
+ '''
+ Indicates that the job results object was corrupted
+
+ Attributes
+ ----------
+ msg : str
+ Details on what caused the error
+ trace : str
+ Error traceback for the error
+ '''
+ super().__init__(500, "Job Results Error", msg, trace)
diff --git a/stochss/handlers/util/stochss_file.py b/stochss/handlers/util/stochss_file.py
index 25f5c4f734..fbe99c91d3 100644
--- a/stochss/handlers/util/stochss_file.py
+++ b/stochss/handlers/util/stochss_file.py
@@ -23,7 +23,7 @@
from .stochss_base import StochSSBase
from .stochss_errors import StochSSFileNotFoundError, StochSSPermissionsError, \
- StochSSFileExistsError
+ StochSSFileExistsError, StochSSUnzipError
class StochSSFile(StochSSBase):
'''
@@ -142,7 +142,7 @@ def read(self):
raise StochSSFileNotFoundError(message, traceback.format_exc()) from err
- def unzip(self):
+ def unzip(self, from_upload=True):
'''
Extract the contents of a zip archive
@@ -151,7 +151,6 @@ def unzip(self):
'''
if not self.path.endswith(".zip"):
return []
-
try:
path = self.get_path(full=True)
dirname = self.get_dir_name(full=True)
@@ -167,6 +166,12 @@ def unzip(self):
zip_file.extractall(dirname)
if "__MACOSX" in os.listdir(dirname):
shutil.rmtree(os.path.join(dirname, "__MACOSX"))
- return []
+ if from_upload:
+ return []
+ message = "Successfully extracted the contents of"
+ return {"message": f"{message} {self.get_file()} to {self.get_dir_name()}"}
except zipfile.BadZipFile as err:
- return [str(err)]
+ message = f"{str(err)} so it could not be unzipped."
+ if not from_upload:
+ raise StochSSUnzipError(message, traceback.format_exc()) from err
+ return [message]
diff --git a/stochss/handlers/util/stochss_folder.py b/stochss/handlers/util/stochss_folder.py
index c2451bbb9f..8d9200e65b 100644
--- a/stochss/handlers/util/stochss_folder.py
+++ b/stochss/handlers/util/stochss_folder.py
@@ -100,8 +100,6 @@ def __upload_file(self, file, body, new_name=None):
path = os.path.join(self.path, file)
new_file = StochSSFile(path=path, new=True, body=body)
error = new_file.unzip()
- if error:
- error[0] += " so it could not be unzipped."
file = new_file.get_file()
dirname = new_file.get_dir_name()
message = f"{file} was successfully uploaded to {dirname}"
diff --git a/stochss/handlers/util/stochss_notebook.py b/stochss/handlers/util/stochss_notebook.py
index f3f2e888cb..2a02836cd8 100644
--- a/stochss/handlers/util/stochss_notebook.py
+++ b/stochss/handlers/util/stochss_notebook.py
@@ -40,7 +40,8 @@ class StochSSNotebook(StochSSBase):
MODEL_EXPLORATION = 5
MODEL_INFERENCE = 6
SOLVER_MAP = {"SSACSolver":"SSA", "NumPySSASolver":"SSA", "ODESolver":"ODE", "Solver":"SSA",
- "TauLeapingSolver":"Tau-Leaping", "TauHybridSolver":"Hybrid-Tau-Leaping"}
+ "TauLeapingSolver":"Tau-Leaping", "TauHybridSolver":"Hybrid-Tau-Leaping",
+ "ODECSolver":"ODE", "TauLeapingCSolver":"Tau-Leaping"}
def __init__(self, path, new=False, models=None, settings=None):
'''Intitialize a notebook object and if its new create it on the users file system
@@ -71,7 +72,6 @@ def __init__(self, path, new=False, models=None, settings=None):
if changed:
self.path = n_path.replace(self.user_dir + '/', "")
-
def __create_common_cells(self, interactive_backend=False):
cells = [self.__create_import_cell(interactive_backend=interactive_backend),
nbf.new_markdown_cell(f"# {self.get_name()}"),
@@ -85,11 +85,15 @@ def __create_configuration_cell(self):
pad = " "
config = ["def configure_simulation():"]
# Add solver instantiation line if the c solver are available
- instance_solvers = ["SSACSolver"]
+ instance_solvers = ["SSACSolver", "ODECSolver", "TauLeapingCSolver"]
is_automatic = self.settings['simulationSettings']['isAutomatic']
- if self.is_ssa_c and self.settings['solver'] in instance_solvers:
- commented = is_automatic and self.nb_type <= self.ENSEMBLE_SIMULATION and \
- self.settings['solver'] not in instance_solvers
+ if self.settings['solver'] in instance_solvers:
+ if is_automatic and self.nb_type <= self.ENSEMBLE_SIMULATION:
+ commented = True
+ elif is_automatic and self.settings['solver'] not in instance_solvers:
+ commented = True
+ else:
+ commented = False
start = f"{pad}# " if commented else pad
config.append(f"{start}solver = {self.settings['solver']}(model=model)")
config.append(pad + "kwargs = {")
@@ -201,11 +205,12 @@ def __create_import_cell(self, interactive_backend=False):
imports.append("from gillespy2 import Model, Species, Parameter, Reaction, Event, \\")
imports.append(" EventTrigger, EventAssignment, RateRule, \\")
imports.append(" AssignmentRule, FunctionDefinition")
- ssa = "SSACSolver" if self.is_ssa_c else "NumPySSASolver"
- algorithm_map = {'SSA': f'from gillespy2 import {ssa}',
- 'Tau-Leaping': 'from gillespy2 import TauLeapingSolver',
- 'Hybrid-Tau-Leaping': 'from gillespy2 import TauHybridSolver',
- 'ODE': 'from gillespy2 import ODESolver'}
+ ssa_import = f'from gillespy2 import {self.model.get_best_solver_algo("SSA").name}'
+ tau_import = 'from gillespy2 import '
+ tau_import += f'{self.model.get_best_solver_algo("Tau-Leaping").name}'
+ ode_import = f'from gillespy2 import {self.model.get_best_solver_algo("ODE").name}'
+ algorithm_map = {'SSA': ssa_import, 'Tau-Leaping': tau_import, 'ODE': ode_import,
+ 'Hybrid-Tau-Leaping': 'from gillespy2 import TauHybridSolver'}
algorithm = self.settings['simulationSettings']['algorithm']
for name, alg_import in algorithm_map.items():
if not is_automatic and name == algorithm:
@@ -796,9 +801,11 @@ def __get_gillespy2_run_settings(self):
settings['realizations'] = 1
# Map algorithm for GillesPy2
ssa_solver = "solver" if self.is_ssa_c else "NumPySSASolver"
+ ode_solver = "solver" if self.is_ssa_c else "ODESolver"
+ tau_solver = "solver" if self.is_ssa_c else "TauLeapingSolver"
solver_map = {"SSA":f'"solver":{ssa_solver}',
- "ODE":'"solver":ODESolver',
- "Tau-Leaping":'"solver":TauLeapingSolver',
+ "ODE":f'"solver":{ode_solver}',
+ "Tau-Leaping":f'"solver":{tau_solver}',
"Hybrid-Tau-Leaping":'"solver":TauHybridSolver'}
# Map algorithm settings for GillesPy2. GillesPy2 requires snake case, remap camelCase
settings_map = {"number_of_trajectories":settings['realizations'],
@@ -824,10 +831,10 @@ def __get_gillespy2_solver_name(self):
solver = self.model.get_best_solver().name
self.settings['simulationSettings']['algorithm'] = self.SOLVER_MAP[solver]
return solver
- algorithm_map = {'SSA': "SSACSolver" if self.is_ssa_c else "NumPySSASolver",
- 'Tau-Leaping': 'TauLeapingSolver',
+ algorithm_map = {'SSA': self.model.get_best_solver_algo("SSA").name,
+ 'Tau-Leaping': self.model.get_best_solver_algo("Tau-Leaping").name,
'Hybrid-Tau-Leaping': 'TauHybridSolver',
- 'ODE': 'ODESolver'}
+ 'ODE': self.model.get_best_solver_algo("ODE").name}
return algorithm_map[self.settings['simulationSettings']['algorithm']]
def create_1dps_notebook(self):
@@ -970,10 +977,7 @@ def create_smi_notebook(self):
return {"Message":message, "FilePath":self.get_path(), "File":self.get_file()}
def load(self):
- '''Read the notebook file and return as a dict
-
- Attributes
- ----------'''
+ '''Read the notebook file and return as a dict'''
try:
with open(self.get_path(full=True), "r") as notebook_file:
return json.load(notebook_file)
diff --git a/stochss/handlers/util/stochss_spatial_model.py b/stochss/handlers/util/stochss_spatial_model.py
index c4bd1affe9..e0f444ca0e 100644
--- a/stochss/handlers/util/stochss_spatial_model.py
+++ b/stochss/handlers/util/stochss_spatial_model.py
@@ -54,6 +54,7 @@ def __init__(self, path, new=False, model=None):
if new:
if model is None:
model = self.get_model_template()
+ if not model['is_spatial']:
model['is_spatial'] = True
if isinstance(model, str):
model = json.loads(model)