From f9773ae7776379f3871dd0081ea27d42ff1f089a Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Mon, 19 Oct 2020 12:41:15 -0400 Subject: [PATCH 01/13] Built the first conceptual design for the model exploration settings section. Added a model exploration button to the StochSS workflows section of the workflow selection page. Refactored the load workflow api handler to work with the model exporation type. Added function to render the model exploration settings when the workflow type is model exploration. --- client/pages/workflow-selection.js | 6 +- .../includes/modelExplorationSettings.pug | 117 ++++++++++++++++++ client/templates/includes/workflowEditor.pug | 2 + client/templates/pages/workflowSelection.pug | 3 + client/views/model-exploration-settings.js | 78 ++++++++++++ client/views/workflow-editor.js | 10 ++ stochss/handlers/workflows.py | 4 +- 7 files changed, 217 insertions(+), 3 deletions(-) create mode 100644 client/templates/includes/modelExplorationSettings.pug create mode 100644 client/views/model-exploration-settings.js diff --git a/client/pages/workflow-selection.js b/client/pages/workflow-selection.js index 0254d31ee8..2903b2d280 100644 --- a/client/pages/workflow-selection.js +++ b/client/pages/workflow-selection.js @@ -40,7 +40,8 @@ let workflowSelection = PageView.extend({ "click [data-hook=sciope-model-exploration]" : "notebookWorkflow", "click [data-hook=model-inference]" : "notebookWorkflow", "click [data-hook=stochss-es]" : "handleEnsembleSimulationClick", - "click [data-hook=stochss-ps]" : "handleParameterSweepClick" + "click [data-hook=stochss-ps]" : "handleParameterSweepClick", + "click [data-hook=stochss-me]" : "handleModelExplorationClick" }, initialize: function (attrs, options) { PageView.prototype.initialize.apply(this, arguments); @@ -147,6 +148,9 @@ let workflowSelection = PageView.extend({ handleParameterSweepClick: function (e) { this.launchStochssWorkflow("parameterSweep") }, + handleModelExplorationClick: function (e) { + this.launchStochssWorkflow("modelExploration") + }, launchStochssWorkflow: function (type) { let queryString = "?type=" + type + "&path=" + this.modelDir + "&parentPath=" + this.parentPath let endpoint = path.join(app.getBasePath(), "stochss/workflow/edit")+queryString diff --git a/client/templates/includes/modelExplorationSettings.pug b/client/templates/includes/modelExplorationSettings.pug new file mode 100644 index 0000000000..2a72410773 --- /dev/null +++ b/client/templates/includes/modelExplorationSettings.pug @@ -0,0 +1,117 @@ +div#model-exploration-settings.card.card-body + + div + + h3.inline Model Explorataion Settings + button.btn.btn-outline-collapse(data-toggle="collapse", data-target="#collapse-mexplore-settings", data-hook="collapse") - + + div.collapse(class="show", id="collapse-mexplore-settings") + + div.row + + div.col-md-4 + + span.inline(for="distribution-type") Distribution Type: + + div.inline(id="distribution-type" data-hook="distribution-type") + + div.col-md-4(style="display: none") + + span.inline(for="exploration-type") Exploration Type: + + div.inline(id="exploration-type" data-hook="exploration-type") + + div.col-md-4(style="display: none") + + span.inline(for="feature-extraction") Feature Extraction: + + div.inline(id="feature-extraction" data-hook="feature-extraction") + + div + + h5 Configure Variable(s) + + div(data-hook="uniform-prior") + + table.table + thead + tr + th(scope="col") + div + div.inline Sweep Variable + + th(scope="col") + div + div.inline Current Value + + th(scope="col") + div + div.inline Minimum Value + + th(scope="col") + div + div.inline Maximum Value + + th(scope="col") + div + div.inline Steps + + tbody(data-hook="ufp-variables") + + div(data-hook="factorial" style="display: none") + + table.table + thead + tr + th(scope="col") + div + div.inline Sweep Variable + + th(scope="col") + div + div.inline Current Value + + th(scope="col") + div + div.inline Minimum Value + + th(scope="col") + div + div.inline Maximum Value + + th(scope="col") + div + div.inline Level + + th(scope="col") + div + div.inline Outliers + + tboby(data-hook="ftl-variables") + + div(data-hook="latin-hypercube" style="display: none") + + table.table + thead + tr + th(scope="col") + div + div.inline Sweep Variable + + th(scope="col") + div + div.inline Current Value + + th(scope="col") + div + div.inline Minimum Value + + th(scope="col") + div + div.inline Maximum Value + + th(scope="col") + div + div.inline Seed Size + + tboby(data-hook="lhc-variables") diff --git a/client/templates/includes/workflowEditor.pug b/client/templates/includes/workflowEditor.pug index 631555a4d6..4e0e4f4c1a 100644 --- a/client/templates/includes/workflowEditor.pug +++ b/client/templates/includes/workflowEditor.pug @@ -12,6 +12,8 @@ div#workflow-editor.card.card-body div.collapse(class="show" data-hook="param-sweep-settings-container") + div.collapse(class="show" data-hook="model-exploration-settings-container") + div(data-hook="sim-settings-container") div(data-hook="workflow-state-buttons-container") \ No newline at end of file diff --git a/client/templates/pages/workflowSelection.pug b/client/templates/pages/workflowSelection.pug index c4de55d0b6..cc072a5c28 100644 --- a/client/templates/pages/workflowSelection.pug +++ b/client/templates/pages/workflowSelection.pug @@ -31,6 +31,9 @@ section.page.col-md-10 div.tooltip-icon.horizontal-space(data-html="true" data-toggle="tooltip" title=this.tooltips.parameterSweep) + td + button.btn.btn-outline-primary.box-shadow.inline(id="stochss-me" data-hook="stochss-me") Model Exploration + table.table thead tr diff --git a/client/views/model-exploration-settings.js b/client/views/model-exploration-settings.js new file mode 100644 index 0000000000..b9fee3b547 --- /dev/null +++ b/client/views/model-exploration-settings.js @@ -0,0 +1,78 @@ +let $ = require('jquery'); +//views +let View = require('ampersand-view'); +var SelectView = require('ampersand-select-view'); +//templates +let template = require('../templates/includes/modelExplorationSettings.pug'); + +module.exports = View.extend({ + template: template, + events: { + 'change [data-hook=distribution-type]' : 'selectDistributionType', + }, + initialize: function (attrs, options) { + View.prototype.initialize.apply(this, arguments) + }, + render: function (attrs, options) { + View.prototype.render.apply(this, arguments) + this.renderDistributionTypeSelect(); + this.renderExplorationTypeSelect(); + this.renderFeatureExtractionSelect(); + }, + renderDistributionTypeSelect: function () { + let options = ["Uniform", "Factorial", "Latin Hypercube"] + var distributionTypeView = new SelectView({ + label: '', + name: 'distribution-type', + required: true, + idAttribute: 'cid', + options: options, + value: "Uniform" + }); + this.registerRenderSubview(distributionTypeView, "distribution-type") + }, + renderExplorationTypeSelect: function () { + let options = ["Basic Exploration"] + var explorationTypeView = new SelectView({ + label: '', + name: 'distribution-type', + required: true, + idAttribute: 'cid', + options: options, + value: "Basic Exploration" + }); + this.registerRenderSubview(explorationTypeView, "exploration-type") + }, + renderFeatureExtractionSelect: function () { + let options = ["Minimum of population", "Maximum of population", "Average of population", + "Variance of population", "Population at last time point"] + var featureExtractionView = new SelectView({ + label: '', + name: 'distribution-type', + required: true, + idAttribute: 'cid', + options: options, + value: "Population at last time point" + }); + this.registerRenderSubview(featureExtractionView, "feature-extraction") + }, + registerRenderSubview: function (view, hook) { + this.registerSubview(view); + this.renderSubview(view, this.queryByHook(hook)); + }, + selectDistributionType: function (e) { + let typeDict = {"Uniform":"uniform-prior", "Factorial":"factorial", "Latin Hypercube":"latin-hypercube"} + let type = e.target.selectedOptions.item(0).text; + $(this.queryByHook(typeDict[type])).css("display", "block") + if(type === "Uniform") { + $(this.queryByHook("factorial")).css("display", "none") + $(this.queryByHook("latin-hypercube")).css("display", "none") + }else if(type === "Factorial") { + $(this.queryByHook("uniform-prior")).css("display", "none") + $(this.queryByHook("latin-hypercube")).css("display", "none") + }else{ + $(this.queryByHook("uniform-prior")).css("display", "none") + $(this.queryByHook("factorial")).css("display", "none") + } + } +}); \ No newline at end of file diff --git a/client/views/workflow-editor.js b/client/views/workflow-editor.js index 02c3674d81..0150b02996 100644 --- a/client/views/workflow-editor.js +++ b/client/views/workflow-editor.js @@ -27,6 +27,7 @@ var SimSettingsView = require('./simulation-settings'); var SimulationSettingsViewer = require('./simulation-settings-viewer'); var ParamSweepSettingsView = require('./parameter-sweep-settings'); var ParameterSweepSettingsViewer = require('./parameter-sweep-settings-viewer'); +var ModelExploreSettingsView = require('./model-exploration-settings'); var WorkflowStateButtonsView = require('./workflow-state-buttons'); //templates var template = require('../templates/includes/workflowEditor.pug'); @@ -53,6 +54,8 @@ module.exports = View.extend({ if(this.type === "parameterSweep"){ this.validatePsweep() this.renderParameterSweepSettingsView() + }else if(this.type === "modelExploration") { + this.renderModelExplorationSettings() } this.renderWorkflowStateButtons() }else{ @@ -97,6 +100,13 @@ module.exports = View.extend({ }); this.registerRenderSubview(parameterSweepSettingsView, 'param-sweep-settings-container'); }, + renderModelExplorationSettings: function () { + if(this.modelExplorationSettings) { + this.modelExplorationSettings.remove() + } + this.modelExplorationSettings = new ModelExploreSettingsView() + this.registerRenderSubview(this.modelExplorationSettings, 'model-exploration-settings-container') + }, renderWorkflowStateButtons: function () { if(this.workflowStateButtons) { this.workflowStateButtons.remove() diff --git a/stochss/handlers/workflows.py b/stochss/handlers/workflows.py index a4fe0e46f2..5ee94ad2a4 100644 --- a/stochss/handlers/workflows.py +++ b/stochss/handlers/workflows.py @@ -70,8 +70,8 @@ async def get(self): log.debug("Time stamp of the workflow: %s", stamp) log.debug("The type of the workflow: %s", wkfl_type) log.debug("The path to the workflow/model: %s", path) - title_types = {"gillespy":"Ensemble Simulation", "parameterSweep":"Parameter Sweep"} - name_types = {"gillespy":"_ES", "parameterSweep":"_PS"} + title_types = {"gillespy":"Ensemble Simulation", "parameterSweep":"Parameter Sweep", "modelExploration":"Model Exploration"} + name_types = {"gillespy":"_ES", "parameterSweep":"_PS", "modelExploration":"_ME"} parent_path = self.get_query_argument(name='parentPath', default=os.path.dirname(path)) if path.endswith('.mdl'): resp = {"mdlPath":path, "timeStamp":stamp, "type":wkfl_type, From c02382d32f1cd0d16a4ede5d610ff99446245b44 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Wed, 28 Oct 2020 09:46:52 -0400 Subject: [PATCH 02/13] Linted the __init__.py file. --- stochss/handlers/__init__.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/stochss/handlers/__init__.py b/stochss/handlers/__init__.py index 6719ee5c47..dadc8c10ed 100644 --- a/stochss/handlers/__init__.py +++ b/stochss/handlers/__init__.py @@ -27,6 +27,12 @@ from .log import init_log def get_page_handlers(route_start): + ''' + Get the list of all page and api handlers + + Atributes + --------- + ''' handlers = [ # ## Page Handlers From 959adafcfaf1c191bf21c7d46decbb3c716c84c1 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Wed, 28 Oct 2020 09:49:48 -0400 Subject: [PATCH 03/13] Linted the file_browser.py. --- stochss/handlers/file_browser.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/stochss/handlers/file_browser.py b/stochss/handlers/file_browser.py index 39a00b7d1d..edec479544 100644 --- a/stochss/handlers/file_browser.py +++ b/stochss/handlers/file_browser.py @@ -16,14 +16,6 @@ along with this program. If not, see . ''' -''' -APIHandler documentation: -https://github.com/jupyter/notebook/blob/master/notebook/base/handlers.py#L583 - -Note APIHandler.finish() sets Content-Type handler to 'application/json' - -Use finish() for json, write() for text -''' import os import json import uuid @@ -44,7 +36,7 @@ from .util.convert_to_sbml import convert_to_sbml from .util.convert_sbml_to_model import convert_sbml_to_model from .util.generate_zip_file import download_zip -from .util.upload_file import upload, upload_from_link +from .util.upload_file import upload from .util.workflow_status import get_status log = logging.getLogger('stochss') @@ -895,14 +887,18 @@ async def get(self): if cmd is None: outfile = str(uuid.uuid4()).replace("-", "_")+".tmp" log.debug("Response file name: %s", outfile) - exec_cmd = ['/stochss/stochss/handlers/util/upload_file.py', '{0}'.format(path), '{}'.format(outfile)] # Script commands for read run_cmd + # Script commands for read run_cmd + exec_cmd = ['/stochss/stochss/handlers/util/upload_file.py', + '{0}'.format(path), '{}'.format(outfile)] log.debug("Exec command: %s", exec_cmd) pipe = subprocess.Popen(exec_cmd) resp = {"responsePath": outfile} log.debug("Response: %s", resp) self.write(resp) else: - exec_cmd = ['/stochss/stochss/handlers/util/upload_file.py', 'None', '{}'.format(path)] # Script commands for read run_cmd + # Script commands for read run_cmd + exec_cmd = ['/stochss/stochss/handlers/util/upload_file.py', + 'None', '{}'.format(path)] log.debug("Exec command: %s", exec_cmd) pipe = subprocess.Popen(exec_cmd, stdout=subprocess.PIPE, text=True) results, error = pipe.communicate() From 287ab0712209566095659302cfdb826aee95bf2d Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Wed, 28 Oct 2020 10:00:25 -0400 Subject: [PATCH 04/13] Linted the log.py file. --- stochss/handlers/log.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/stochss/handlers/log.py b/stochss/handlers/log.py index 795f7f09eb..4e1e7828b8 100644 --- a/stochss/handlers/log.py +++ b/stochss/handlers/log.py @@ -22,10 +22,17 @@ log = logging.getLogger('stochss') def init_log(): - ch = logging.StreamHandler() - formatter = LogFormatter(fmt='%(color)s[%(levelname)1.1s %(asctime)s StochSS %(filename)s:%(lineno)d]%(end_color)s %(message)s', datefmt='%H:%M:%S') - ch.setFormatter(formatter) + ''' + Set up StochSS logger + + Attributes + ---------- + ''' + handler = logging.StreamHandler() + formatter = LogFormatter(fmt='%(color)s[%(levelname)1.1s %(asctime)s \ + StochSS %(filename)s:%(lineno)d]%(end_color)s %(message)s', + datefmt='%H:%M:%S') + handler.setFormatter(formatter) log.setLevel(logging.WARNING) - log.addHandler(ch) + log.addHandler(handler) log.propagate = False - From 115342089a996752aa8f732c113fc8da1cf064c7 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Wed, 28 Oct 2020 10:01:58 -0400 Subject: [PATCH 05/13] Linted the models.py file. --- stochss/handlers/models.py | 212 ++++++++++++++++++++++++++----------- 1 file changed, 151 insertions(+), 61 deletions(-) diff --git a/stochss/handlers/models.py b/stochss/handlers/models.py index c2e23a00a9..2aee09b7b0 100644 --- a/stochss/handlers/models.py +++ b/stochss/handlers/models.py @@ -52,62 +52,99 @@ async def get(self): purpose = self.get_query_argument(name="for") log.debug("Purpose of the handler: %s", purpose) file_path = self.get_query_argument(name="path") - log.debug("Path to the file: {0}".format(file_path)) + log.debug("Path to the file: %s", file_path) if file_path.startswith('/'): file_path = file_path.replace('/', '', 1) full_path = os.path.join('/home/jovyan', file_path) - log.debug("Full path to the file: {0}".format(full_path)) + log.debug("Full path to the file: %s", full_path) self.set_header('Content-Type', 'application/json') if os.path.exists(full_path): - with open(full_path, 'r') as f: - data = json.load(f) - log.debug("Contents of the json file: {0}".format(data)) - if full_path.endswith(".mdl"): - self.update_model_data(data) - if "volume" not in data.keys(): - data['volume'] = data['modelSettings']['volume'] - del data['modelSettings']['volume'] - self.write(data) + self.read_json_file(full_path) elif purpose == "edit": - new_path = '/stochss/stochss_templates/nonSpatialModelTemplate.json' - log.debug("Path to the model template: {0}".format(new_path)) - try: - with open(new_path, 'r') as json_file: - template = json.load(json_file) - log.debug("Contents of the model template: {0}".format(template)) - directories = os.path.dirname(full_path) - log.debug("Path of parent directories: {0}".format(directories)) - try: - os.makedirs(directories) - except FileExistsError: - log.debug("The directories in the path to the model already exists.") - with open(full_path, 'w') as file: - json.dump(template, file) - self.write(template) - except FileNotFoundError as err: - self.set_status(404) - error = {"Reason":"Model Template Not Found", "Message":"Could not find the model template file: "+str(err)} - trace = traceback.format_exc() - log.error("Exception information: {0}\n{1}".format(error, trace)) - error['Traceback'] = trace - self.write(error) - except JSONDecodeError as err: - self.set_status(406) - error = {"Reason":"Template Data Not JSON Format", "Message":"Template data is not JSON decodeable: "+str(err)} - trace = traceback.format_exc() - log.error("Exception information: {0}\n{1}".format(error, trace)) - error['Traceback'] = trace - self.write(error) + self.write_new_model_file(full_path) else: self.set_status(404) - error = {"Reason":"Model Not Found", "Message":"Could not find the model file: "+file_path} - log.error("Exception information: {0}".format(error)) + error = {"Reason":"Model Not Found", + "Message":"Could not find the model file: "+file_path} + log.error("Exception information: %s", error) self.write(error) self.finish() + def read_json_file(self, full_path): + ''' + Get the contents of the target json file + + Attributes + ---------- + full_path : str + Path to the target json file + ''' + with open(full_path, 'r') as file: + data = json.load(file) + log.debug("Contents of the json file: %s", data) + if full_path.endswith(".mdl"): + self.update_model_data(data) + if "volume" not in data.keys(): + data['volume'] = data['modelSettings']['volume'] + del data['modelSettings']['volume'] + self.write(data) + + + def write_new_model_file(self, full_path): + ''' + Write a new model file using the model template + + Attributes + ---------- + full_path : str + Path to the new model file + ''' + new_path = '/stochss/stochss_templates/nonSpatialModelTemplate.json' + log.debug("Path to the model template: %s", new_path) + try: + with open(new_path, 'r') as json_file: + template = json.load(json_file) + log.debug("Contents of the model template: %s", template) + directories = os.path.dirname(full_path) + log.debug("Path of parent directories: %s", directories) + try: + os.makedirs(directories) + except FileExistsError: + log.debug("The directories in the path to the model already exists.") + with open(full_path, 'w') as file: + json.dump(template, file) + self.write(template) + except FileNotFoundError as err: + self.set_status(404) + error = {"Reason":"Model Template Not Found", + "Message":"Could not find the model template file: "+str(err)} + trace = traceback.format_exc() + log.error("Exception information: %s", "{0}\n{1}".format(error, trace)) + error['Traceback'] = trace + self.write(error) + except JSONDecodeError as err: + self.set_status(406) + error = {"Reason":"Template Data Not JSON Format", + "Message":"Template data is not JSON decodeable: "+str(err)} + trace = traceback.format_exc() + log.error("Exception information: %s", "{0}\n{1}".format(error, trace)) + error['Traceback'] = trace + self.write(error) + + @classmethod def update_parameter(cls, param, param_ids): + ''' + Update the expression of a StochSS Parameter + + Attributes + ---------- + param : dict + StochSS Parameter + param_ids : list + list of compIDs for the StochSS parameters collection + ''' try: param_ids.append(param['compID']) if isinstance(param['expression'], str): @@ -121,10 +158,19 @@ def update_parameter(cls, param, param_ids): @classmethod def update_reaction_rate(cls, reaction): + ''' + Update the expression of a StochSS Reactions rate parameter + + Attributes + ---------- + reaction : dict + StochSS Reaction + ''' try: if reaction['rate'].keys() and isinstance(reaction['rate']['expression'], str): try: - reaction['rate']['expression'] = ast.literal_eval(reaction['rate']['expression']) + value = ast.literal_eval(reaction['rate']['expression']) + reaction['rate']['expression'] = value except ValueError: pass except KeyError: @@ -133,10 +179,22 @@ def update_reaction_rate(cls, reaction): @classmethod def update_event_assignment(cls, assignment, param_ids): + ''' + Update the target of a StochSS Event Assignment in an event + if its a StochSS Parameter. + + Attributes + ---------- + assignment : dict + StochSS Event Assignment + param_ids : list + list of compIDs for the StochSS parameters collection + ''' try: if assignment['variable']['compID'] in param_ids: try: - assignment['variable']['expression'] = ast.literal_eval(assignment['variable']['expression']) + value = ast.literal_eval(assignment['variable']['expression']) + assignment['variable']['expression'] = value except ValueError: pass except KeyError: @@ -145,6 +203,16 @@ def update_event_assignment(cls, assignment, param_ids): @classmethod def update_event_targets(cls, event, param_ids): + ''' + Update each StochSS Event Assignment in an event. + + Attributes + ---------- + event : dict + StochSS Event + param_ids : list + list of compIDs for the StochSS parameters collection + ''' try: if "eventAssignments" in event.keys(): for assignment in event['eventAssignments']: @@ -155,10 +223,21 @@ def update_event_targets(cls, event, param_ids): @classmethod def update_rule_targets(cls, rule, param_ids): + ''' + Update the target of a StochSS Rule if its a StochSS Parameter. + + Attributes + ---------- + rule : dict + StochSS Rule + param_ids : list + list of compIDs for the StochSS parameters collection + ''' try: if rule['variable']['compID'] in param_ids: try: - rule['variable']['expression'] = ast.literal_eval(rule['variable']['expression']) + value = ast.literal_eval(rule['variable']['expression']) + rule['variable']['expression'] = value except ValueError: pass except KeyError: @@ -167,6 +246,14 @@ def update_rule_targets(cls, rule, param_ids): @classmethod def update_model_data(cls, data): + ''' + Update the expressions in all Parameters, Reactions, Rules, and Events + + Attributes + ---------- + data : dict + StochSS Model + ''' param_ids = [] if "parameters" in data.keys(): for param in data['parameters']: @@ -193,20 +280,21 @@ async def post(self): model_path = self.get_query_argument(name="path") if model_path.startswith('/'): model_path = model_path.replace('/', '', 1) - log.debug("Path to the model: {0}".format(model_path)) - log.debug("Path with escape char spaces: {0}".format(model_path)) + log.debug("Path to the model: %s", model_path) + log.debug("Path with escape char spaces: %s", model_path) full_path = os.path.join('/home/jovyan', model_path) - log.debug("Full path to the model: {0}".format(full_path)) + log.debug("Full path to the model: %s", full_path) data = self.request.body.decode() - log.debug("Model data to be saved: {0}".format(data)) + log.debug("Model data to be saved: %s", data) if os.path.exists(full_path): with open(full_path, 'w') as file: file.write(data) - log.debug("Saved the model: {0}".format(full_path.split('/').pop().split('.')[0])) + log.debug("Saved the model: %s", full_path.split('/').pop().split('.')[0]) else: self.set_status(404) - error = {"Reason":"Model Not Found", "Message":"Could not find the model file: {0}".format(model_path)} - log.error("Exception information: {0}".format(error)) + error = {"Reason":"Model Not Found", + "Message":"Could not find the model file: {0}".format(model_path)} + log.error("Exception information: %s", error) self.write(error) self.finish() @@ -229,27 +317,29 @@ async def get(self): run_cmd = self.get_query_argument(name="cmd") outfile = self.get_query_argument(name="outfile") model_path = self.get_query_argument(name="path") - log.debug("Run command sent to the script: {0}".format(run_cmd)) - log.debug("Path to the model: {0}".format(model_path)) + log.debug("Run command sent to the script: %s", run_cmd) + log.debug("Path to the model: %s", model_path) self.set_header('Content-Type', 'application/json') # Create temporary results file it doesn't already exist if outfile == 'none': outfile = str(uuid.uuid4()).replace("-", "_") - log.debug("Temporary outfile: {0}".format(outfile)) - exec_cmd = ['/stochss/stochss/handlers/util/run_model.py', '{0}'.format(model_path), '{}.tmp'.format(outfile)] # Script commands for read run_cmd + log.debug("Temporary outfile: %s", outfile) + # Script commands for read run_cmd + exec_cmd = ['/stochss/stochss/handlers/util/run_model.py', + '{0}'.format(model_path), '{}.tmp'.format(outfile)] exec_cmd.append(''.join(['--', run_cmd])) - log.debug("Exec command sent to the subprocess: {0}".format(exec_cmd)) + log.debug("Exec command sent to the subprocess: %s", exec_cmd) resp = {"Running":False, "Outfile":outfile, "Results":""} if run_cmd == "start": pipe = subprocess.Popen(exec_cmd) resp['Running'] = True - log.debug("Response to the start command: {0}".format(resp)) + log.debug("Response to the start command: %s", resp) self.write(resp) else: pipe = subprocess.Popen(exec_cmd, stdout=subprocess.PIPE, text=True) results, error = pipe.communicate() - log.debug("Results for the model preview: {0}".format(results)) - log.error("Errors thrown by the subprocess: {0}".format(error)) + log.debug("Results for the model preview: %s", results) + log.error("Errors thrown by the subprocess: %s", error) # Send data back to client if results: resp['Results'] = json.loads(results) @@ -257,7 +347,7 @@ async def get(self): self.set_status(406) else: resp['Running'] = True - log.debug("Response to the read command: {0}".format(resp)) + log.debug("Response to the read command: %s", resp) self.write(resp) self.finish() From 68fb9149218491e6ce81f9caf8b379a04e6900f5 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Wed, 28 Oct 2020 10:35:00 -0400 Subject: [PATCH 06/13] linted the pages.py file. --- stochss/handlers/pages.py | 57 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/stochss/handlers/pages.py b/stochss/handlers/pages.py index f5df99782d..b7b185bbb6 100644 --- a/stochss/handlers/pages.py +++ b/stochss/handlers/pages.py @@ -37,6 +37,9 @@ def get_template_path(self): @classmethod def get_server_path(cls): + ''' + Get the stochss server path + ''' try: server_path = os.environ['JUPYTERHUB_SERVICE_PREFIX'] except KeyError: @@ -46,54 +49,108 @@ def get_server_path(cls): class HomeHandler(PageHandler): + ''' + Handler for rendering the stochss hub home page + ''' @web.authenticated async def get(self): + ''' + Render the stochss hub home page + ''' self.render("stochss-home.html", server_path=self.get_server_path()) class UserHomeHandler(PageHandler): + ''' + Handler for rendering the stochss users home page + ''' @web.authenticated async def get(self): + ''' + Render the stochss users home page + ''' self.render("stochss-user-home.html", server_path=self.get_server_path()) class QuickstartHandler(PageHandler): + ''' + Handler for rendering the stochss tutorials page + ''' @web.authenticated async def get(self): + ''' + Render the stochss tutorials page + ''' self.render("stochss-quickstart.html", server_path=self.get_server_path()) class ModelBrowserHandler(PageHandler): + ''' + Handler for rendering the stochss file browser page + ''' @web.authenticated async def get(self): + ''' + Render the stochss file browser page + ''' self.render("stochss-file-browser.html", server_path=self.get_server_path()) class ModelEditorHandler(PageHandler): + ''' + Handler for rendering the stochss model editor page + ''' @web.authenticated async def get(self): + ''' + Render the stochss model editor page + ''' self.render("stochss-model-editor.html", server_path=self.get_server_path()) class WorkflowSelectionHandler(PageHandler): + ''' + Handler for rendering the stochss workflow selection page + ''' @web.authenticated async def get(self): + ''' + Render the stochss workflow selection page + ''' self.render("stochss-workflow-selection.html", server_path=self.get_server_path()) class WorkflowEditorHandler(PageHandler): + ''' + Handler for rendering the stochss workflow manager page + ''' @web.authenticated async def get(self): + ''' + Render the stochss workflow manager page + ''' self.render("stochss-workflow-manager.html", server_path=self.get_server_path()) class ProjectBrowserHandler(PageHandler): + ''' + Handler for rendering the stochss project browser page + ''' @web.authenticated async def get(self): + ''' + Render the stochss project browser page + ''' self.render("stochss-project-browser.html", server_path=self.get_server_path()) class ProjectManagerHandler(PageHandler): + ''' + Handler for rendering the stochss project manager page + ''' @web.authenticated async def get(self): + ''' + Render the stochss project manager page + ''' self.render("stochss-project-manager.html", server_path=self.get_server_path()) From 670d27e3a40a64b851f6c387613b676e026608eb Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Wed, 28 Oct 2020 10:48:10 -0400 Subject: [PATCH 07/13] linted the project.py file. --- stochss/handlers/project.py | 128 ++++++++++++++++++++++++++++++++---- 1 file changed, 115 insertions(+), 13 deletions(-) diff --git a/stochss/handlers/project.py b/stochss/handlers/project.py index 0ab84cc11b..57834b819b 100644 --- a/stochss/handlers/project.py +++ b/stochss/handlers/project.py @@ -135,37 +135,139 @@ def get(self): @classmethod - def update_model_data(cls, data): - param_ids = [] - for param in data['parameters']: + def update_parameter(cls, param, param_ids): + ''' + Update the expression of a StochSS Parameter + + Attributes + ---------- + param : dict + StochSS Parameter + param_ids : list + list of compIDs for the StochSS parameters collection + ''' + try: param_ids.append(param['compID']) if isinstance(param['expression'], str): try: param['expression'] = ast.literal_eval(param['expression']) except ValueError: pass - for reaction in data['reactions']: + except KeyError: + pass + + + @classmethod + def update_reaction_rate(cls, reaction): + ''' + Update the expression of a StochSS Reactions rate parameter + + Attributes + ---------- + reaction : dict + StochSS Reaction + ''' + try: if reaction['rate'].keys() and isinstance(reaction['rate']['expression'], str): try: value = ast.literal_eval(reaction['rate']['expression']) reaction['rate']['expression'] = value except ValueError: pass - for event in data['eventsCollection']: - for assignment in event['eventAssignments']: - if assignment['variable']['compID'] in param_ids: - try: - value = ast.literal_eval(assignment['variable']['expression']) - assignment['variable']['expression'] = value - except ValueError: - pass - for rule in data['rules']: + except KeyError: + pass + + + @classmethod + def update_event_assignment(cls, assignment, param_ids): + ''' + Update the target of a StochSS Event Assignment in an event + if its a StochSS Parameter. + + Attributes + ---------- + assignment : dict + StochSS Event Assignment + param_ids : list + list of compIDs for the StochSS parameters collection + ''' + try: + if assignment['variable']['compID'] in param_ids: + try: + value = ast.literal_eval(assignment['variable']['expression']) + assignment['variable']['expression'] = value + except ValueError: + pass + except KeyError: + pass + + + @classmethod + def update_event_targets(cls, event, param_ids): + ''' + Update each StochSS Event Assignment in an event. + + Attributes + ---------- + event : dict + StochSS Event + param_ids : list + list of compIDs for the StochSS parameters collection + ''' + try: + if "eventAssignments" in event.keys(): + for assignment in event['eventAssignments']: + cls.update_event_assignment(assignment, param_ids) + except KeyError: + pass + + + @classmethod + def update_rule_targets(cls, rule, param_ids): + ''' + Update the target of a StochSS Rule if its a StochSS Parameter. + + Attributes + ---------- + rule : dict + StochSS Rule + param_ids : list + list of compIDs for the StochSS parameters collection + ''' + try: if rule['variable']['compID'] in param_ids: try: value = ast.literal_eval(rule['variable']['expression']) rule['variable']['expression'] = value except ValueError: pass + except KeyError: + pass + + + @classmethod + def update_model_data(cls, data): + ''' + Update the expressions in all Parameters, Reactions, Rules, and Events + + Attributes + ---------- + data : dict + StochSS Model + ''' + param_ids = [] + if "parameters" in data.keys(): + for param in data['parameters']: + cls.update_parameter(param, param_ids) + if "reactions" in data.keys(): + for reaction in data['reactions']: + cls.update_reaction_rate(reaction) + if "eventsCollection" in data.keys(): + for event in data['eventsCollection']: + cls.update_event_assignment(event, param_ids) + if "rules" in data.keys(): + for rule in data['rules']: + cls.update_rule_targets(rule, param_ids) @classmethod From 4f01aa57423d79cbf45fdf0725441ce405af748d Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Wed, 28 Oct 2020 13:15:19 -0400 Subject: [PATCH 08/13] Fixed backwards compatability checks for volume. --- stochss/handlers/models.py | 5 +++-- stochss/handlers/util/generate_notebook_cells.py | 5 +++-- stochss/handlers/util/run_model.py | 6 ++++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/stochss/handlers/models.py b/stochss/handlers/models.py index 2aee09b7b0..4f6572a66e 100644 --- a/stochss/handlers/models.py +++ b/stochss/handlers/models.py @@ -85,9 +85,10 @@ def read_json_file(self, full_path): log.debug("Contents of the json file: %s", data) if full_path.endswith(".mdl"): self.update_model_data(data) - if "volume" not in data.keys(): + if "volume" in data['modelSettings'].keys(): data['volume'] = data['modelSettings']['volume'] - del data['modelSettings']['volume'] + elif "volume" not in data.keys(): + data['volume'] = 1 self.write(data) diff --git a/stochss/handlers/util/generate_notebook_cells.py b/stochss/handlers/util/generate_notebook_cells.py index 969c868662..9a7f24f252 100644 --- a/stochss/handlers/util/generate_notebook_cells.py +++ b/stochss/handlers/util/generate_notebook_cells.py @@ -199,9 +199,10 @@ def generate_model_cell(json_data, name): raise Exception('Spatial not yet implemented.') else: # Non-Spatial - if "volume" not in json_data.keys(): + if 'volume' in json_data['modelSettings'].keys(): json_data['volume'] = json_data['modelSettings']['volume'] - + elif "volume" not in json_data.keys(): + json_data['volume'] = 1 model_cell += 'class {0}(Model):\n'.format(name) model_cell += ' def __init__(self, parameter_values=None):\n' padding = ' ' diff --git a/stochss/handlers/util/run_model.py b/stochss/handlers/util/run_model.py index e8cf8010fd..c9b3c4b88f 100755 --- a/stochss/handlers/util/run_model.py +++ b/stochss/handlers/util/run_model.py @@ -239,10 +239,12 @@ def __init__(self, data, is_ode): name = data['name'] timeStep = (data['modelSettings']['timeStep']) endSim = data['modelSettings']['endSim'] - if "volume" not in data.keys(): + if "volume" in data.keys(): + volume = data['volume'] + elif "volume" in data['modelSettings'].keys(): volume = data['modelSettings']['volume'] else: - volume = data['volume'] + volume = 1 self.species = list(map(lambda s: self.build_specie(s, is_ode), data['species'])) self.parameters = list(map(lambda p: self.build_parameter(p), data['parameters'])) self.reactions = list(map(lambda r: self.build_reaction(r, self.parameters), data['reactions'])) From 952d355ef6b6d7a9b5ac8b25322fe549fc23d04d Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Wed, 28 Oct 2020 15:39:22 -0400 Subject: [PATCH 09/13] Added the model exploration setting model to the workflow settings model. Added the defaults for the model exploration settings to the workflow settings template. --- client/models/model-exploration-settings.js | 46 +++++++++++++++++++ client/models/model-exploration-variable.js | 44 ++++++++++++++++++ client/models/model-exploration-variables.js | 40 ++++++++++++++++ client/models/settings.js | 2 + .../workflowSettingsTemplate.json | 4 ++ 5 files changed, 136 insertions(+) create mode 100644 client/models/model-exploration-settings.js create mode 100644 client/models/model-exploration-variable.js create mode 100644 client/models/model-exploration-variables.js diff --git a/client/models/model-exploration-settings.js b/client/models/model-exploration-settings.js new file mode 100644 index 0000000000..47b212548d --- /dev/null +++ b/client/models/model-exploration-settings.js @@ -0,0 +1,46 @@ +/* +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 . +*/ + +//models +var State = require('ampersand-state'); +//collections +var ModelExplorationVariables = require('./model-exploration-variables'); + +module.exports = State.extend({ + props: { + distributionType: 'string' + }, + collection: { + variables: ModelExplorationVariables + }, + initialize: function(attrs, options) { + State.prototype.initialize.apply(this, arguments); + }, + updateVariables: function (parameters) { + this.variables.forEach(function (variable) { + let parameter = parameters.filter(function (parameter) { + return parameter.compID === variable.paramID + })[0] + if(parameter === undefined) { + this.removeVariable(variable) + }else{ + variable.updateVariable(variable) + } + }) + } +}); diff --git a/client/models/model-exploration-variable.js b/client/models/model-exploration-variable.js new file mode 100644 index 0000000000..befdc440bc --- /dev/null +++ b/client/models/model-exploration-variable.js @@ -0,0 +1,44 @@ +/* +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 . +*/ + +//models +var State = require('ampersand-state'); + +module.exports = State.extend({ + props: { + paramID: 'string', + min: 'number', + max: 'number', + steps: 'number', + level: 'number', + outliers: 'object', + seedSize: 'number' + }, + initialize: function(attrs, options) { + State.prototype.initialize.apply(this, arguments); + }, + updateVariable: function (parameter) { + let value = parameter.expression + if(this.min <= 0) { + this.min = value * 0.5 + } + if(this.max <= 0) { + this.max = value * 1.5 + } + } +}); \ No newline at end of file diff --git a/client/models/model-exploration-variables.js b/client/models/model-exploration-variables.js new file mode 100644 index 0000000000..b53a1417fc --- /dev/null +++ b/client/models/model-exploration-variables.js @@ -0,0 +1,40 @@ +/* +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 . +*/ + +//models +var Variable = require('./model-exploration-variable'); +//collections +var Collection = require('ampersand-collection'); + +module.exports = Collection.extend({ + model: Variable, + addVariable: function (paramID) { + var variable = this.add({ + paramID: paramID, + min: 0, + max: 0, + steps: 11, + level: 1, + outliers: [], + seedSize: 1 + }); + }, + removeVariable: function (variable) { + this.remove(variable); + } +}); \ No newline at end of file diff --git a/client/models/settings.js b/client/models/settings.js index 7b80a1271c..3e934d139e 100644 --- a/client/models/settings.js +++ b/client/models/settings.js @@ -20,12 +20,14 @@ along with this program. If not, see . var State = require('ampersand-state'); var SimulationSettings = require('./simulation-settings'); var ParameterSweepSettings = require('./parameter-sweep-settings'); +var ModelExplorationSettings = require('./model-exploration-settings'); var ResultsSettings = require('./results-settings'); module.exports = State.extend({ children: { simulationSettings: SimulationSettings, parameterSweepSettings: ParameterSweepSettings, + modelExplorationSettings: ModelExplorationSettings, resultsSettings: ResultsSettings }, initialize: function(attrs, options) { diff --git a/stochss_templates/workflowSettingsTemplate.json b/stochss_templates/workflowSettingsTemplate.json index fe88916077..fd33153cc3 100644 --- a/stochss_templates/workflowSettingsTemplate.json +++ b/stochss_templates/workflowSettingsTemplate.json @@ -20,6 +20,10 @@ "parameterTwo": {}, "speciesOfInterest": {} }, + "modelExplorationSettings":{ + "distributionType":"Uniform", + "variables": [] + }, "resultsSettings":{ "mapper":"final", "reducer":"avg", From 58cae1123b8215dd12dd2eb41d98fba68cc75a8f Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Wed, 28 Oct 2020 16:27:30 -0400 Subject: [PATCH 10/13] Added the model exploration class file. Added the model exploration settings to the get setting file of the ensemble simulation and parameter sweep workflow get settings functions. --- client/views/model-exploration-settings.js | 3 +- client/views/workflow-editor.js | 6 +- stochss/handlers/util/model_exploration.py | 80 ++++++++++++++++++++++ stochss/handlers/util/parameter_sweep.py | 1 + stochss/handlers/util/run_model.py | 1 + 5 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 stochss/handlers/util/model_exploration.py diff --git a/client/views/model-exploration-settings.js b/client/views/model-exploration-settings.js index b9fee3b547..8296655686 100644 --- a/client/views/model-exploration-settings.js +++ b/client/views/model-exploration-settings.js @@ -27,7 +27,7 @@ module.exports = View.extend({ required: true, idAttribute: 'cid', options: options, - value: "Uniform" + value: this.model.distributionType }); this.registerRenderSubview(distributionTypeView, "distribution-type") }, @@ -63,6 +63,7 @@ module.exports = View.extend({ selectDistributionType: function (e) { let typeDict = {"Uniform":"uniform-prior", "Factorial":"factorial", "Latin Hypercube":"latin-hypercube"} let type = e.target.selectedOptions.item(0).text; + this.model.distributionType = type; $(this.queryByHook(typeDict[type])).css("display", "block") if(type === "Uniform") { $(this.queryByHook("factorial")).css("display", "none") diff --git a/client/views/workflow-editor.js b/client/views/workflow-editor.js index 0150b02996..1ed2a861f6 100644 --- a/client/views/workflow-editor.js +++ b/client/views/workflow-editor.js @@ -104,7 +104,11 @@ module.exports = View.extend({ if(this.modelExplorationSettings) { this.modelExplorationSettings.remove() } - this.modelExplorationSettings = new ModelExploreSettingsView() + this.modelExplorationSettings = new ModelExploreSettingsView({ + parent: this, + model: this.settings.modelExplorationSettings, + stochssModel: this.model + }); this.registerRenderSubview(this.modelExplorationSettings, 'model-exploration-settings-container') }, renderWorkflowStateButtons: function () { diff --git a/stochss/handlers/util/model_exploration.py b/stochss/handlers/util/model_exploration.py new file mode 100644 index 0000000000..bfdc13dc35 --- /dev/null +++ b/stochss/handlers/util/model_exploration.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 + +''' +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 . +''' + +import os + +from stochss.handlers.util.stochss_errors import StochSSPermissionsError + +class ModelExploration(): + + def __init__(self, wkfl_path, mdl_path, settings=None): + self.wkfl_path = wkfl_path + self.mdl_path = mdl_path + self.settings = self.get_settings() if settings is None else settings + self.mdl_file = mdl_path.split('/').pop() + self.info_path = os.path.join(wkfl_path, 'info.json') + self.log_path = os.path.join(wkfl_path, 'logs.txt') + self.wkfl_mdl_path = os.path.join(wkfl_path, self.mdl_file) + self.res_path = os.path.join(wkfl_path, 'results') + wkfl_name_elements = wkfl_path.split('/').pop().split('.')[0].split('_') + try: + date, time = wkfl_name_elements[-2:] + if date.isdigit() and time.isdigit(): + self.wkfl_timestamp = '_'.join(["",date,time]) + else: + self.wkfl_timestamp = None + except: + self.wkfl_timestamp = None + + + def get_settings(self): + settings_path = os.path.join(self.wkfl_path, "settings.json") + + if os.path.exists(settings_path): + with open(settings_path, "r") as settings_file: + return json.load(settings_file) + + with open("/stochss/stochss_templates/workflowSettingsTemplate.json", "r") as template_file: + settings_template = json.load(template_file) + + if os.path.exists(self.wkfl_mdl_path): + with open(self.wkfl_mdl_path, "r") as mdl_file: + mdl = json.load(mdl_file) + try: + settings = {"simulationSettings":mdl['simulationSettings'], + "parameterSweepSettings":mdl['parameterSweepSettings'], + "modelExplarationSettings":settings_template['modelExplorationSettings'], + "resultsSettings":settings_template['resultsSettings']} + return settings + except: + return settings_template + else: + return settings_template + + + def save(self): + settings_path = os.path.join(self.wkfl_path, "settings.json") + with open(settings_path, "w") as settings_file: + json.dump(self.settings, settings_file) + + + def run(self, gillespy2_model, verbose): + message = "StochSS Model Exploration Jobs are currently not supported" + raise StochSSPermissionsError(message) diff --git a/stochss/handlers/util/parameter_sweep.py b/stochss/handlers/util/parameter_sweep.py index af03e21414..ecbb3f089d 100755 --- a/stochss/handlers/util/parameter_sweep.py +++ b/stochss/handlers/util/parameter_sweep.py @@ -439,6 +439,7 @@ def get_settings(self): try: settings = {"simulationSettings":mdl['simulationSettings'], "parameterSweepSettings":mdl['parameterSweepSettings'], + "modelExplarationSettings":settings_template['modelExplorationSettings'], "resultsSettings":settings_template['resultsSettings']} return settings except: diff --git a/stochss/handlers/util/run_model.py b/stochss/handlers/util/run_model.py index e8cf8010fd..4ff103f0af 100755 --- a/stochss/handlers/util/run_model.py +++ b/stochss/handlers/util/run_model.py @@ -87,6 +87,7 @@ def get_settings(self): try: settings = {"simulationSettings":mdl['simulationSettings'], "parameterSweepSettings":mdl['parameterSweepSettings'], + "modelExplarationSettings":settings_template['modelExplorationSettings'], "resultsSettings":settings_template['resultsSettings']} return settings except: From 64c43553b4d88a9a50eb7670f243cd0b01756694 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Wed, 28 Oct 2020 16:57:31 -0400 Subject: [PATCH 11/13] Fixed json import error and workflows ddictionary for model exploration workflows. --- stochss/handlers/util/model_exploration.py | 1 + stochss/handlers/util/run_workflow.py | 12 +++++------- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/stochss/handlers/util/model_exploration.py b/stochss/handlers/util/model_exploration.py index bfdc13dc35..c345b576dd 100644 --- a/stochss/handlers/util/model_exploration.py +++ b/stochss/handlers/util/model_exploration.py @@ -19,6 +19,7 @@ ''' import os +import json from stochss.handlers.util.stochss_errors import StochSSPermissionsError diff --git a/stochss/handlers/util/run_workflow.py b/stochss/handlers/util/run_workflow.py index 45ca764a1b..f8ebc8c12a 100755 --- a/stochss/handlers/util/run_workflow.py +++ b/stochss/handlers/util/run_workflow.py @@ -31,12 +31,9 @@ from datetime import datetime, timezone, timedelta from gillespy2.core import log -try: - from parameter_sweep import ParameterSweep - from run_model import GillesPy2Workflow, ModelFactory, run_solver -except ModuleNotFoundError: - from .parameter_sweep import ParameterSweep - from .run_model import GillesPy2Workflow, ModelFactory, run_solver +from stochss.handlers.util.parameter_sweep import ParameterSweep +from stochss.handlers.util.run_model import GillesPy2Workflow, ModelFactory, run_solver +from stochss.handlers.util.model_exploration import ModelExploration def save_new_workflow(wkfl, wkfl_type, initialize): @@ -238,7 +235,8 @@ def initialize(mdl_path, wkfl_path, wkfl_type, settings=None, new=False, existin if not workflow_name in os.listdir(path=dir_path): os.mkdir(workflow_path) # make the workflow directory - workflows = {"gillespy":GillesPy2Workflow, "parameterSweep":ParameterSweep} + workflows = {"gillespy":GillesPy2Workflow, "parameterSweep":ParameterSweep, + "modelExploration":ModelExploration} workflow = workflows[wkfl_type](workflow_path, model_path, settings) From ba7ebbcbd826d01f03bca2de64d5e68c69f72466 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Thu, 29 Oct 2020 18:10:59 -0400 Subject: [PATCH 12/13] Refactored the templates for the variables table. Added function to render the sweep target select view. Added function to render the min value input field. Added the variable targets current value to the variable view. Added an event listener to update the variable if the target changes. Added function that disables the add variable button if there are no unique targets or if the number of variables has reached the max limit. --- client/models/model-exploration-settings.js | 2 +- client/models/model-exploration-variable.js | 11 ++- client/models/model-exploration-variables.js | 3 +- .../includes/modelExplorationSettings.pug | 85 +---------------- .../includes/modelExplorationVariable.pug | 27 ++++++ .../includes/modelExplorationVariables.pug | 40 ++++++++ client/views/model-exploration-settings.js | 57 ++++++++--- client/views/model-exploration-variable.js | 94 +++++++++++++++++++ client/views/model-exploration-variables.js | 32 +++++++ 9 files changed, 249 insertions(+), 102 deletions(-) create mode 100644 client/templates/includes/modelExplorationVariable.pug create mode 100644 client/templates/includes/modelExplorationVariables.pug create mode 100644 client/views/model-exploration-variable.js create mode 100644 client/views/model-exploration-variables.js diff --git a/client/models/model-exploration-settings.js b/client/models/model-exploration-settings.js index 47b212548d..7359e194d8 100644 --- a/client/models/model-exploration-settings.js +++ b/client/models/model-exploration-settings.js @@ -25,7 +25,7 @@ module.exports = State.extend({ props: { distributionType: 'string' }, - collection: { + collections: { variables: ModelExplorationVariables }, initialize: function(attrs, options) { diff --git a/client/models/model-exploration-variable.js b/client/models/model-exploration-variable.js index befdc440bc..34d465da79 100644 --- a/client/models/model-exploration-variable.js +++ b/client/models/model-exploration-variable.js @@ -21,23 +21,24 @@ var State = require('ampersand-state'); module.exports = State.extend({ props: { - paramID: 'string', - min: 'number', + paramID: 'number', + min: 'any', max: 'number', steps: 'number', level: 'number', outliers: 'object', - seedSize: 'number' + seedSize: 'number', + hasChangedRanged: 'boolean' }, initialize: function(attrs, options) { State.prototype.initialize.apply(this, arguments); }, updateVariable: function (parameter) { let value = parameter.expression - if(this.min <= 0) { + if(this.min <= 0 || !this.hasChangedRanged) { this.min = value * 0.5 } - if(this.max <= 0) { + if(this.max <= 0 || !this.hasChangedRanged) { this.max = value * 1.5 } } diff --git a/client/models/model-exploration-variables.js b/client/models/model-exploration-variables.js index b53a1417fc..50c7932d6e 100644 --- a/client/models/model-exploration-variables.js +++ b/client/models/model-exploration-variables.js @@ -31,7 +31,8 @@ module.exports = Collection.extend({ steps: 11, level: 1, outliers: [], - seedSize: 1 + seedSize: 1, + hasChangedRange: false }); }, removeVariable: function (variable) { diff --git a/client/templates/includes/modelExplorationSettings.pug b/client/templates/includes/modelExplorationSettings.pug index 2a72410773..fef17738dc 100644 --- a/client/templates/includes/modelExplorationSettings.pug +++ b/client/templates/includes/modelExplorationSettings.pug @@ -31,87 +31,6 @@ div#model-exploration-settings.card.card-body h5 Configure Variable(s) - div(data-hook="uniform-prior") + div(data-hook="me-variables-collection") - table.table - thead - tr - th(scope="col") - div - div.inline Sweep Variable - - th(scope="col") - div - div.inline Current Value - - th(scope="col") - div - div.inline Minimum Value - - th(scope="col") - div - div.inline Maximum Value - - th(scope="col") - div - div.inline Steps - - tbody(data-hook="ufp-variables") - - div(data-hook="factorial" style="display: none") - - table.table - thead - tr - th(scope="col") - div - div.inline Sweep Variable - - th(scope="col") - div - div.inline Current Value - - th(scope="col") - div - div.inline Minimum Value - - th(scope="col") - div - div.inline Maximum Value - - th(scope="col") - div - div.inline Level - - th(scope="col") - div - div.inline Outliers - - tboby(data-hook="ftl-variables") - - div(data-hook="latin-hypercube" style="display: none") - - table.table - thead - tr - th(scope="col") - div - div.inline Sweep Variable - - th(scope="col") - div - div.inline Current Value - - th(scope="col") - div - div.inline Minimum Value - - th(scope="col") - div - div.inline Maximum Value - - th(scope="col") - div - div.inline Seed Size - - tboby(data-hook="lhc-variables") + button.btn.btn-outline-secondary.box-shadow(data-hook="add-me-variable") Add Variable diff --git a/client/templates/includes/modelExplorationVariable.pug b/client/templates/includes/modelExplorationVariable.pug new file mode 100644 index 0000000000..c7ab34c60d --- /dev/null +++ b/client/templates/includes/modelExplorationVariable.pug @@ -0,0 +1,27 @@ +tr + + td: div(data-hook="variable-target") + + td: div(data-hook="target-value")=this.parameter.expression + + td: div(data-hook="variable-min") + + td: div TODO: Max Value + + td + + if(this.distributionType == "Uniform") + div TODO: Steps + + if(this.distributionType == "Factorial") + div + div TODO: Level + div Testing multi-elements + + if(this.distributionType == "Latin Hypercube") + div TODO: Seed Size + + if(this.distributionType == "Factorial") + td: div TODO: Outliers + + td: button.btn.btn-outline-secondary.box-shadow(data-hook="remove") X \ No newline at end of file diff --git a/client/templates/includes/modelExplorationVariables.pug b/client/templates/includes/modelExplorationVariables.pug new file mode 100644 index 0000000000..31f7c0d753 --- /dev/null +++ b/client/templates/includes/modelExplorationVariables.pug @@ -0,0 +1,40 @@ +table.table + thead + tr + th(scope="col") + div + div.inline Sweep Target + + th(scope="col") + div + div.inline Current Value + + th(scope="col") + div + div.inline Minimum Value + + th(scope="col") + div + div.inline Maximum Value + + th(scope="col") + if(this.distributionType == "Uniform") + div + div.inline Steps + + if(this.distributionType == "Factorial") + div + div.inline Level + + if(this.distributionType == "Latin Hypercube") + div + div.inline Seed Size + + if(this.distributionType == "Factorial") + th(scope="col") + div + div.inline Outliers + + th(scope="col"): div Remove + + tbody(data-hook="me-variables") \ No newline at end of file diff --git a/client/views/model-exploration-settings.js b/client/views/model-exploration-settings.js index 8296655686..9bcc55f179 100644 --- a/client/views/model-exploration-settings.js +++ b/client/views/model-exploration-settings.js @@ -1,7 +1,8 @@ let $ = require('jquery'); //views let View = require('ampersand-view'); -var SelectView = require('ampersand-select-view'); +let SelectView = require('ampersand-select-view'); +let VariablesCollectionView = require('./model-exploration-variables'); //templates let template = require('../templates/includes/modelExplorationSettings.pug'); @@ -9,15 +10,24 @@ module.exports = View.extend({ template: template, events: { 'change [data-hook=distribution-type]' : 'selectDistributionType', + 'click [data-hook=add-me-variable]' : 'handleAddVariableClick', + 'click [data-hook=collapse]' : 'changeCollapseButtonText' }, initialize: function (attrs, options) { View.prototype.initialize.apply(this, arguments) + this.stochssModel = attrs.stochssModel + this.model.variables.on("add remove", function () { + let disable = this.model.variables.length >= 2 || this.model.variables.length >= this.stochssModel.parameters.length + $(this.queryByHook("add-me-variable")).prop("disabled", disable) + }, this) }, render: function (attrs, options) { View.prototype.render.apply(this, arguments) this.renderDistributionTypeSelect(); this.renderExplorationTypeSelect(); this.renderFeatureExtractionSelect(); + this.model.updateVariables(this.stochssModel.parameters) + this.renderVariablesCollection() }, renderDistributionTypeSelect: function () { let options = ["Uniform", "Factorial", "Latin Hypercube"] @@ -56,24 +66,47 @@ module.exports = View.extend({ }); this.registerRenderSubview(featureExtractionView, "feature-extraction") }, + renderVariablesCollection: function () { + if(this.variablesView) { + this.variablesView.remove() + } + this.variablesView = new VariablesCollectionView({ + parent: this, + collection: this.model.variables, + distributionType: this.model.distributionType, + stochssModel: this.stochssModel + }); + this.registerRenderSubview(this.variablesView, "me-variables-collection") + }, registerRenderSubview: function (view, hook) { this.registerSubview(view); this.renderSubview(view, this.queryByHook(hook)); }, selectDistributionType: function (e) { - let typeDict = {"Uniform":"uniform-prior", "Factorial":"factorial", "Latin Hypercube":"latin-hypercube"} let type = e.target.selectedOptions.item(0).text; this.model.distributionType = type; - $(this.queryByHook(typeDict[type])).css("display", "block") - if(type === "Uniform") { - $(this.queryByHook("factorial")).css("display", "none") - $(this.queryByHook("latin-hypercube")).css("display", "none") - }else if(type === "Factorial") { - $(this.queryByHook("uniform-prior")).css("display", "none") - $(this.queryByHook("latin-hypercube")).css("display", "none") - }else{ - $(this.queryByHook("uniform-prior")).css("display", "none") - $(this.queryByHook("factorial")).css("display", "none") + this.renderVariablesCollection() + }, + getParameterID: function () { + let variableTargets = this.model.variables.map(function (variable) { return variable.paramID }); + let target = this.stochssModel.parameters.filter(function (param) { + return !variableTargets.includes(param.compID) + })[0].compID + return target + }, + handleAddVariableClick: function () { + let target = this.getParameterID() + this.model.variables.addVariable(target) + }, + changeCollapseButtonText: function (e) { + if(e.target.dataset && e.target.dataset.toggle === "collapse") { + let source = e.target.dataset.hook + let collapseContainer = $(this.queryByHook(source).dataset.target) + if(!collapseContainer.length || !collapseContainer.attr("class").includes("collapsing")) { + let collapseBtn = $(this.queryByHook(source)) + let text = collapseBtn.text(); + text === '+' ? collapseBtn.text('-') : collapseBtn.text('+'); + } } } }); \ No newline at end of file diff --git a/client/views/model-exploration-variable.js b/client/views/model-exploration-variable.js new file mode 100644 index 0000000000..06dcd13d10 --- /dev/null +++ b/client/views/model-exploration-variable.js @@ -0,0 +1,94 @@ +let $ = require('jquery'); +let tests = require('./tests'); +//views +let View = require('ampersand-view'); +let InputView = require('./input'); +let SelectView = require('ampersand-select-view'); +//templates +let template = require('../templates/includes/modelExplorationVariable.pug'); + +module.exports = View.extend({ + template: template, + events: { + 'change [data-hook=variable-target]' : 'setSelectedTarget', + }, + initialize: function (attrs, options) { + View.prototype.initialize.apply(this, arguments); + let self = this + this.distributionType = this.parent.distributionType + this.parameters = this.parent.stochssModel.parameters + this.parameter = this.parameters.filter(function (param) { + return param.compID === self.model.paramID + })[0] + this.model.updateVariable(this.parameter) + this.model.collection.on('add update-target remove', this.registerTargetSelectView, this) + }, + render: function (attrs, options) { + View.prototype.render.apply(this, arguments); + this.registerTargetSelectView() + this.renderMinValInputView() + }, + update: function (e) {}, + updateValid: function (e) {}, + registerTargetSelectView: function () { + if(this.targetSelectView) { + this.targetSelectView.remove() + } + let options = this.getAvailableParameters(); + this.targetSelectView = new SelectView({ + label: '', + name: 'variable-target', + required: true, + idAttribute: 'cid', + options: options, + value: this.parameter.name + }); + this.registerRenderSubview(this.targetSelectView, "variable-target") + }, + renderMinValInputView: function () { + if(this.minValInputView) { + this.minValInputView.remove() + } + this.minValInputView = new InputView({ + parent: this, + required: true, + name: 'min-value', + label: '', + tests: tests.valueTests, + modelKey: 'min', + valueType: 'number', + value: this.model.min, + }); + this.registerRenderSubview(this.minValInputView, "variable-min") + }, + registerRenderSubview: function (view, hook) { + this.registerSubview(view); + this.renderSubview(view, this.queryByHook(hook)); + }, + getAvailableParameters: function () { + let variableTargets = this.model.collection.map(function (variable) { return variable.paramID}) + let availableParameters = this.parameters.filter(function (param) { + return !variableTargets.includes(param.compID) + }).map(function (param) { return param.name }); + if(!availableParameters.includes(this.parameter.name)) { + availableParameters.push(this.parameter.name) + } + return availableParameters + }, + setSelectedTarget: function (e) { + let targetName = e.target.selectedOptions.item(0).text; + this.parameter = this.parameters.filter(function (param) { + return param.name === targetName + })[0] + let targetID = this.parameter.compID + this.model.paramID = targetID + this.updateVariableProps() + this.model.collection.trigger('update-target') + }, + updateVariableProps: function () { + $(this.queryByHook("target-value")).text(this.parameter.expression) + this.model.hasChangedRange = false + this.model.updateVariable(this.parameter) + this.renderMinValInputView() + } +}); \ No newline at end of file diff --git a/client/views/model-exploration-variables.js b/client/views/model-exploration-variables.js new file mode 100644 index 0000000000..dedf7e2be2 --- /dev/null +++ b/client/views/model-exploration-variables.js @@ -0,0 +1,32 @@ +let $ = require('jquery'); +//views +let View = require('ampersand-view'); +let VariableView = require('./model-exploration-variable'); +//templates +let template = require('../templates/includes/modelExplorationVariables.pug'); + +module.exports = View.extend({ + template: template, + events: {}, + initialize: function (attrs, options) { + View.prototype.initialize.apply(this, arguments); + this.distributionType = attrs.distributionType + this.stochssModel = attrs.stochssModel + }, + render: function (attrs, options) { + View.prototype.render.apply(this, arguments); + this.renderVariablesCollection() + }, + update: function (e) {}, + updateValid: function (e) {}, + renderVariablesCollection: function () { + if(this.variablesCollection) { + this.variablesCollection.remove() + } + this.variablesCollection = this.renderCollection( + this.collection, + VariableView, + this.queryByHook("me-variables") + ) + } +}); \ No newline at end of file From 1f54c905929402ddbcb8b8269ab9706ec13b2530 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Mon, 2 Nov 2020 11:28:22 -0500 Subject: [PATCH 13/13] Added render functions for the following input fields: Max Value, Steps, Level, Outliers, Seed Size. Added change events to the min and max input fields that update the models hasChangedRange prop when the user changes either the min or max value. Added function to remove a variable when the remove button is clicked. Added check to the renderTargetSelectView function that ensures the model is part of the collection before trying to re-render the select view. Changed the prop types of min, max, steps, level, and seed size to 'any' to allow the input views to work correctly. Changed the prop type of outliers to string. --- client/models/model-exploration-variable.js | 10 +- client/models/model-exploration-variables.js | 2 +- .../includes/modelExplorationVariable.pug | 12 +- client/views/model-exploration-variable.js | 139 ++++++++++++++++-- 4 files changed, 137 insertions(+), 26 deletions(-) diff --git a/client/models/model-exploration-variable.js b/client/models/model-exploration-variable.js index 34d465da79..756d39ef90 100644 --- a/client/models/model-exploration-variable.js +++ b/client/models/model-exploration-variable.js @@ -23,11 +23,11 @@ module.exports = State.extend({ props: { paramID: 'number', min: 'any', - max: 'number', - steps: 'number', - level: 'number', - outliers: 'object', - seedSize: 'number', + max: 'any', + steps: 'any', + level: 'any', + outliers: 'string', + seedSize: 'any', hasChangedRanged: 'boolean' }, initialize: function(attrs, options) { diff --git a/client/models/model-exploration-variables.js b/client/models/model-exploration-variables.js index 50c7932d6e..9388a88654 100644 --- a/client/models/model-exploration-variables.js +++ b/client/models/model-exploration-variables.js @@ -30,7 +30,7 @@ module.exports = Collection.extend({ max: 0, steps: 11, level: 1, - outliers: [], + outliers: "", seedSize: 1, hasChangedRange: false }); diff --git a/client/templates/includes/modelExplorationVariable.pug b/client/templates/includes/modelExplorationVariable.pug index c7ab34c60d..99a071181c 100644 --- a/client/templates/includes/modelExplorationVariable.pug +++ b/client/templates/includes/modelExplorationVariable.pug @@ -6,22 +6,20 @@ tr td: div(data-hook="variable-min") - td: div TODO: Max Value + td: div(data-hook="variable-max") td if(this.distributionType == "Uniform") - div TODO: Steps + div(data-hook="variable-steps") if(this.distributionType == "Factorial") - div - div TODO: Level - div Testing multi-elements + div(data-hook="variable-level") if(this.distributionType == "Latin Hypercube") - div TODO: Seed Size + div(data-hook="variable-seed-size") if(this.distributionType == "Factorial") - td: div TODO: Outliers + td: div(data-hook="variable-outliers") td: button.btn.btn-outline-secondary.box-shadow(data-hook="remove") X \ No newline at end of file diff --git a/client/views/model-exploration-variable.js b/client/views/model-exploration-variable.js index 06dcd13d10..51423d06e0 100644 --- a/client/views/model-exploration-variable.js +++ b/client/views/model-exploration-variable.js @@ -11,6 +11,9 @@ module.exports = View.extend({ template: template, events: { 'change [data-hook=variable-target]' : 'setSelectedTarget', + 'change [data-hook=variable-min]' : 'setHasChangedRange', + 'change [data-hook=variable-max]' : 'setHasChangedRange', + 'click [data-hook=remove]' : 'removeVariable' }, initialize: function (attrs, options) { View.prototype.initialize.apply(this, arguments); @@ -27,23 +30,34 @@ module.exports = View.extend({ View.prototype.render.apply(this, arguments); this.registerTargetSelectView() this.renderMinValInputView() + this.renderMaxValInputView() + if(this.parent.parent.model.distributionType === "Uniform"){ + this.renderStepsInputView() + }else if(this.parent.parent.model.distributionType === "Factorial") { + this.renderLevelInputView() + this.renderOutliersInputView() + }else if(this.parent.parent.model.distributionType === "Latin Hypercube") { + this.renderSeedSizeInputView() + } }, update: function (e) {}, updateValid: function (e) {}, - registerTargetSelectView: function () { - if(this.targetSelectView) { - this.targetSelectView.remove() + registerTargetSelectView: function (e) { + if(this.model.collection) { + if(this.targetSelectView) { + this.targetSelectView.remove() + } + let options = this.getAvailableParameters(); + this.targetSelectView = new SelectView({ + label: '', + name: 'variable-target', + required: true, + idAttribute: 'cid', + options: options, + value: this.parameter.name + }); + this.registerRenderSubview(this.targetSelectView, "variable-target") } - let options = this.getAvailableParameters(); - this.targetSelectView = new SelectView({ - label: '', - name: 'variable-target', - required: true, - idAttribute: 'cid', - options: options, - value: this.parameter.name - }); - this.registerRenderSubview(this.targetSelectView, "variable-target") }, renderMinValInputView: function () { if(this.minValInputView) { @@ -61,6 +75,87 @@ module.exports = View.extend({ }); this.registerRenderSubview(this.minValInputView, "variable-min") }, + renderMaxValInputView: function () { + if(this.maxValInputView) { + this.maxValInputView.remove() + } + this.maxValInputView = new InputView({ + parent: this, + required: true, + name: 'max-value', + label: '', + tests: tests.valueTests, + modelKey: 'max', + valueType: 'number', + value: this.model.max, + }); + this.registerRenderSubview(this.maxValInputView, "variable-max") + }, + renderStepsInputView: function () { + if(this.stepsInputView) { + this.stepsInputView.remove() + } + this.stepsInputView = new InputView({ + parent: this, + required: true, + name: 'steps', + label: '', + tests: tests.valueTests, + modelKey: 'steps', + valueType: 'number', + value: this.model.steps, + }); + this.registerRenderSubview(this.stepsInputView, "variable-steps") + }, + renderLevelInputView: function () { + if(this.levelInputView) { + this.levelInputView.remove() + } + this.levelInputView = new InputView({ + parent: this, + required: true, + name: 'level', + label: '', + tests: tests.valueTests, + modelKey: 'level', + valueType: 'number', + value: this.model.level, + }); + this.registerRenderSubview(this.levelInputView, "variable-level") + }, + renderOutliersInputView: function () { + if(this.outliersInputView) { + this.outliersInputView.remove() + } + this.outliersInputView = new InputView({ + parent: this, + required: false, + name: 'outliers', + label: '', + tests: [], + modelKey: 'outliers', + valueType: 'string', + value: this.model.outliers, + placeholder: "--Enter Outliers--" + }); + this.registerRenderSubview(this.outliersInputView, "variable-outliers") + }, + renderSeedSizeInputView: function () { + if(this.seedSizeInputView) { + this.seedSizeInputView.remove() + } + this.seedSizeInputView = new InputView({ + parent: this, + required: true, + name: 'seed-size', + label: '', + tests: tests.valueTests, + modelKey: 'seedSize', + valueType: 'number', + value: this.model.seedSize, + }); + this.registerRenderSubview(this.seedSizeInputView, "variable-seed-size") + }, registerRenderSubview: function (view, hook) { this.registerSubview(view); this.renderSubview(view, this.queryByHook(hook)); @@ -85,10 +180,28 @@ module.exports = View.extend({ this.updateVariableProps() this.model.collection.trigger('update-target') }, + setHasChangedRange: function () { + this.model.hasChangedRange = true + }, updateVariableProps: function () { $(this.queryByHook("target-value")).text(this.parameter.expression) this.model.hasChangedRange = false this.model.updateVariable(this.parameter) this.renderMinValInputView() + this.renderMaxValInputView() + if(this.parent.parent.model.distributionType === "Uniform"){ + this.renderStepsInputView() + }else if(this.parent.parent.model.distributionType === "Factorial") { + this.renderLevelInputView() + this.renderOutliersInputView() + }else if(this.parent.parent.model.distributionType === "Latin Hypercube") { + this.renderSeedSizeInputView() + } + }, + removeVariable: function () { + // console.log("Removing variable targeting " + this.parameter.name) + this.remove() + this.model.collection.removeVariable(this.model) + this.remove() } }); \ No newline at end of file