diff --git a/.gitignore b/.gitignore
index 07081b1b5b..b6ec4a4606 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,6 +14,7 @@ stochss/dist/stochss-loading-page.html
stochss/dist/stochss-quick-start.html
stochss/dist/stochss-user-home.html
stochss/dist/stochss-example-library.html
+stochss/dist/stochss-user-settings.html
jupyterhub/templates/page.html
jupyterhub/templates/stochss-home.html
jupyterhub/templates/stochss-job-presentation.html
diff --git a/.pylintrc b/.pylintrc
index 668c9fe2bd..6749525c85 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -329,7 +329,7 @@ indent-after-paren=4
indent-string=' '
# Maximum number of characters on a single line.
-max-line-length=100
+max-line-length=120
# Maximum number of lines in a module.
max-module-lines=1000
@@ -555,31 +555,31 @@ valid-metaclass-classmethod-first-arg=cls
[DESIGN]
# Maximum number of arguments for function / method.
-max-args=5
+max-args=20
# Maximum number of attributes for a class (see R0902).
-max-attributes=7
+max-attributes=25
# Maximum number of boolean expressions in an if statement (see R0916).
max-bool-expr=5
# Maximum number of branch for function / method body.
-max-branches=12
+max-branches=20
# Maximum number of locals for function / method body.
-max-locals=15
+max-locals=25
# Maximum number of parents for a class (see R0901).
max-parents=7
# Maximum number of public methods for a class (see R0904).
-max-public-methods=20
+max-public-methods=30
# Maximum number of return / yield for function / method body.
max-returns=6
# Maximum number of statements in function / method body.
-max-statements=50
+max-statements=60
# Minimum number of public methods for a class (see R0903).
min-public-methods=2
diff --git a/COPYRIGHT b/COPYRIGHT
index 1472719f26..9d0ffc3188 100644
--- a/COPYRIGHT
+++ b/COPYRIGHT
@@ -1,5 +1,5 @@
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/Dockerfile b/Dockerfile
index 27d57c6e4d..c511ae5629 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -3,6 +3,7 @@ FROM jupyter/minimal-notebook:612aa5710bf9
ARG STOCHSS_PIP_EDITABLE
ARG JUPYTER_CONFIG_DIR
+
USER root
WORKDIR /stochss
@@ -17,6 +18,10 @@ COPY --chown=jovyan:users requirements.txt .
RUN python -m pip install --no-cache-dir -r requirements.txt
+COPY --chown=jovyan:users package.json /stochss
+
+RUN npm install
+
COPY --chown=jovyan:users public_models/ /home/jovyan/Examples
COPY --chown=jovyan:users . /stochss
@@ -31,30 +36,10 @@ COPY --chown=jovyan:users custom.css $JUPYTER_CONFIG_DIR/custom/custom.css
COPY --chown=jovyan:users jupyter_notebook_config.py $JUPYTER_CONFIG_DIR/jupyter_notebook_config.py
-USER root
-
-#RUN wget -q https://julialang-s3.julialang.org/bin/linux/x64/1.4/julia-1.4.2-linux-x86_64.tar.gz
-#RUN tar -xvzf julia-1.4.2-linux-x86_64.tar.gz
-#RUN mv julia-1.4.2 /usr/local/
-#RUN chown -R jovyan:users /usr/local/julia-1.4.2/
-
-USER jovyan
-
-#ENV PATH="/usr/local/julia-1.4.2/bin:${PATH}"
-
-#RUN julia -e 'using Pkg; Pkg.add("IJulia")'
-#RUN julia -e 'using Pkg; Pkg.add("Plots")'
-#RUN julia -e 'using Pkg; Pkg.add(PackageSpec(url="https://github.com/stochss/gillespy2lia", rev="main"))'
-
-RUN npm install
-
-RUN npm run webpack
-
-#RUN julia -e 'using Pkg; Pkg.add("IJulia")'
-#RUN julia -e 'using Pkg; Pkg.add(PackageSpec(url="https://github.com/stochss/gillespy2lia", rev="main"))'
-
RUN pip install --no-cache-dir -e .
RUN rm -r /home/jovyan/work
+RUN npm run webpack
+
WORKDIR /home/jovyan
diff --git a/__version__.py b/__version__.py
index afa635caba..d3476703d8 100644
--- a/__version__.py
+++ b/__version__.py
@@ -5,7 +5,7 @@
# @website https://github.com/stochss/stochss
# =============================================================================
-__version__ = '2.4.15'
+__version__ = '2.5.0'
__title__ = 'StochSS'
__description__ = 'StochSS is an integrated development environment (IDE) \
for simulation of biochemical networks.'
@@ -14,4 +14,4 @@
__author__ = 'StochSS Team'
__email__ = 'bdrawert@unca.edu'
__license__ = 'GPL License'
-__copyright__ = 'Copyright (c) 2008-2022'
+__copyright__ = 'Copyright (c) 2008-2023'
diff --git a/client/app.js b/client/app.js
index 65275f7076..f734e9b2d7 100644
--- a/client/app.js
+++ b/client/app.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -25,61 +25,49 @@ let modals = require('./modals');
let routePrefix = 'stochss';
let apiPrefix = path.join(routePrefix, 'api');
-let getBasePath = () => {
- try {
- let base = document.querySelector('base')
- let href = base.getAttribute('href')
- return href
- } catch (error) {
- return '/'
- }
-};
-let getApiPath = () => path.join(getBasePath(), apiPrefix);
-var BrowserDetect = {
+let BrowserDetect = {
+ dataBrowser: [
+ {subString: "Edge", identity: "MS Edge"},
+ {subString: "MSIE", identity: "Explorer"},
+ {subString: "Trident", identity: "Explorer"},
+ {subString: "Firefox", identity: "Firefox"},
+ {subString: "Opera", identity: "Opera"},
+ {subString: "OPR", identity: "Opera"},
+ {subString: "Chrome", identity: "Chrome"},
+ {subString: "Safari", identity: "Safari"}
+ ],
init: function () {
this.browser = this.searchString(this.dataBrowser) || "Other";
this.version = this.searchVersion(navigator.userAgent) || this.searchVersion(navigator.appVersion) || "Unknown";
},
searchString: function (data) {
- var dataString = navigator.userAgent;
+ let dataString = navigator.userAgent;
for (var i = 0; i < data.length; i++) {
this.versionSearchString = data[i].subString;
-
if (dataString.indexOf(data[i].subString) !== -1) {
return data[i].identity;
}
}
},
searchVersion: function (dataString) {
- var index = dataString.indexOf(this.versionSearchString);
- if (index === -1) {
- return;
- }
+ let index = dataString.indexOf(this.versionSearchString);
+ if (index === -1) { return; }
- var rv = dataString.indexOf("rv:");
+ let rv = dataString.indexOf("rv:");
if (this.versionSearchString === "Trident" && rv !== -1) {
return parseFloat(dataString.substring(rv + 3));
- }else if(this.versionSearchString === "Safari"){
+ }
+
+ if(this.versionSearchString === "Safari"){
let versionSearchString = "Version";
- let versionIndex = dataString.indexOf("Version")
+ let versionIndex = dataString.indexOf("Version");
return parseFloat(dataString.substring(versionIndex + versionSearchString.length + 1, index - 1));
- }else{
- return parseFloat(dataString.substring(index + this.versionSearchString.length + 1));
}
- },
- dataBrowser: [
- {subString: "Edge", identity: "MS Edge"},
- {subString: "MSIE", identity: "Explorer"},
- {subString: "Trident", identity: "Explorer"},
- {subString: "Firefox", identity: "Firefox"},
- {subString: "Opera", identity: "Opera"},
- {subString: "OPR", identity: "Opera"},
- {subString: "Chrome", identity: "Chrome"},
- {subString: "Safari", identity: "Safari"}
- ]
-};
+ return parseFloat(dataString.substring(index + this.versionSearchString.length + 1));
+ }
+}
let changeCollapseButtonText = (view, e) => {
let source = e.target.dataset.hook;
@@ -90,13 +78,40 @@ let changeCollapseButtonText = (view, e) => {
let text = collapseBtn.text();
text === '+' ? collapseBtn.text('-') : collapseBtn.text('+');
}
-};
-
-let registerRenderSubview = (parent, view, hook) => {
- parent.registerSubview(view);
- parent.renderSubview(view, parent.queryByHook(hook));
-};
-
+}
+let copyToClipboard = (text, success, error) => {
+ fullURL = window.location.protocol + '//' + window.location.hostname + text;
+ if (window.clipboardData && window.clipboardData.setData) {
+ // Internet Explorer-specific code path to prevent textarea being shown while dialog is visible.
+ return window.clipboardData.setData("Text", fullURL);
+ }
+ else {
+ navigator.clipboard.writeText(fullURL).then(success, error);
+ }
+}
+let documentSetup = () => {
+ tooltipSetup();
+ $(document).on('shown.bs.modal', (e) => {
+ $('[autofocus]', e.target).focus();
+ });
+ $(document).on('hide.bs.modal', '.modal', (e) => {
+ e.target.remove();
+ });
+}
+let getApiPath = () => path.join(getBasePath(), apiPrefix);
+let getBasePath = () => {
+ try {
+ let base = document.querySelector('base')
+ let href = base.getAttribute('href')
+ return href
+ } catch (error) {
+ return '/'
+ }
+}
+let getBrowser = () => {
+ BrowserDetect.init();
+ return {"name":BrowserDetect.browser,"version":BrowserDetect.version};
+}
let getXHR = (endpoint, {
always = function (err, response, body) {}, success = function (err, response, body) {},
error = function (err, response, body) {}}={}) => {
@@ -117,140 +132,7 @@ let getXHR = (endpoint, {
let body = {response: response, err: exception}
error(exception, response, body);
}
-};
-
-let postXHR = (endpoint, data, {
- always = function (err, response, body) {}, success = function (err, response, body) {},
- error = function (err, response, body) {}}={}, isJSON) => {
- try {
- xhr({uri: endpoint, json: isJSON !== undefined ? isJSON : true, method: "post", body: data}, function (err, response, body) {
- if(response.statusCode < 400) {
- success(err, response, body);
- }else if(response.statusCode < 500) {
- error(err, response, body);
- }else{
- console.log("Critical Error Detected");
- }
- always(err, response, body);
- });
- }catch(exception){
- console.log(exception);
- let response = {Reason: "Network Error", Message: exception};
- let body = {response: response, err: exception}
- error(exception, response, body);
- }
-};
-
-let getBrowser = () => {
- BrowserDetect.init();
- return {"name":BrowserDetect.browser,"version":BrowserDetect.version};
-}
-
-let validateName = (input, {rename=false, saveAs=true}={}) => {
- var error = "";
- if(input.endsWith('/')) {
- error = 'forward';
- }
- var invalidChars = "`~!@#$%^&*=+[{]}\"|:;'<,>?\\";
- if(rename || !saveAs) {
- invalidChars += "/";
- }
- for(var i = 0; i < input.length; i++) {
- if(invalidChars.includes(input.charAt(i))) {
- error = error === "" || error === "special" ? "special" : "both";
- }
- }
- return error;
-}
-
-let newWorkflow = (parent, mdlPath, isSpatial, type) => {
- if(document.querySelector('#newWorkflowModal')) {
- document.querySelector('#newWorkflowModal').remove()
- }
- let typeCodes = {
- "Ensemble Simulation": "_ES",
- "Spatial Ensemble Simulation": "_SES",
- "Parameter Sweep": "_PS"
- }
- let self = parent;
- let ext = isSpatial ? /.smdl/g : /.mdl/g
- let typeCode = typeCodes[type];
- let name = mdlPath.split('/').pop().replace(ext, typeCode)
- let modal = $(modals.createWorkflowHtml(name, type)).modal();
- let okBtn = document.querySelector('#newWorkflowModal .ok-model-btn');
- let input = document.querySelector('#newWorkflowModal #workflowNameInput');
- okBtn.disabled = false;
- input.addEventListener("keyup", (event) => {
- if(event.keyCode === 13){
- event.preventDefault();
- okBtn.click();
- }
- });
- input.addEventListener("input", (e) => {
- let endErrMsg = document.querySelector('#newWorkflowModal #workflowNameInputEndCharError')
- let charErrMsg = document.querySelector('#newWorkflowModal #workflowNameInputSpecCharError')
- let error = validateName(input.value)
- okBtn.disabled = error !== "" || input.value.trim() === ""
- charErrMsg.style.display = error === "both" || error === "special" ? "block" : "none"
- endErrMsg.style.display = error === "both" || error === "forward" ? "block" : "none"
- });
- okBtn.addEventListener('click', (e) => {
- modal.modal("hide");
- let wkflFile = `${input.value.trim()}.wkfl`;
- if(mdlPath.includes(".proj") && !mdlPath.includes(".wkgp")){
- var wkflPath = path.join(path.dirname(mdlPath), "WorkflowGroup1.wkgp", wkflFile);
- }else{
- var wkflPath = path.join(path.dirname(mdlPath), wkflFile);
- }
- let queryString = `?path=${wkflPath}&model=${mdlPath}&type=${type}`;
- let endpoint = path.join(getApiPath(), "workflow/new") + queryString;
- getXHR(endpoint, {
- success: (err, response, body) => {
- window.location.href = `${path.join(getBasePath(), "stochss/workflow/edit")}?path=${body.path}`;
- }
- });
- });
-}
-
-tooltipSetup = () => {
- $(function () {
- $('[data-toggle="tooltip"]').tooltip();
- $('[data-toggle="tooltip"]').on('click ', function () {
- $('[data-toggle="tooltip"]').tooltip("hide");
- });
- });
-}
-
-documentSetup = () => {
- tooltipSetup();
- $(document).on('shown.bs.modal', function (e) {
- $('[autofocus]', e.target).focus();
- });
- $(document).on('hide.bs.modal', '.modal', function (e) {
- e.target.remove();
- });
-}
-
-copyToClipboard = (text, success, error) => {
- fullURL = window.location.protocol + '//' + window.location.hostname + text;
- if (window.clipboardData && window.clipboardData.setData) {
- // Internet Explorer-specific code path to prevent textarea being shown while dialog is visible.
- return window.clipboardData.setData("Text", fullURL);
- }
- else {
- navigator.clipboard.writeText(fullURL).then(success, error)
- }
-}
-
-let switchToEditTab = (view, section) => {
- let elementID = Boolean(view.model && view.model.elementID) ? view.model.elementID + "-" : "";
- if($(view.queryByHook(elementID + 'view-' + section)).hasClass('active')) {
- $(view.queryByHook(elementID + section + '-edit-tab')).tab('show');
- $(view.queryByHook(elementID + 'edit-' + section)).addClass('active');
- $(view.queryByHook(elementID + 'view-' + section)).removeClass('active');
- }
}
-
let maintenance = (view) => {
getXHR("stochss/api/message", {
always: (err, response, body) => {
@@ -304,23 +186,125 @@ let maintenance = (view) => {
}
});
}
+let newWorkflow = (parent, mdlPath, isSpatial, type) => {
+ if(document.querySelector('#newWorkflowModal')) {
+ document.querySelector('#newWorkflowModal').remove();
+ }
+ let typeCodes = {
+ "Ensemble Simulation": "_ES",
+ "Spatial Ensemble Simulation": "_SES",
+ "Parameter Sweep": "_PS"
+ }
+ let ext = isSpatial ? /.smdl/g : /.mdl/g;
+ let typeCode = typeCodes[type];
+ let name = mdlPath.split('/').pop().replace(ext, typeCode);
+ let modal = $(modals.createWorkflowHtml(name, type)).modal();
+ let okBtn = document.querySelector('#newWorkflowModal .ok-model-btn');
+ let input = document.querySelector('#newWorkflowModal #workflowNameInput');
+ okBtn.disabled = false;
+ input.addEventListener("keyup", (event) => {
+ if(event.keyCode === 13){
+ event.preventDefault();
+ okBtn.click();
+ }
+ });
+ input.addEventListener("input", (e) => {
+ let endErrMsg = document.querySelector('#newWorkflowModal #workflowNameInputEndCharError');
+ let charErrMsg = document.querySelector('#newWorkflowModal #workflowNameInputSpecCharError');
+ let error = validateName(input.value);
+ okBtn.disabled = error !== "" || input.value.trim() === "";
+ charErrMsg.style.display = error === "both" || error === "special" ? "block" : "none";
+ endErrMsg.style.display = error === "both" || error === "forward" ? "block" : "none";
+ });
+ okBtn.addEventListener('click', (e) => {
+ modal.modal("hide");
+ let wkflFile = `${input.value.trim()}.wkfl`;
+ if(mdlPath.includes(".proj") && !mdlPath.includes(".wkgp")){
+ var wkflPath = path.join(path.dirname(mdlPath), "WorkflowGroup1.wkgp", wkflFile);
+ }else{
+ var wkflPath = path.join(path.dirname(mdlPath), wkflFile);
+ }
+ let queryString = `?path=${wkflPath}&model=${mdlPath}&type=${type}`;
+ let endpoint = path.join(getApiPath(), "workflow/new") + queryString;
+ getXHR(endpoint, {
+ success: (err, response, body) => {
+ window.location.href = `${path.join(getBasePath(), "stochss/workflow/edit")}?path=${body.path}`;
+ }
+ });
+ });
+}
+let postXHR = (endpoint, data, {
+ always = function (err, response, body) {}, success = function (err, response, body) {},
+ error = function (err, response, body) {}}={}, isJSON) => {
+ try {
+ xhr({uri: endpoint, json: isJSON !== undefined ? isJSON : true, method: "post", body: data}, function (err, response, body) {
+ if(response.statusCode < 400) {
+ success(err, response, body);
+ }else if(response.statusCode < 500) {
+ error(err, response, body);
+ }else{
+ console.log("Critical Error Detected");
+ }
+ always(err, response, body);
+ });
+ }catch(exception){
+ console.log(exception);
+ let response = {Reason: "Network Error", Message: exception};
+ let body = {response: response, err: exception}
+ error(exception, response, body);
+ }
+}
+let registerRenderSubview = (parent, view, hook) => {
+ parent.registerSubview(view);
+ parent.renderSubview(view, parent.queryByHook(hook));
+}
+let switchToEditTab = (view, section) => {
+ let elementID = Boolean(view.model && view.model.elementID) ? view.model.elementID + "-" : "";
+ if($(view.queryByHook(elementID + 'view-' + section)).hasClass('active')) {
+ $(view.queryByHook(elementID + section + '-edit-tab')).tab('show');
+ $(view.queryByHook(elementID + 'edit-' + section)).addClass('active');
+ $(view.queryByHook(elementID + 'view-' + section)).removeClass('active');
+ }
+}
+let tooltipSetup = () => {
+ $(() => {
+ $('[data-toggle="tooltip"]').tooltip();
+ $('[data-toggle="tooltip"]').on('click ', () => {
+ $('[data-toggle="tooltip"]').tooltip("hide");
+ });
+ });
+}
+let validateName = (input, {rename=false, saveAs=true}={}) => {
+ var error = "";
+ if(input.endsWith('/')) {
+ error = 'forward';
+ }
+ var invalidChars = "`~!@#$%^&*=+[{]}\"|:;'<,>?\\";
+ if(rename || !saveAs) {
+ invalidChars += "/";
+ }
+ for(var i = 0; i < input.length; i++) {
+ if(invalidChars.includes(input.charAt(i))) {
+ error = error === "" || error === "special" ? "special" : "both";
+ }
+ }
+ return error;
+}
module.exports = {
- routePrefix: routePrefix,
- getApiPath: getApiPath,
- getBasePath: getBasePath,
- getBrowser: getBrowser,
- registerRenderSubview: registerRenderSubview,
- changeCollapseButtonText: changeCollapseButtonText,
- newWorkflow: newWorkflow,
- getXHR: getXHR,
- postXHR: postXHR,
- tooltipSetup: tooltipSetup,
- documentSetup: documentSetup,
- copyToClipboard: copyToClipboard,
- switchToEditTab: switchToEditTab,
- validateName: validateName,
- maintenance: maintenance
+ changeCollapseButtonText: changeCollapseButtonText,
+ copyToClipboard: copyToClipboard,
+ documentSetup: documentSetup,
+ getApiPath: getApiPath,
+ getBasePath: getBasePath,
+ getBrowser: getBrowser,
+ getXHR: getXHR,
+ maintenance: maintenance,
+ newWorkflow: newWorkflow,
+ postXHR: postXHR,
+ registerRenderSubview: registerRenderSubview,
+ routePrefix: routePrefix,
+ switchToEditTab: switchToEditTab,
+ tooltipSetup: tooltipSetup,
+ validateName: validateName
};
-
-
diff --git a/client/config.js b/client/config.js
index 0d696a21c8..f6d4c42f05 100644
--- a/client/config.js
+++ b/client/config.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/domain-view/domain-view.js b/client/domain-view/domain-view.js
index c90b8fbefb..2274055cfe 100644
--- a/client/domain-view/domain-view.js
+++ b/client/domain-view/domain-view.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -22,20 +22,21 @@ let _ = require('underscore');
//support files
let app = require('../app');
let Plotly = require('plotly.js-dist');
+//collections
+let Particles = require('../models/particles');
//models
-let Particle = require('../models/particle');
+let Action = require('../models/action');
//views
let View = require('ampersand-view');
let TypesView = require('./views/types-view');
let LimitsView = require('./views/limits-view');
+let ShapesView = require('./views/shapes-view');
+let ActionsView = require('./views/actions-view');
let QuickviewType = require('./views/quickview-type');
let PropertiesView = require('./views/properties-view');
let EditParticleView = require('./views/particle-view');
let ViewParticleView = require('./views/view-particle');
-let FillGeometryView = require('./views/fill-geometry-view');
-let ImportMeshView = require('./views/import-mesh-view');
-let Edit3DDomainView = require('./views/edit-3D-domain-view');
-let TypesDescriptionView = require('./views/types-description-view');
+let TransformationsView = require('./views/transformations-view');
//templates
let template = require('./domainView.pug');
@@ -43,10 +44,11 @@ module.exports = View.extend({
template: template,
events: {
'click [data-hook=collapse-domain-particles]' : 'changeCollapseButtonText',
- 'click [data-hook=add-new-particle]' : 'handleAddParticle',
'click [data-hook=save-selected-particle]' : 'handleSaveParticle',
'click [data-hook=remove-selected-particle]' : 'handleRemoveParticle',
- 'click [data-hook=collapse-domain-figure]' : 'changeCollapseButtonText'
+ 'click [data-hook=collapse-domain-figure]' : 'changeCollapseButtonText',
+ 'click [data-hook=collapse-dv-advanced-section]' : 'changeCollapseButtonText',
+ 'click [data-hook=update-preview-plot]' : 'updatePlotPreview'
},
initialize: function (attrs, options) {
View.prototype.initialize.apply(this, arguments);
@@ -54,233 +56,98 @@ module.exports = View.extend({
this.plot = attrs.plot ? attrs.plot : null;
this.elements = attrs.elements ? attrs.elements : null;
this.queryStr = attrs.queryStr;
- this.newPart = this.createNewParticle();
- this.actPart = {"part":null, "tn":0, "pn":0};
+ this.actPart = {"action": null, "part": null, "tn": 0, "pn": 0};
this.model.updateValid();
},
render: function (attrs, options) {
View.prototype.render.apply(this, arguments);
- this.renderPropertiesView();
- this.renderLimitsView();
- this.renderTypesView();
if(this.readOnly) {
$(this.queryByHook('domain-particles-editor')).css('display', 'none');
$(this.queryByHook('domain-figure-preview')).css('display', 'none');
this.renderTypesQuickview();
}else{
- this.updateParticleViews({includeGeometry: true});
- this.renderTypesDescriptionView();
+ this.model.on('update-type-deps', this.updateTypeDeps, this);
+ this.model.on('update-shape-deps', this.updateShapeDeps, this);
+ this.model.on('update-transformation-deps', this.updateTransformationDeps, this);
+ this.model.on('update-particle-type-options', this.updateParticleTypeOptions, this);
+ this.renderEditParticleView();
}
+ this.renderPropertiesView();
+ this.renderLimitsView();
+ this.renderTypesView();
+ this.renderShapesView();
+ this.renderTransformationsView();
+ this.renderActionsView();
if(!this.elements) {
this.elements = {
figure: this.queryByHook('domain-figure'),
figureEmpty: this.queryByHook('domain-figure-empty')
}
}
- let endpoint = path.join(app.getApiPath(), "spatial-model/domain-plot") + this.queryStr;
+ this.model.on('update-plot-preview', this.updatePlotPreview, this);
if(this.plot) {
this.displayFigure();
}else{
- app.getXHR(endpoint, {success: (err, response, body) => {
- this.plot = body.fig;
- this.traceTemp = body.trace_temp;
- this.displayFigure();
- }});
+ this.model.trigger('update-plot-preview', {resetFigure: false});
}
},
- add3DDomain: function (limits, particles) {
- let limitsChanged = this.changeDomainLimits(limits, false);
- particles.forEach((particle) => {
- particle = new Particle(particle);
- this.model.particles.addParticle({particle: particle});
- this.addParticle({particle: particle});
- });
- if(limitsChanged) {
- this.renderLimitsView();
- }
- this.renderTypesView();
- this.resetFigure();
+ changeCollapseButtonText: function (e) {
+ app.changeCollapseButtonText(this, e)
},
- addMeshDomain: function (limits, particles, types, reset) {
- let limitsChanged = this.changeDomainLimits(limits, reset);
- if(types) {
- this.addMissingTypes(types);
+ changeDomainLimits: function (limits) {
+ limitsChanged = false;
+ if(this.model.x_lim[0] > limits[0][0]) {
+ this.model.x_lim[0] = limits[0][0];
+ limitsChanged = true;
}
- particles.forEach((particle) => {
- particle = new Particle(particle);
- this.model.particles.addParticle({particle: particle});
- this.addParticle({particle: particle});
- });
- if(limitsChanged) {
- this.renderLimitsView();
+ if(this.model.y_lim[0] > limits[1][0]) {
+ this.model.y_lim[0] = limits[1][0];
+ limitsChanged = true;
}
- this.renderTypesView();
- if(types) {
- this.updateParticleViews();
+ if(this.model.z_lim[0] > limits[2][0]) {
+ this.model.z_lim[0] = limits[2][0];
+ limitsChanged = true;
}
- this.resetFigure();
- },
- addMissingTypes: function (typeIDs) {
- typeIDs.forEach((typeID) => {
- if(!this.model.types.get(typeID, 'typeID')) {
- let name = this.model.types.addType();
- this.addType(name, {update: false});
- }
- });
- },
- addParticle: function ({particle=this.newPart}={}) {
- this.plot.data[particle.type].ids.push(particle.particle_id);
- this.plot.data[particle.type].x.push(particle.point[0]);
- this.plot.data[particle.type].y.push(particle.point[1]);
- this.plot.data[particle.type].z.push(particle.point[2]);
- },
- addType: function (name, {update=true}={}) {
- let newTrace = JSON.parse(JSON.stringify(this.traceTemp));
- newTrace.name = name;
- this.plot.data.push(newTrace);
- if(update) {
- this.updateParticleViews();
+ if(this.model.x_lim[1] < limits[0][1]) {
+ this.model.x_lim[1] = limits[0][1];
+ limitsChanged = true;
}
- },
- applyGeometry: function (ids, type) {
- let actPart = JSON.parse(JSON.stringify(this.actPart));
- ids.forEach((id) => {
- let particle = this.model.particles.get(id, 'particle_id');
- // Set active particle for updating particle in the plot
- this.actPart = {
- part: particle,
- tn: particle.type,
- pn: this.plot.data[particle.type].ids.indexOf(particle.particle_id)
- }
- // Update the particle attributes
- particle.type = type.typeID;
- particle.mass = type.mass;
- particle.volume = type.volume;
- particle.rho = type.rho;
- particle.nu = type.nu;
- particle.c = type.c;
- particle.fixed = type.fixed;
- // Update the particle in the figure
- this.changeParticleType(type.typeID, {update: false});
- });
- if(actPart.part) {
- this.actPart = {
- part: this.model.particles.get(actPart.part.particle_id, "particle_id"),
- tn: type.typeID,
- pn: this.plot.data[type.typeID].ids.indexOf(actPart.part.particle_id)
- }
- if(ids.includes(actPart.part.particle_id)) {
- this.renderEditParticleView();
- }
- }else{
- this.actPart = actPart
+ if(this.model.y_lim[1] < limits[1][1]) {
+ this.model.y_lim[1] = limits[1][1];
+ limitsChanged = true;
}
- this.renderTypesView();
- this.resetFigure();
- },
- changeCollapseButtonText: function (e) {
- app.changeCollapseButtonText(this, e)
- },
- changeDomainLimits: function (limits, reset) {
- var limitsChanged = false;
- if(reset) {
- this.model.x_lim = limits.x_lim;
- this.model.y_lim = limits.y_lim;
- this.model.y_lim = limits.y_lim;
+ if(this.model.z_lim[1] < limits[2][1]) {
+ this.model.z_lim[1] = limits[2][1];
limitsChanged = true;
- }else{
- if(this.model.x_lim[0] > limits.x_lim[0]) {
- this.model.x_lim[0] = limits.x_lim[0];
- limitsChanged = true;
- }
- if(this.model.y_lim[0] > limits.y_lim[0]) {
- this.model.y_lim[0] = limits.y_lim[0];
- limitsChanged = true;
- }
- if(this.model.z_lim[0] > limits.z_lim[0]) {
- this.model.z_lim[0] = limits.z_lim[0];
- limitsChanged = true;
- }
- if(this.model.x_lim[1] < limits.x_lim[1]) {
- this.model.x_lim[1] = limits.x_lim[1];
- limitsChanged = true;
- }
- if(this.model.y_lim[1] < limits.y_lim[1]) {
- this.model.y_lim[1] = limits.y_lim[1];
- limitsChanged = true;
- }
- if(this.model.z_lim[1] < limits.z_lim[1]) {
- this.model.z_lim[1] = limits.z_lim[1];
- limitsChanged = true;
- }
}
- return limitsChanged;
- },
- changeParticleLocation: function () {
- this.plot.data[this.actPart.tn].x[this.actPart.pn] = this.actPart.part.point[0];
- this.plot.data[this.actPart.tn].y[this.actPart.pn] = this.actPart.part.point[1];
- this.plot.data[this.actPart.tn].z[this.actPart.pn] = this.actPart.part.point[2];
- },
- changeParticleType: function (type, {update=true}={}) {
- let id = this.plot.data[this.actPart.tn].ids.splice(this.actPart.pn, 1)[0];
- let x = this.plot.data[this.actPart.tn].x.splice(this.actPart.pn, 1)[0];
- let y = this.plot.data[this.actPart.tn].y.splice(this.actPart.pn, 1)[0];
- let z = this.plot.data[this.actPart.tn].z.splice(this.actPart.pn, 1)[0];
- this.plot.data[type].ids.push(id);
- this.plot.data[type].x.push(x);
- this.plot.data[type].y.push(y);
- this.plot.data[type].z.push(z);
- if(update) {
- this.resetFigure();
+ if(limitsChanged) {
+ this.renderLimitsView();
}
},
- createNewParticle: function () {
+ createNewAction: function () {
let type = this.model.types.get(0, 'typeID');
- return new Particle({
- c: type.c,
- fixed: type.fixed,
- mass: type.mass,
- nu: type.nu,
- point: [0, 0, 0],
- rho: type.rho,
- type: type.typeID,
- volume: type.volume
+ return new Action({
+ type: "", scope: 'Single Particle', priority: 1, enable: true,
+ shape: '', transformation: '', typeID: type.typeID,
+ point: {x: 0, y: 0, z: 0}, newPoint: {x: 0, y: 0, z: 0},
+ c: type.c, fixed: type.fixed, mass: type.mass,
+ nu: type.nu, rho: type.rho, vol: type.volume
});
},
completeAction: function (prefix) {
$(this.queryByHook(`${prefix}-in-progress`)).css("display", "none");
$(this.queryByHook(`${prefix}-complete`)).css("display", "inline-block");
+ $(this.queryByHook("upp-error")).css("display", "none");
setTimeout(() => {
$(this.queryByHook(`${prefix}-complete`)).css('display', 'none');
}, 5000);
},
- deleteParticle: function () {
- this.plot.data[this.actPart.tn].ids.splice(this.actPart.pn, 1);
- this.plot.data[this.actPart.tn].x.splice(this.actPart.pn, 1);
- this.plot.data[this.actPart.tn].y.splice(this.actPart.pn, 1);
- this.plot.data[this.actPart.tn].z.splice(this.actPart.pn, 1);
- this.resetFigure();
- },
- deleteType: function (type, {unassign=true}={}) {
- if(unassign) {
- this.unassignAllParticles(type, {update: false});
- }else{
- if(this.actPart.part && this.actPart.part.type === type) {
- this.actPart = {"part":null, "tn":0, "pn":0};
- }
- if(this.newPart && this.newPart.type === type) {
- this.newPart.type = 0
- }
- let particles = this.model.particles.filter((particle) => {
- return particle.type === type;
- });
- this.model.particles.removeParticles(particles);
+ displayError: function () {
+ let views = {
+ types: this.typesView,
+ actions: this.actionsView
}
- this.model.realignTypes(type);
- this.plot.data.splice(type, 1);
- this.renderTypesView();
- this.updateParticleViews({includeGeometry: true});
- this.resetFigure();
+ views[this.model.error.type].openSection();
},
displayFigure: function () {
if(this.model.particles.length > 0) {
@@ -293,36 +160,28 @@ module.exports = View.extend({
$(this.elements.figure).css('display', 'none');
}
},
- handleAddParticle: function () {
- this.startAction("anp")
- this.model.particles.addParticle({particle: this.newPart});
- this.addParticle();
- this.resetFigure();
- this.renderTypesView();
- this.newPart = this.createNewParticle();
- this.renderNewParticleView();
- this.completeAction("anp");
+ errorAction: function (action) {
+ $(this.queryByHook("upp-in-progress")).css("display", "none");
+ $(this.queryByHook("upp-action-error")).text(action);
+ $(this.queryByHook("upp-error")).css("display", "block");
},
handleRemoveParticle: function () {
this.startAction("rsp")
- this.model.particles.removeParticle(this.actPart.part);
- this.deleteParticle();
- this.actPart = {"part":null, "tn":0, "pn":0};
- this.renderTypesView();
- this.renderEditParticleView();
+ let action = this.actPart.action;
+ this.model.actions.addAction("Remove Action", {action: action});
+ this.model.trigger('update-plot-preview');
+ this.actPart.action = this.createNewAction();
this.completeAction("rsp")
+ this.renderEditParticleView();
},
handleSaveParticle: function () {
this.startAction("esp");
- if(this.editParticleView.origType !== this.actPart.part.type) {
- this.changeParticleType(this.actPart.part.type, {update: false});
- this.renderTypesView();
- }
- if(!this.actPart.part.comparePoint(this.editParticleView.origPoint)) {
- this.changeParticleLocation();
- }
- this.resetFigure();
+ let action = this.actPart.action;
+ this.model.actions.addAction("Set Action", {action: action});
+ this.model.trigger('update-plot-preview');
+ this.actPart.action = this.createNewAction();
this.completeAction("esp");
+ this.renderEditParticleView();
},
removeFigure: function () {
try {
@@ -332,25 +191,25 @@ module.exports = View.extend({
return
}
},
- renameType: function (index, name) {
- this.plot.data[index].name = name;
- this.resetFigure();
- },
- renderEdit3DDomainView: function () {
- if(this.edit3DDomainView) {
- this.edit3DDomainView.remove();
+ renderActionsView: function () {
+ if(this.actionsView) {
+ this.actionsView.remove();
}
- this.edit3DDomainView = new Edit3DDomainView();
- app.registerRenderSubview(this, this.edit3DDomainView, "3d-domain-container");
+ this.actionsView = new ActionsView({
+ collection: this.model.actions,
+ readOnly: this.readOnly
+ });
+ let hook = "domain-actions-container";
+ app.registerRenderSubview(this, this.actionsView, hook);
},
renderEditParticleView: function () {
if(this.editParticleView) {
this.editParticleView.remove();
}
- let disable = this.actPart.part == null
+ let disable = this.actPart.action == null
this.editParticleView = new EditParticleView({
- model: this.actPart.part ? this.actPart.part : this.createNewParticle(),
- defaultType: this.model.types.get(this.actPart.part ? this.actPart.part.type : 0, "typeID"),
+ model: this.actPart.action ? this.actPart.action : this.createNewAction(),
+ defaultType: this.model.types.get(this.actPart.action ? this.actPart.action.typeID : 0, "typeID"),
viewIndex: 1,
disable: disable
});
@@ -359,20 +218,6 @@ module.exports = View.extend({
$(this.queryByHook("save-selected-particle")).prop('disabled', disable);
$(this.queryByHook("remove-selected-particle")).prop('disabled', disable);
},
- renderFillGeometryView: function () {
- if(this.fillGeometryView) {
- this.fillGeometryView.remove();
- }
- this.fillGeometryView = new FillGeometryView();
- app.registerRenderSubview(this, this.fillGeometryView, 'fill-geometry-container');
- },
- renderImportMeshView: function () {
- if(this.importMeshView) {
- this.importMeshView.remove();
- }
- this.importMeshView = new ImportMeshView();
- app.registerRenderSubview(this, this.importMeshView, "import-particles-section");
- },
renderLimitsView: function () {
if(this.limitsView) {
this.limitsView.remove();
@@ -383,17 +228,6 @@ module.exports = View.extend({
});
app.registerRenderSubview(this, this.limitsView, "domain-limits-container");
},
- renderNewParticleView: function () {
- if(this.newParticleView) {
- this.newParticleView.remove();
- }
- this.newParticleView = new EditParticleView({
- model: this.newPart,
- defaultType: this.model.types.get(0, "typeID"),
- viewIndex: 0
- });
- app.registerRenderSubview(this, this.newParticleView, "new-particle-container");
- },
renderPropertiesView: function () {
if(this.propertiesView) {
this.propertiesView.remove();
@@ -404,12 +238,27 @@ module.exports = View.extend({
});
app.registerRenderSubview(this, this.propertiesView, "domain-properties-container");
},
- renderTypesDescriptionView: function () {
- if(this.typesDescriptionView) {
- this.typesDescriptionView.remove();
+ renderShapesView: function () {
+ if(this.shapesView) {
+ this.shapesView.remove();
}
- this.typesDescriptionView = new TypesDescriptionView();
- app.registerRenderSubview(this, this.typesDescriptionView, "particle-types-container");
+ this.shapesView = new ShapesView({
+ collection: this.model.shapes,
+ readOnly: this.readOnly
+ });
+ let hook = "domain-shapes-container";
+ app.registerRenderSubview(this, this.shapesView, hook);
+ },
+ renderTransformationsView: function () {
+ if(this.transformationsView) {
+ this.transformationsView.reomve();
+ }
+ this.transformationsView = new TransformationsView({
+ collection: this.model.transformations,
+ readOnly: this.readOnly
+ });
+ let hook = "domain-transformations-container";
+ app.registerRenderSubview(this, this.transformationsView, hook);
},
renderTypesQuickview: function () {
if(this.typesQuickviewView) {
@@ -426,21 +275,6 @@ module.exports = View.extend({
if(this.typesView) {
this.typesView.remove();
}
- let particleCounts = {};
- this.model.particles.forEach((particle) => {
- if(particleCounts[particle.type]) {
- particleCounts[particle.type] += 1;
- }else{
- particleCounts[particle.type] = 1;
- }
- });
- this.model.types.forEach((dType) => {
- if(particleCounts[dType.typeID]) {
- dType.numParticles = particleCounts[dType.typeID];
- }else{
- dType.numParticles = 0;
- }
- });
this.typesView = new TypesView({
collection: this.model.types,
readOnly: this.readOnly
@@ -453,6 +287,7 @@ module.exports = View.extend({
}
this.elements.select.css('display', 'none');
this.viewParticleView = new ViewParticleView({
+ parent: this,
model: this.actPart.part
});
app.registerRenderSubview(this.elements.particle.view, this.viewParticleView, this.elements.particle.hook);
@@ -463,7 +298,16 @@ module.exports = View.extend({
},
selectParticle: function (data) {
let point = data.points[0];
- this.actPart.part = this.model.particles.get(point.id, 'particle_id');
+ let particle = this.model.particles.get(point.id, 'particle_id')
+ this.actPart.part = particle;
+ this.actPart.action = new Action({
+ type: "", scope: 'Single Particle', priority: 1, enable: true,
+ shape: '', transformation: '', typeID: particle.type,
+ point: {x: particle.point[0], y: particle.point[1], z: particle.point[2]},
+ newPoint: {x: particle.point[0], y: particle.point[1], z: particle.point[2]},
+ c: particle.c, fixed: particle.fixed, mass: particle.mass,
+ nu: particle.nu, rho: particle.rho, vol: particle.volume
+ });
this.actPart.tn = point.curveNumber;
this.actPart.pn = point.pointNumber;
if(this.readOnly) {
@@ -472,73 +316,113 @@ module.exports = View.extend({
this.renderEditParticleView();
}
},
- setParticleTypes: function (typeIDs, types) {
- this.addMissingTypes(typeIDs);
- let actPart = JSON.parse(JSON.stringify(this.actPart));
- types.forEach((type) => {
- let particle = this.model.particles.get(type.particle_id, 'particle_id');
- this.actPart = {
- part: particle,
- tn: particle.type,
- pn: this.plot.data[particle.type].ids.indexOf(particle.particle_id)
- }
- this.changeParticleType(type.typeID, {update: false});
- particle.type = type.typeID;
- });
- if(actPart.part && actPart.part.type === type) {
- this.actPart = {
- part: this.model.particles.get(actPart.part.particle_id, "particle_id"),
- tn: 0,
- pn: this.plot.data[0].ids.indexOf(actPart.part.particle_id)
- }
- this.renderEditParticleView();
- }else{
- this.actPart = actPart
- }
- this.resetFigure();
- this.updateParticleViews();
- },
startAction: function (prefix) {
$(this.queryByHook(`${prefix}-complete`)).css('display', 'none');
$(this.queryByHook(`${prefix}-in-progress`)).css("display", "inline-block");
},
- unassignAllParticles: function (type, {update=true}={}) {
- let actPart = JSON.parse(JSON.stringify(this.actPart));
- this.model.particles.forEach((particle) => {
- if(particle.type === type) {
- this.actPart = {
- part: particle,
- tn: type,
- pn: this.plot.data[type].ids.indexOf(particle.particle_id)
+ updateParticleTypeOptions: function ({currName=null, newName=null}={}) {
+ if(currName === null && newName === null) { return; }
+ if(this.editParticleView.model.typeID === currName) {
+ this.editParticleView.model.typeID = newName;
+ }
+ this.editParticleView.renderTypeSelectView();
+ },
+ updatePlotPreview: function ({resetFigure=true}={}) {
+ if(resetFigure) {
+ this.startAction("upp");
+ }
+ let endpoint = path.join(app.getApiPath(), "spatial-model/domain-plot");
+ app.postXHR(endpoint, this.model, {
+ success: (err, response, body) => {
+ this.plot = body.fig;
+ this.model.particles = new Particles(body.particles);
+ this.typesView.updateParticleCounts(this.model.particles);
+ this.actionsView.toggleActionsCollectionError("vv");
+ if(this.readOnly) {
+ this.renderTypesQuickview();
+ }else{
+ this.actionsView.toggleActionsCollectionError("ev");
+ }
+ if(resetFigure) {
+ this.resetFigure();
+ this.completeAction("upp");
+ }else{
+ this.displayFigure();
}
- this.changeParticleType(0, {update: false});
- particle.type = 0;
+ this.changeDomainLimits(body.limits);
+ },
+ error: (err, response, body) => {
+ this.removeFigure();
+ $(this.elements.figureEmpty).css('display', 'none');
+ $(this.elements.figure).css('display', 'none');
+ this.errorAction(body.Message);
}
});
- if(actPart.part) {
- this.actPart = {
- part: this.model.particles.get(actPart.part.particle_id, "particle_id"),
- tn: 0,
- pn: this.plot.data[0].ids.indexOf(actPart.part.particle_id)
+ },
+ updateShapeDeps: function () {
+ let deps = [];
+ let shapeNames = [];
+ let combForms = [];
+ this.model.shapes.forEach((shape) => {
+ shapeNames.push(shape.name);
+ if(shape.type === "Combinatory") {
+ combForms.push(shape.formula)
}
- if(actPart.part.type === type) {
- this.renderEditParticleView();
+ });
+ combForms.forEach((formula) => {
+ formula = formula.replace(/\(/g, ' ').replace(/\)/g, ' ');
+ let formDeps = formula.split(' ');
+ shapeNames.forEach((name) => {
+ if(formDeps.includes(name) && !deps.includes(name)) {
+ deps.push(name);
+ }
+ });
+ });
+ this.model.actions.forEach((action) => {
+ let shape = action.shape;
+ if(shapeNames.includes(shape) && !deps.includes(shape)) {
+ deps.push(shape);
}
- }else{
- this.actPart = actPart
- }
- if(update) {
- this.renderTypesView();
- this.resetFigure();
- }
+ });
+ this.model.shapes.trigger('update-inuse', {deps: deps});
},
- updateParticleViews: function ({includeGeometry=false}={}) {
- this.renderNewParticleView();
- this.renderEditParticleView();
- this.renderEdit3DDomainView();
- this.renderImportMeshView();
- if(includeGeometry) {
- this.renderFillGeometryView();
- }
+ updateTransformationDeps: function () {
+ let deps = [];
+ let transNames = this.model.transformations.map((transformation) => {
+ return transformation.name;
+ });
+ this.model.transformations.forEach((transformation) => {
+ let nestedTrans = transformation.transformation;
+ if(transNames.includes(nestedTrans) && !deps.includes(nestedTrans)) {
+ deps.push(nestedTrans);
+ }
+ });
+ this.model.actions.forEach((action) => {
+ let transformation = action.transformation;
+ if(transNames.includes(transformation) && !deps.includes(transformation)) {
+ deps.push(transformation);
+ }
+ });
+ this.model.transformations.trigger('update-inuse', {deps: deps});
+ },
+ updateTypeDeps: function () {
+ let deps = [];
+ let revDeps = {};
+ this.model.types.forEach((type) => {
+ if(type.name !== "Un-Assigned") {
+ revDeps[type.name] = [];
+ }
+ });
+ this.model.actions.forEach((action) => {
+ let type = this.model.types.get(action.typeID, 'typeID').name;
+ if(Object.keys(revDeps).includes(type)) {
+ revDeps[type].push(action.cid);
+ if(!deps.includes(type)) {
+ deps.push(type);
+ }
+ }
+ });
+ console.log(revDeps);
+ this.model.types.trigger('update-inuse', {deps: deps});
}
});
diff --git a/client/domain-view/domainView.pug b/client/domain-view/domainView.pug
index 646a80baa5..3b3ed1aca5 100644
--- a/client/domain-view/domainView.pug
+++ b/client/domain-view/domainView.pug
@@ -2,67 +2,27 @@ div#domain-view
div(data-hook="domain-properties-container")
- div(data-hook="domain-limits-container")
-
div(data-hook="domain-types-container")
- div#domain-particles-editor.card(data-hook="domain-particles-editor")
-
- div.card-header.pb-0
-
- h3.inline.mr-3 Particles
-
- div.inline.mr-3
-
- ul.nav.nav-tabs.card-header-tabs(id="domain-particles-tabs")
-
- li.nav-item
-
- a.nav-link.tab.active(data-hook="new-particle-tab" data-toggle="tab" href="#create-new-particle") Create New
-
- li.nav-item
-
- a.nav-link.tab(data-hook="edit-particle-tab" data-toggle="tab" href="#edit-selected-particle") Edit Selected
-
- li.nav-item
+ div(data-hook="domain-shapes-container")
- a.nav-link.tab(data-hook="particle-types-tab" data-toggle="tab" href="#set-particle-types") Set Types from File
+ div(data-hook="domain-transformations-container")
- li.nav-item
+ div(data-hook="domain-actions-container")
- a.nav-link.tab(data-hook="3d-domain-tab" data-toggle="tab" href="#create-3d-domain") Create 3D Domain
-
- li.nav-item
-
- a.nav-link.tab(data-hook="import-particles-tab" data-toggle="tab" href="#import-particles") Import Mesh
-
- li.nav-item
-
- a.nav-link.tab(data-hook="fill-geometry-tab" data-toggle="tab" href="#fill-geometry") Fill Geometry
-
- button.btn.btn-outline-collapse(data-toggle="collapse" data-target="#domain-particles-section" data-hook="collapse-domain-particles") -
-
- div.collapse.show(id="domain-particles-section" data-hook="domain-particles-section")
-
- div.card-body.tab-content
-
- div.tab-pane.active(id="create-new-particle" data-hook="new-particle-section")
-
- div(data-hook="new-particle-container")
-
- button.btn.btn-outline-primary.box-shadow(data-hook="add-new-particle") Add Particle
+ div#domain-particles-editor.card(data-hook="domain-particles-editor")
- div.mdl-edit-btn.saving-status.inline(data-hook="anp-in-progress")
+ div.card-header.pb-0
- div.spinner-grow.mr-2
+ h3.inline.mr-3 Edit Single Particle
- span Adding particle ...
+ button.btn.btn-outline-collapse(data-toggle="collapse" data-target="#domain-particles-section" data-hook="collapse-domain-particles") +
- div.mdl-edit-btn.saved-status.inline(data-hook="anp-complete")
+ div.collapse(id="domain-particles-section" data-hook="domain-particles-section")
- span Particle Added
+ div.card-body
- div.tab-pane(id="edit-selected-particle" data-hook="edit-particle-section")
+ div(id="edit-selected-particle" data-hook="edit-particle-section")
div.text-info.mb-4(data-hook="edit-select-message" style="display: none")
| Click on a particle in the Domain section to begin editing.
@@ -93,34 +53,51 @@ div#domain-view
span Particle Removed
- div.tab-pane(id="set-particle-types" data-hook="set-particle-types-section")
+ div#domain-figure-preview.card(data-hook="domain-figure-preview")
- div(data-hook="particle-types-container")
+ div.card-header.pb-0
- div.tab-pane(id="create-3d-domain" data-hook="3d-domain-section")
+ h3.inline.mr-3 Domain
- div(data-hook="3d-domain-container")
+ button.btn.btn-outline-collapse(data-toggle="collapse" data-target="#domain-figure-container" data-hook="collapse-domain-figure") -
- div.tab-pane(id="import-particles" data-hook="import-particles-section")
+ div.card-body
- div(data-hook="import-particles-container")
+ p.mb-0
+ | Preview the domain below.
- div.tab-pane(id="fill-geometry" data-hook="fill-geometry-section")
+ div.collapse.show(id="domain-figure-container" data-hook="domain-figure-container")
- div(data-hook="fill-geometry-container")
+ div(id="domain-figure" data-hook="domain-figure" style="height: 800px")
- div#domain-figure-preview.card(data-hook="domain-figure-preview")
+ div.text-danger(data-hook="domain-figure-empty") The domain currently has no particles to display
+
+ button.btn.btn-outline-primary.box-shadow.ml-2(data-hook="update-preview-plot") Update Preview
+
+ div.mdl-edit-btn.saving-status.inline(data-hook="upp-in-progress")
+
+ div.spinner-grow.mr-2
+
+ span Updating preview ...
+
+ div.mdl-edit-btn.saved-status.inline(data-hook="upp-complete")
+
+ span Preview Updated
+
+ div.mdl-edit-btn.save-error-status(data-hook="upp-error")
+
+ span(data-hook="upp-action-error")
+
+ div.card(data-hook="domain-view-advanced-container")
div.card-header.pb-0
- h3.inline.mr-3 Domain
+ h3.inline Advanced
- button.btn.btn-outline-collapse(data-toggle="collapse" data-target="#domain-figure-container" data-hook="collapse-domain-figure") -
+ button.btn.btn-outline-collapse(data-toggle="collapse" data-target="#dv-advanced-section" data-hook="collapse-dv-advanced-section") +
- div.collapse.show(id="domain-figure-container" data-hook="domain-figure-container")
+ div.collapse(id="dv-advanced-section" data-hook="dv-advanced-section")
div.card-body
- div(id="domain-figure" data-hook="domain-figure" style="height: 800px")
-
- div.text-danger(data-hook="domain-figure-empty") The domain currently has no particles to display
\ No newline at end of file
+ div(data-hook="domain-limits-container")
diff --git a/client/domain-view/templates/actionsView.pug b/client/domain-view/templates/actionsView.pug
new file mode 100644
index 0000000000..69f253e7fc
--- /dev/null
+++ b/client/domain-view/templates/actionsView.pug
@@ -0,0 +1,96 @@
+div#domain-actions-editor.card
+
+ div.card-header.pb-0
+
+ h3.inline.mr-3 Actions
+
+ div.inline.mr-3
+
+ ul.nav.nav-tabs.card-header-tabs(id="actions-selection-tabs")
+
+ li.nav-item
+
+ a.nav-link.tab.active(data-hook="actions-edit-tab" data-toggle="tab" href="#edit-actions") Edit
+
+ li.nav-item
+
+ a.nav-link.tab(data-hook="actions-view-tab" data-toggle="tab" href="#view-actions") View
+
+ button.btn.btn-outline-collapse.inline(data-toggle="collapse" data-target="#collapse-actions" data-hook="collapse") +
+
+ div.card-body
+
+ p.mb-0
+ | Actions define changes to the particle state of a Shape/region. These changes can include the adding and removing of particles, setting particle properties, and importing particles from mesh/domain files.
+
+ div.collapse.tab-content(id="collapse-actions" data-hook="actions-list-container")
+
+ div.tab-pane.active(id="edit-actions" data-hook="edit-actions")
+
+ hr
+
+ div.mb-3.mx-1.row.head.align-items-baseline
+
+ div.col-sm-1
+
+ h6.inline Edit
+
+ div.col-sm-4
+
+ h6.inline Type
+
+ div.col-sm-3
+
+ h6.inline Priority
+
+ div.col-sm-2
+
+ h6.inline Enable
+
+ div.col-sm-2
+
+ h6.inline Remove
+
+ div.my-3(data-hook="edit-actions-list")
+
+ div(data-hook="ev-actions-collection-error")
+
+ p.text-danger Domains must have at least one enabled action that creates particles ('Fill Action' or 'Import Action').
+
+ div.dropdown
+
+ button.btn.btn-outline-primary.dropdown-toggle.box-shadow#addActionBtn(
+ data-hook="add-action",
+ data-toggle="dropdown",
+ aria-haspopup="true",
+ aria-expanded="false",
+ type="button"
+ ) Add Action
+
+ ul.dropdown-menu(aria-labelledby="addActionBtn")
+ li.dropdown-item(data-hook="fill-action" data-name="Fill Action") Fill Action
+ li.dropdown-item(data-hook="set-action" data-name="Set Action") Set Action
+ li.dropdown-item(data-hook="remove-action" data-name="Remove Action") Remove Action
+ li.dropdown-item(href="#") Import Action »
+ ul.dropdown-menu.dropdown-submenu
+ li.dropdown-item(data-hook="xml-mesh-action" data-name="XML Mesh") XML Mesh
+ li.dropdown-item(data-hook="mesh-io-action" data-name="Mesh IO") Mesh IO
+ li.dropdown-item(data-hook="stochss-domain-action" data-name="StochSS Domain") StochSS Domain
+
+ div.tab-pane(id="view-actions" data-hook="view-actions")
+
+ hr
+
+ div.mx-1.row.head.align-items-baseline
+
+ div.col-sm-4: h6 Type
+
+ div.col-sm-4: h6 Priority
+
+ div.col-sm-4: h6 Enable
+
+ div.mt-3(data-hook="view-actions-list")
+
+ div(data-hook="vv-actions-collection-error")
+
+ p.text-danger Domains must have at least one enabled action that creates particles ('Fill Action' or 'Import Action').
diff --git a/client/domain-view/templates/edit3DDomainView.pug b/client/domain-view/templates/edit3DDomainView.pug
deleted file mode 100644
index 7e3a60dd29..0000000000
--- a/client/domain-view/templates/edit3DDomainView.pug
+++ /dev/null
@@ -1,231 +0,0 @@
-div.mx-1
-
- h4.mt-3 Particle Distribution
-
- div
-
- hr
-
- div.mb-3.mx-1.row.head.align-items-baseline
-
- div.col-sm-3
-
- div.col-sm-2
-
- h6.inline X-Plane
-
- div.col-sm-2
-
- h6.inline Y-Plane
-
- div.col-sm-2
-
- h6.inline Z-Plane
-
- div.col-sm-3
-
- h6.inline Total
-
- div.row
-
- div.col-sm-3
-
- h6.inline Number of Particles
-
- div.col-sm-2
-
- div(data-target="edit-3d-domain-n" data-hook="edit-3d-domain-nx" data-key="nx")
-
- div.col-sm-2
-
- div(data-target="edit-3d-domain-n" data-hook="edit-3d-domain-ny" data-key="ny")
-
- div.col-sm-2
-
- div(data-target="edit-3d-domain-n" data-hook="edit-3d-domain-nz" data-key="nz")
-
- div.col-sm-3
-
- div(data-target="3d-domain-n" data-hook="3d-domain-n")
-
- h4.mt-3 Domain Limits
-
- div
-
- div.mx-1.row.head.align-items-baseline
-
- div.col-sm-3
-
- div.col-sm-3
-
- h6.inline X-Axis
-
- div.col-sm-3
-
- h6.inline Y-Axis
-
- div.col-sm-3
-
- h6.inline Z-Axis
-
- div.mt-3.mx-1.row
-
- div.col-sm-3
-
- h6.inline Minimum
-
- div.col-sm-3
-
- div(data-hook="edit-3d-domain-x-lim-min" data-target="edit-3d-domain-limitation" data-name="xLim" data-index="0")
-
- div.col-sm-3
-
- div(data-hook="edit-3d-domain-y-lim-min" data-target="edit-3d-domain-limitation" data-name="yLim" data-index="0")
-
- div.col-sm-3
-
- div(data-hook="edit-3d-domain-z-lim-min" data-target="edit-3d-domain-limitation" data-name="zLim" data-index="0")
-
- hr
-
- div.mx-1.row
-
- div.col-sm-3
-
- h6.inline Maximum
-
- div.col-sm-3
-
- div(data-hook="edit-3d-domain-x-lim-max" data-target="edit-3d-domain-limitation" data-name="xLim" data-index="1")
-
- div.col-sm-3
-
- div(data-hook="edit-3d-domain-y-lim-max" data-target="edit-3d-domain-limitation" data-name="yLim" data-index="1")
-
- div.col-sm-3
-
- div(data-hook="edit-3d-domain-z-lim-max" data-target="edit-3d-domain-limitation" data-name="zLim" data-index="1")
-
- div.mt-3
-
- h4.inline Advanced
-
- button.btn.btn-outline-collapse(data-toggle="collapse" data-target="#edit-3D-domain-advanced" data-hook="collapse-3D-domain-advanced") +
-
- div.collapse(id="edit-3D-domain-advanced" data-hook="3D-domain-advanced-container")
-
- div.mt-3
-
- h5.inline.mr-2 Type:
-
- div.inline(data-hook="edit-3d-domain-type-select")
-
- div.mt-3
-
- h5 Type Defaults
-
- hr
-
- div.mb-3.mx-1.row.head.align-items-baseline
-
- div.col-sm-2
-
- h6.inline Mass
-
- div.col-sm-2
-
- h6.inline Volume
-
- div.col-sm-2
-
- h6.inline Density
-
- div.col-sm-2
-
- h6.inline Viscosity
-
- div.col-sm-2
-
- h6.inline Speed of Sound
-
- div.col-sm-2
-
- h6.inline Fixed
-
- div.row.pl-3
-
- div.col-sm-2
-
- div(data-hook="edit-3d-domain-td-mass")
-
- div.col-sm-2
-
- div(data-hook="edit-3d-domain-td-vol")
-
- div.col-sm-2
-
- div(data-hook="edit-3d-domain-td-rho")
-
- div.col-sm-2
-
- div(data-hook="edit-3d-domain-td-nu")
-
- div.col-sm-2
-
- div(data-hook="edit-3d-domain-td-c")
-
- div.col-sm-2
-
- input(type="checkbox" data-hook="edit-3d-domain-td-fixed" disabled)
-
- div.my-3
-
- h5 Particle Transformations
-
- hr
-
- div.mb-3.mx-1.row.head.align-items-baseline
-
- div.col-sm-4
-
- h6.inline X-Axis
-
- div.col-sm-4
-
- h6.inline Y-Axis
-
- div.col-sm-4
-
- h6.inline Z-Axis
-
- div.row
-
- div.col-sm-4
-
- div(data-target="edit-3d-domain-trans" data-hook="edit-3d-domain-x-trans" data-index="0")
-
- div.col-sm-4
-
- div(data-target="edit-3d-domain-trans" data-hook="edit-3d-domain-y-trans" data-index="1")
-
- div.col-sm-4
-
- div(data-target="edit-3d-domain-trans" data-hook="edit-3d-domain-z-trans" data-index="2")
-
- div.inline
-
- button.btn.btn-outline-primary(data-hook="build-3d-domain") Build Domain
-
- div.mdl-edit-btn.saving-status.inline(data-hook="c3dd-in-progress")
-
- div.spinner-grow.mr-2
-
- span Creating domain ...
-
- div.mdl-edit-btn.saved-status.inline(data-hook="c3dd-complete")
-
- span Domain successfully created
-
- div.mdl-edit-btn.save-error-status(data-hook="c3dd-error")
-
- span(data-hook="c3dd-action-error")
diff --git a/client/domain-view/templates/editAction.pug b/client/domain-view/templates/editAction.pug
new file mode 100644
index 0000000000..34bea6af37
--- /dev/null
+++ b/client/domain-view/templates/editAction.pug
@@ -0,0 +1,265 @@
+div.mx-1
+
+ if(this.model.collection.indexOf(this.model) !== 0)
+ hr
+
+ div.row
+
+ div.col-sm-1
+
+ div.pl-3
+
+ input(type="checkbox" data-hook="select-action" data-toggle="collapse" data-target="#collapse-action-details" + this.model.cid)
+
+ div.col-sm-4
+
+ div(data-hook="select-type-container")
+
+ div.col-sm-3
+
+ div.pl-2(data-hook="input-priority-container" data-target="update-preview-plot")
+
+ div.col-sm-2
+
+ input(type="checkbox" data-hook="enable-action")
+
+ div.col-sm-2
+
+ button.btn.btn-outline-secondary.box-shadow(data-hook="remove") X
+
+ div-mx-1.pl-2(data-hook="action-details")
+
+ div.collapse(id="collapse-action-details" + this.model.cid)
+
+ hr
+
+ div
+
+ div.mb-3.mx-1.row.align-items-baseline
+
+ div.col-sm-4.pl-0.hidden(data-hook="action-scope")
+
+ h5.inline.mr-2 Scope:
+
+ div.inline(data-hook="select-scope-container")
+
+ div.col-sm-4.hidden(data-hook="multi-particle-shape")
+
+ h5.inline.mr-2 Shape:
+
+ div.inline(data-hook="shape-container")
+
+ div.col-sm-4.hidden(data-hook="multi-particle-transformation")
+
+ h5.inline.mr-2 Transformation:
+
+ div.inline(data-hook="transformation-container")
+
+ div.hidden(data-hook="single-particle-scope")
+
+ div
+
+ h5.inline.mr-2 Current Location:
+
+ div.inline.mr-2 (x:
+
+ div.inline.ml-2(data-hook="point-x-container" data-target="point" data-name="x" style="width: 25%")
+
+ div.inline.mr-2 , y:
+
+ div.inline.ml-2(data-hook="point-y-container" data-target="point" data-name="y" style="width: 25%")
+
+ div.inline.mr-2 , z:
+
+ div.inline.ml-2(data-hook="point-z-container" data-target="point" data-name="z" style="width: 25%")
+
+ div.inline )
+
+ div.hidden(data-hook="new-location")
+
+ h5.inline.mr-2 New Location:
+
+ div.inline.mr-2 (x:
+
+ div.inline.ml-2(data-hook="new-point-x-container" data-target="new-point" data-name="x" style="width: 25%")
+
+ div.inline.mr-2 , y:
+
+ div.inline.ml-2(data-hook="new-point-y-container" data-target="new-point" data-name="y" style="width: 25%")
+
+ div.inline.mr-2 , z:
+
+ div.inline.ml-2(data-hook="new-point-z-container" data-target="new-point" data-name="z" style="width: 25%")
+
+ div.inline )
+
+ div.hidden(data-hook="particle-properties")
+
+ h4 Particle Properties
+
+ div
+
+ h5.inline.mr-2 Type:
+
+ div.inline(data-hook="particle-type")
+
+ div
+
+ hr
+
+ div.mb-3.mx-1.row.head.align-items-baseline
+
+ div.col-sm-2
+
+ h6.inline Mass
+
+ div.col-sm-2
+
+ h6.inline Volume
+
+ div.col-sm-2
+
+ h6.inline Density
+
+ div.col-sm-2
+
+ h6.inline Viscosity
+
+ div.col-sm-2
+
+ h6.inline Speed of Sound
+
+ div.col-sm-2
+
+ h6.inline Fixed
+
+ div.row
+
+ div.col-sm-2
+
+ div.pl-2(data-hook="particle-mass" data-target="particle-property-containers")
+
+ div.col-sm-2
+
+ div(data-hook="particle-vol" data-target="particle-property-containers")
+
+ div.col-sm-2
+
+ div(data-hook="particle-rho" data-target="particle-property-containers")
+
+ div.col-sm-2
+
+ div(data-hook="particle-nu" data-target="particle-property-containers")
+
+ div.col-sm-2
+
+ div(data-hook="particle-c" data-target="particle-property-containers")
+
+ div.col-sm-2
+
+ input(type="checkbox" data-hook="particle-fixed" checked=this.model.fixed)
+
+ div.accordion.hidden(id="import-properties" + this.model.cid data-hook="import-properties")
+
+ div.card
+
+ div.card-header(id="importFilesHeader" + this.model.cid)
+
+ h3.mb-0.inline="Import Files"
+
+ button.btn.btn-outline-collapse.collapsed.inline(type="button" data-toggle="collapse" data-target="#collapseImportFiles" + this.model.cid data-hook="collapseImportFiles" aria-expanded="false" aria-controls="collapseImportFiles" style="float: right")
+
+ div(data-hook="import-chevron")
+
+ svg(xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 512 512")
+
+ path(d="M233.4 406.6c12.5 12.5 32.8 12.5 45.3 0l192-192c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L256 338.7 86.6 169.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l192 192z")
+
+ div.collapse(id="collapseImportFiles" + this.model.cid aria-labelledby="importFilesHeader" + this.model.cid data-parent="#import-properties" + this.model.cid)
+
+ div.card-body
+
+ div.my-3
+
+ span.inline(data-hook="meshfile-label" for="meshfile")=`Please specify a ${this.filetype} to import: `
+
+ input.ml-2(id="meshfile" data-hook="mesh-file" type="file" name="meshfile" size="30" accept=this.accept required)
+
+ div.mb-3
+
+ span.inline(for=typefile) Type descriptions (optional):
+
+ input.ml-2(id="typefile" data-hook="type-file" type="file" name="typefile" size="30" accept=".txt")
+
+ div.inline
+
+ button.btn.btn-outline-primary.box-shadow(data-hook="import-mesh-file" disabled style="text-transform: capitalize;")=`Import ${this.filetype}`
+
+ div.mdl-edit-btn.saving-status.inline(data-hook="imf-in-progress")
+
+ div.spinner-grow.mr-2
+
+ span Importing mesh ...
+
+ div.mdl-edit-btn.saved-status.inline(data-hook="imf-complete")
+
+ span
+
+ div.mdl-edit-btn.save-error-status(data-hook="imf-error")
+
+ span(data-hook="imf-action-error")
+
+ div.card
+
+ div.card-header(id="uploadedFilesHeader" + this.model.cid)
+
+ h3.mb-0.inline="Select Files"
+
+ button.btn.btn-outline-collapse.collapsed.inline(type="button" data-toggle="collapse" data-target="#collapseUploadedFiles" + this.model.cid data-hook="collapseUploadedFiles" aria-expanded="false" aria-controls="collapseUploadedFiles" style="float: right")
+
+ div(data-hook="uploaded-chevron")
+
+ svg(xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 512 512")
+
+ path(d="M233.4 406.6c12.5 12.5 32.8 12.5 45.3 0l192-192c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L256 338.7 86.6 169.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l192 192z")
+
+ div.collapse(id="collapseUploadedFiles" + this.model.cid aria-labelledby="uploadedFilesHeader" + this.model.cid data-parent="#import-properties" + this.model.cid)
+
+ div.card-body
+
+ div
+
+ div.hidden.text-info(data-hook="mesh-location-message")=`There are multiple ${this.filetype} files with that name, please select a location`
+
+ div
+
+ div.inline.mr-3
+
+ span.inline(data-hook="action-filename-label" for="action-filename-select")=`Please specify a ${this.filetype} to import: `
+
+ div.inline(id="action-filename-select" data-hook="action-mesh-select")
+
+ div.hidden.inline(data-hook="mesh-location-container")
+
+ span.inline.mr-2(for="mesh-location-select") Location:
+
+ div.inline(id="mesh-location-select" data-hook="mesh-location-select")
+
+ div.hidden(data-hook="type-descriptions-prop")
+
+ div.hidden.text-info(data-hook="types-location-message")
+ | There are multiple type files with that name, please select a location
+
+ div
+
+ div.inline.mr-3
+
+ span.inline(for=action-types-types-select) Type descriptions (optional):
+
+ div.inline(id="action-types-select" data-hook="action-type-select")
+
+ div.hidden.inline(data-hook="types-location-container")
+
+ span.inline.mr-2(for="file-location-select") Location:
+
+ div.inline(id="types-location-select" data-hook="type-location-select")
diff --git a/client/domain-view/templates/editParticleView.pug b/client/domain-view/templates/editParticleView.pug
index 0ab7f02d4e..1918a4a35e 100644
--- a/client/domain-view/templates/editParticleView.pug
+++ b/client/domain-view/templates/editParticleView.pug
@@ -2,31 +2,49 @@ div.mx-1
div
- h4.inline.mr-2 Type:
+ h5.inline.mr-2 Current Location:
- div.inline(data-target="type" data-hook=`particle-type-${this.viewIndex}`)
+ span.inline.mr-2 (x:
+
+ div.inline=this.model.point.x
+
+ span.inline.mr-2 , y:
+
+ div.inline=this.model.point.y
+
+ span.inline.mr-2 , z:
+
+ div.inline=this.model.point.z
+
+ span.inline )
div
- h4.inline.mr-2 Location:
+ h5.inline.mr-2 New Location:
div.inline.mr-2 (x:
- div.inline.ml2(data-target=`location-${this.viewIndex}` data-hook=`x-coord-${this.viewIndex}` data-index="0")
+ div.inline.ml-2(data-hook="new-point-x-container" data-target="new-point" data-name="x" style="width: 25%")
div.inline.mr-2 , y:
- div.inline.ml2(data-target=`location-${this.viewIndex}` data-hook=`y-coord-${this.viewIndex}` data-index="1")
+ div.inline.ml-2(data-hook="new-point-y-container" data-target="new-point" data-name="y" style="width: 25%")
div.inline.mr-2 , z:
- div.inline.ml2(data-target=`location-${this.viewIndex}` data-hook=`z-coord-${this.viewIndex}` data-index="2")
+ div.inline.ml-2(data-hook="new-point-z-container" data-target="new-point" data-name="z" style="width: 25%")
div.inline )
h4 Particle Properties
- div.pl-2(data-hook="particle-details")
+ div
+
+ h5.inline.mr-2 Type:
+
+ div.inline(data-hook="particle-type")
+
+ div
hr
@@ -60,24 +78,24 @@ div.mx-1
div.col-sm-2
- div(data-hook=`particle-mass-${this.viewIndex}`)
+ div.pl-2(data-hook="particle-mass" data-target="particle-property-containers")
div.col-sm-2
- div(data-hook=`particle-vol-${this.viewIndex}`)
+ div(data-hook="particle-vol" data-target="particle-property-containers")
div.col-sm-2
- div(data-hook=`particle-rho-${this.viewIndex}`)
+ div(data-hook="particle-rho" data-target="particle-property-containers")
div.col-sm-2
- div(data-hook=`particle-nu-${this.viewIndex}`)
+ div(data-hook="particle-nu" data-target="particle-property-containers")
div.col-sm-2
- div(data-hook=`particle-c-${this.viewIndex}`)
+ div(data-hook="particle-c" data-target="particle-property-containers")
div.col-sm-2
- input(type="checkbox" data-hook=`particle-fixed-${this.viewIndex}`)
\ No newline at end of file
+ input(type="checkbox" data-hook="particle-fixed" checked=this.model.fixed)
\ No newline at end of file
diff --git a/client/domain-view/templates/editShape.pug b/client/domain-view/templates/editShape.pug
new file mode 100644
index 0000000000..4f5c02e978
--- /dev/null
+++ b/client/domain-view/templates/editShape.pug
@@ -0,0 +1,138 @@
+div.mx-1
+
+ if(this.model.collection.indexOf(this.model) !== 0)
+ hr
+
+ div.row
+
+ div.col-sm-1
+
+ div.pl-3
+
+ input(type="checkbox" data-hook="select-shape" data-toggle="collapse" data-target="#edit-collapse-shape-details" + this.model.cid)
+
+ div.col-sm-2
+
+ div.pl-2(data-hook="input-name-container")
+
+ div.col-sm-2
+
+ div(data-hook="select-geometry-container")
+
+ div.col-sm-4
+
+ div(data-hook="input-formula-container")
+
+ div.col-sm-1
+
+ div.pl-3
+
+ input(type="checkbox" data-hook="fillable-shape")
+
+ div.col-sm-2
+
+ button.btn.btn-outline-secondary.box-shadow(data-hook="remove") X
+
+ div.mx-1.pl-2(data-hook="shape-details")
+
+ div.collapse(id="edit-collapse-shape-details" + this.model.cid data-hook="edit-collapse-shape-details" + this.model.cid)
+
+ hr
+
+ div.mx-1.mb-2
+
+ h6.inline.mr-3 Lattice:
+
+ div.inline(data-hook="select-lattice-container")
+
+ div.hidden(data-hook="cartesian-lattice-props")
+
+ div.mx-1.row.head.align-items-baseline
+
+ div.col-sm-2
+
+ h6.inline Length
+
+ div.col-sm-2
+
+ h6.inline Height
+
+ div.col-sm-2
+
+ h6.inline Depth
+
+ div.col-sm-2
+
+ h6.inline X-Spacing
+
+ div.col-sm-2
+
+ h6.inline Y-Spacing
+
+ div.col-sm-2
+
+ h6.inline Z-Spacing
+
+ div.mt-3.mx-1.row
+
+ div.col-sm-2
+
+ div(data-hook="length-container" data-target="shape-property")
+
+ div.col-sm-2
+
+ div(data-hook="height-container" data-target="shape-property")
+
+ div.col-sm-2
+
+ div(data-hook="depth-container" data-target="shape-property")
+
+ div.col-sm-2
+
+ div(data-hook="delta-x-container" data-target="shape-property")
+
+ div.col-sm-2
+
+ div(data-hook="delta-y-container" data-target="shape-property")
+
+ div.col-sm-2
+
+ div(data-hook="delta-z-container" data-target="shape-property")
+
+ div.hidden(data-hook="circular-lattice-props")
+
+ div.mx-1.row.head.align-items-baseline
+
+ div.col-sm-3
+
+ h6.inline Radius
+
+ div.col-sm-3.hidden(data-hook="cylinder-length-header")
+
+ h6.inline length
+
+ div.col-sm-3
+
+ h6.inline Surface Spacing
+
+ div.col-sm-3
+
+ h6.inline Radial Spacing
+
+ div.mt-3.mx-1.row
+
+ div.col-sm-3
+
+ div(data-hook="radius-container" data-target="shape-property")
+
+ div.col-sm-3.hidden(data-hook="cylinder-length-feild")
+
+ div(data-hook="cylinder-length-container" data-target="shape-property")
+
+ div.col-sm-3
+
+ div(data-hook="delta-s-container" data-target="shape-property")
+
+ div.col-sm-3
+
+ div(data-hook="delta-r-container" data-target="shape-property")
diff --git a/client/domain-view/templates/editTransformation.pug b/client/domain-view/templates/editTransformation.pug
new file mode 100644
index 0000000000..fafab99ea8
--- /dev/null
+++ b/client/domain-view/templates/editTransformation.pug
@@ -0,0 +1,264 @@
+div.mx-1
+
+ if(this.model.collection.indexOf(this.model) !== 0)
+ hr
+
+ div.row
+
+ div.col-sm-1
+
+ div.pl-3
+
+ input(type="checkbox" data-hook="select-transformation" data-toggle="collapse" data-target="#collapse-transformation-details" + this.model.cid)
+
+ div.col-sm-3
+
+ div.pl-2(data-hook="input-name-container")
+
+ div.col-sm-6
+
+ div.pl-2(data-hook="select-type-container")
+
+ div.col-sm-2
+
+ button.btn.btn-outline-secondary.box-shadow(data-hook="remove") X
+
+ div-mx-1.pl-2(data-hook="transformation-details")
+
+ div.collapse(id="collapse-transformation-details" + this.model.cid)
+
+ div
+
+ h6.inline.mr-3 Nested Transformation:
+
+ div.inline(data-hook="transformation-select-container")
+
+ div.hidden(data-hook="vector-transformation-props")
+
+ hr
+
+ div.mx-1.row.head.align-items-baseline
+
+ div.col-sm-9
+
+ h6 Vector
+
+ div.col-sm-3.hidden(data-hook="rotation-angle-header")
+
+ h6 Angle (deg)
+
+ div.mx-1.mt-3.row
+
+ div.col-sm-9
+
+ div
+
+ span.inline [(x:
+
+ div.inline(data-hook="vector-point1-x-container" data-target="vector-point1" data-name="x" style="width: 25%")
+
+ span.inline.mr-2 , y:
+
+ div.inline(data-hook="vector-point1-y-container" data-target="vector-point1" data-name="y" style="width: 25%")
+
+ span.inline.mr-2 , z:
+
+ div.inline(data-hook="vector-point1-z-container" data-target="vector-point1" data-name="z" style="width: 25%")
+
+ span.inline ),
+
+ div.pl-2
+
+ span.inline (x:
+
+ div.inline(data-hook="vector-point2-x-container" data-target="vector-point2" data-name="x" style="width: 25%")
+
+ span.inline.mr-2 , y:
+
+ div.inline(data-hook="vector-point2-y-container" data-target="vector-point2" data-name="y" style="width: 25%")
+
+ span.inline.mr-2 , z:
+
+ div.inline(data-hook="vector-point2-z-container" data-target="vector-point2" data-name="z" style="width: 25%")
+
+ span.inline )]
+
+ div.col-sm-3.hidden(data-hook="rotation-angle-prop")
+
+ div(data-hook="rotation-angle-container")
+
+ div.accordion.hidden(id="reflectProps" + this.model.cid data-hook="reflect-transformation-props")
+
+ hr
+
+ div.mx-1.my-3.row.align-items-baseline
+
+ div.col-sm-2
+
+ h6 Point 1
+
+ div.col-sm-10
+
+ div
+
+ span.inline (x:
+
+ div.inline(data-hook="point1-x-container" data-target="reflect-point1" data-name="x" style="width: 25%")
+
+ span.inline.mr-2 , y:
+
+ div.inline(data-hook="point1-y-container" data-target="reflect-point1" data-name="y" style="width: 25%")
+
+ span.inline.mr-2 , z:
+
+ div.inline(data-hook="point1-z-container" data-target="reflect-point1" data-name="z" style="width: 25%")
+
+ span.inline )
+
+ div.card
+
+ div.card-header(id="pointNormalHeader" + this.model.cid)
+
+ h3.mb-0.inline Point Normal Method
+
+ button.btn.btn-outline-collapse.collapsed.inline(type="button" data-toggle="collapse" data-target="#collapsePointNormal" + this.model.cid data-hook="collapsePointNormal" aria-expanded="false" aria-controls="collapsePointNormal" style="float: right")
+
+ div(data-hook="point-normal-chevron")
+
+ svg(xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 512 512")
+
+ path(d="M233.4 406.6c12.5 12.5 32.8 12.5 45.3 0l192-192c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L256 338.7 86.6 169.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l192 192z")
+
+ div.collapse(id="collapsePointNormal" + this.model.cid aria-labelledby="pointNormalHeader" + this.model.cid data-parent="#reflectProps" + this.model.cid)
+
+ div.card-body.px-0
+
+ div.mx-1.mt-3.row.align-items-baseline
+
+ div.col-sm-2
+
+ h6 Normal
+
+ div.col-sm-10
+
+ div
+
+ span.inline (x:
+
+ div.inline(data-hook="normal-x-container" data-target="reflect-normal" data-name="x" style="width: 25%")
+
+ span.inline.mr-2 , y:
+
+ div.inline(data-hook="normal-y-container" data-target="reflect-normal" data-name="y" style="width: 25%")
+
+ span.inline.mr-2 , z:
+
+ div.inline(data-hook="normal-z-container" data-target="reflect-normal" data-name="z" style="width: 25%")
+
+ span.inline )
+
+ div.card
+
+ div.card-header(id="threePointHeader" + this.model.cid)
+
+ h3.mb-0.inline Three Point Method
+
+ button.btn.btn-outline-collapse.collapsed.inline(type="button" data-toggle="collapse" data-target="#collapseThreePoint" + this.model.cid data-hook="collapseThreePoint" aria-expanded="false" aria-controls="collapseThreePoint" style="float: right")
+
+ div(data-hook="three-point-chevron")
+
+ svg(xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 512 512")
+
+ path(d="M233.4 406.6c12.5 12.5 32.8 12.5 45.3 0l192-192c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L256 338.7 86.6 169.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l192 192z")
+
+ div.collapse(id="collapseThreePoint" + this.model.cid aria-labelledby="threePointHeader" + this.model.cid data-parent="#reflectProps" + this.model.cid)
+
+ div.card-body.px-0
+
+ div.mx-1.mt-3.row.align-items-baseline
+
+ div.col-sm-2
+
+ h6 Point 2
+
+ div.col-sm-10
+
+ div
+
+ span.inline (x:
+
+ div.inline(data-hook="point2-x-container" data-target="reflect-point2" data-name="x" style="width: 25%")
+
+ span.inline.mr-2 , y:
+
+ div.inline(data-hook="point2-y-container" data-target="reflect-point2" data-name="y" style="width: 25%")
+
+ span.inline.mr-2 , z:
+
+ div.inline(data-hook="point2-z-container" data-target="reflect-point2" data-name="z" style="width: 25%")
+
+ span.inline )
+
+ hr
+
+ div.mx-1.mt-3.row.align-items-baseline
+
+ div.col-sm-2
+
+ h6 Point 3
+
+ div.col-sm-10
+
+ div
+
+ span.inline (x:
+
+ div.inline(data-hook="point3-x-container" data-target="reflect-point3" data-name="x" style="width: 25%")
+
+ span.inline.mr-2 , y:
+
+ div.inline(data-hook="point3-y-container" data-target="reflect-point3" data-name="y" style="width: 25%")
+
+ span.inline.mr-2 , z:
+
+ div.inline(data-hook="point3-z-container" data-target="reflect-point3" data-name="z" style="width: 25%")
+
+ span.inline )
+
+ div.hidden(data-hook="scale-transformation-props")
+
+ hr
+
+ div.mx-1.row.head.align-items-baseline
+
+ div.col-sm-3
+
+ h6 Factor
+
+ div.col-sm-9
+
+ h6 Center
+
+ div.mx-1.mt-3.row
+
+ div.col-sm-3
+
+ div(data-hook="scale-factor-container")
+
+ div.col-sm-9
+
+ div
+
+ span.inline (x:
+
+ div.inline(data-hook="center-x-container" data-target="scale-center" data-name="x" style="width: 25%")
+
+ span.inline.mr-2 , y:
+
+ div.inline(data-hook="center-y-container" data-target="scale-center" data-name="y" style="width: 25%")
+
+ span.inline.mr-2 , z:
+
+ div.inline(data-hook="center-z-container" data-target="scale-center" data-name="z" style="width: 25%")
+
+ span.inline )
diff --git a/client/domain-view/templates/editType.pug b/client/domain-view/templates/editType.pug
index 0c630182e8..f20f091483 100644
--- a/client/domain-view/templates/editType.pug
+++ b/client/domain-view/templates/editType.pug
@@ -11,31 +11,17 @@ div.mx-1
input(type="checkbox" data-hook="select" data-toggle="collapse" data-target="#collapse-type-details" + this.model.typeID)
- div.col-sm-3
+ div.col-sm-5
div(data-hook="type-name" data-target=this.model.typeID)
- div.col-sm-3
+ div.col-sm-4
div=this.model.numParticles
- div.col-sm-3
-
- button.btn.btn-outline-secondary.box-shadow(data-type=this.model.typeID data-hook='unassign-all') Un-Assign Particles
-
div.col-sm-2
- button.btn.btn-outline-secondary.box-shadow.dropdown-toggle(
- id="delete-domain-type",
- data-toggle="dropdown",
- aria-haspopup="true",
- aria-expanded="false",
- type="button"
- ) Delete
-
- ul.dropdown-menu(aria-labelledby="delete-domain-type")
- li.dropdown-item(data-hook="delete-type" data-type=this.model.typeID) Type
- li.dropdown-item(data-hook="delete-all" data-type=this.model.typeID) Type and Particles
+ button.btn.btn-outline-secondary.box-shadow(data-hook="remove") X
div-mx-1.pl-2(data-hook="type-details")
@@ -94,35 +80,3 @@ div.mx-1
div.col-sm-2
input(type="checkbox" data-hook="td-fixed")
-
- hr
-
- div.row.align-items-baseline
-
- div.col-sm-10.align-items-baseline
-
- h6.inline Geometry:
-
- div.tooltip-icon.mr-3(data-html="true" data-toggle="tooltip" title=this.tooltips.geometry)
-
- div.inline(style="width: 80%")
-
- div(id="type-geometry" data-hook="type-geometry")
-
- div.col-sm-2
-
- button.btn.btn-outline-secondary.box-shadow(data-hook="apply-geometry") Apply
-
- div.mdl-edit-btn.saving-status.inline(data-hook="tg-in-progress-" + this.model.typeID)
-
- div.spinner-grow.mr-2
-
- span Applying type to particles ...
-
- div.mdl-edit-btn.saved-status.inline(data-hook="tg-complete-" + this.model.typeID)
-
- span Type successfully applied
-
- div.mdl-edit-btn.save-error-status(data-hook="tg-error-" + this.model.typeID)
-
- span(data-hook="tg-action-error-" + this.model.typeID)
diff --git a/client/domain-view/templates/fillGeometryView.pug b/client/domain-view/templates/fillGeometryView.pug
deleted file mode 100644
index e9f2400326..0000000000
--- a/client/domain-view/templates/fillGeometryView.pug
+++ /dev/null
@@ -1,167 +0,0 @@
-div.mx-1
-
- div
-
- h4.inline.mr-2 Type:
-
- div.inline(data-hook="fill-geometry-type-select")
-
- div
-
- h4.inline.mr-2 Geometry:
-
- div.inline(data-hook="fill-geometry-type-geometry")
-
- div.mt-3
-
- h5 Type Defaults
-
- hr
-
- div.mb-3.mx-1.row.head.align-items-baseline
-
- div.col-sm-2
-
- h6.inline Mass
-
- div.col-sm-2
-
- h6.inline Volume
-
- div.col-sm-2
-
- h6.inline Density
-
- div.col-sm-2
-
- h6.inline Viscosity
-
- div.col-sm-2
-
- h6.inline Speed of Sound
-
- div.col-sm-2
-
- h6.inline Fixed
-
- div.row.pl-3
-
- div.col-sm-2
-
- div(data-hook="fill-geometry-td-mass")
-
- div.col-sm-2
-
- div(data-hook="fill-geometry-td-vol")
-
- div.col-sm-2
-
- div(data-hook="fill-geometry-td-rho")
-
- div.col-sm-2
-
- div(data-hook="fill-geometry-td-nu")
-
- div.col-sm-2
-
- div(data-hook="fill-geometry-td-c")
-
- div.col-sm-2
-
- input(type="checkbox" data-hook="fill-geometry-td-fixed" disabled)
-
- h4.mt-3 Domain Limits
-
- div
-
- div.mx-1.row.head.align-items-baseline
-
- div.col-sm-3
-
- div.col-sm-3
-
- h6.inline X-Axis
-
- div.col-sm-3
-
- h6.inline Y-Axis
-
- div.col-sm-3
-
- h6.inline Z-Axis
-
- div.mt-3.mx-1.row
-
- div.col-sm-3
-
- h6.inline Minimum
-
- div.col-sm-3
-
- div(data-hook="fill-geometry-x-lim-min" data-target="fill-geometry-limitation" data-name="xmin")
-
- div.col-sm-3
-
- div(data-hook="fill-geometry-y-lim-min" data-target="fill-geometry-limitation" data-name="ymin")
-
- div.col-sm-3
-
- div(data-hook="fill-geometry-z-lim-min" data-target="fill-geometry-limitation" data-name="zmin")
-
- hr
-
- div.mx-1.row
-
- div.col-sm-3
-
- h6.inline Maximum
-
- div.col-sm-3
-
- div(data-hook="fill-geometry-x-lim-max" data-target="fill-geometry-limitation" data-name="xmax")
-
- div.col-sm-3
-
- div(data-hook="fill-geometry-y-lim-max" data-target="fill-geometry-limitation" data-name="ymax")
-
- div.col-sm-3
-
- div(data-hook="fill-geometry-z-lim-max" data-target="fill-geometry-limitation" data-name="zmax")
-
- hr
-
- div.mx-1.row
-
- div.col-sm-3
-
- h6.inline Spacing
-
- div.col-sm-3
-
- div(data-hook="fill-geometry-deltax" data-target="fill-geometry-delta" data-name="deltax")
-
- div.col-sm-3
-
- div(data-hook="fill-geometry-deltay" data-target="fill-geometry-delta" data-name="deltay")
-
- div.col-sm-3
-
- div(data-hook="fill-geometry-deltaz" data-target="fill-geometry-delta" data-name="deltaz")
-
- div.inline
-
- button.btn.btn-outline-primary(data-hook="fill-geometry" disabled) Fill with Particles
-
- div.mdl-edit-btn.saving-status.inline(data-hook="fg-in-progress")
-
- div.spinner-grow.mr-2
-
- span Creating particles ...
-
- div.mdl-edit-btn.saved-status.inline(data-hook="fg-complete")
-
- span Particles successfully created
-
- div.mdl-edit-btn.save-error-status(data-hook="fg-error")
-
- span(data-hook="fg-action-error")
diff --git a/client/domain-view/templates/importMeshView.pug b/client/domain-view/templates/importMeshView.pug
deleted file mode 100644
index 000a38acc3..0000000000
--- a/client/domain-view/templates/importMeshView.pug
+++ /dev/null
@@ -1,137 +0,0 @@
-div.mx-1
-
- div.my-3
-
- span.inline.mr-2(for="meshfile") Please specify a mesh to import:
-
- input(id="meshfile" type="file" name="meshfile" size="30" accept=".xml" required)
-
- div.mb-3
-
- span.inline.mr-2(for=typefile) Type descriptions (optional):
-
- input(id="typefile" type="file" name="typefile" size="30" accept=".txt")
-
- div.mt-3
-
- h4.inline Advanced
-
- button.btn.btn-outline-collapse(data-toggle="collapse" data-target="#import-mesh-advanced" data-hook="collapse-import-mesh-advanced") +
-
- div.collapse(id="import-mesh-advanced" data-hook="import-mesh-container")
-
- div.mt-3
-
- h5.inline.mr-2 Type:
-
- div.inline(data-hook="import-mesh-type-select")
-
- div.mt-3
-
- h5 Type Defaults
-
- hr
-
- div.mb-3.mx-1.row.head.align-items-baseline
-
- div.col-sm-2
-
- h6.inline Mass
-
- div.col-sm-2
-
- h6.inline Volume
-
- div.col-sm-2
-
- h6.inline Density
-
- div.col-sm-2
-
- h6.inline Viscosity
-
- div.col-sm-2
-
- h6.inline Speed of Sound
-
- div.col-sm-2
-
- h6.inline Fixed
-
- div.row.pl-3
-
- div.col-sm-2
-
- div(data-hook="import-mesh-td-mass")
-
- div.col-sm-2
-
- div(data-hook="import-mesh-td-vol")
-
- div.col-sm-2
-
- div(data-hook="import-mesh-td-rho")
-
- div.col-sm-2
-
- div(data-hook="import-mesh-td-nu")
-
- div.col-sm-2
-
- div(data-hook="import-mesh-td-c")
-
- div.col-sm-2
-
- input(type="checkbox" data-hook="import-mesh-td-fixed" disabled)
-
- div.my-3
-
- h5 Particle Transformations
-
- hr
-
- div.mb-3.mx-1.row.head.align-items-baseline
-
- div.col-sm-4
-
- h6.inline X-Axis
-
- div.col-sm-4
-
- h6.inline Y-Axis
-
- div.col-sm-4
-
- h6.inline Z-Axis
-
- div.row
-
- div.col-sm-4
-
- div(data-target="import-mesh-trans" data-hook="import-mesh-x-trans" data-index="0")
-
- div.col-sm-4
-
- div(data-target="import-mesh-trans" data-hook="import-mesh-y-trans" data-index="1")
-
- div.col-sm-4
-
- div(data-target="import-mesh-trans" data-hook="import-mesh-z-trans" data-index="2")
-
- div.inline
-
- button.btn.btn-outline-primary.box-shadow(data-hook="import-mesh-particles" disabled) Import Mesh
-
- div.mdl-edit-btn.saving-status.inline(data-hook="imp-in-progress")
-
- div.spinner-grow.mr-2
-
- span Importing mesh ...
-
- div.mdl-edit-btn.saved-status.inline(data-hook="imp-complete")
-
- span
-
- div.mdl-edit-btn.save-error-status(data-hook="imp-error")
-
- span(data-hook="imp-action-error")
\ No newline at end of file
diff --git a/client/domain-view/templates/limitsView.pug b/client/domain-view/templates/limitsView.pug
index 3fb1868a60..c339118baa 100644
--- a/client/domain-view/templates/limitsView.pug
+++ b/client/domain-view/templates/limitsView.pug
@@ -16,11 +16,14 @@ div#domain-limits-editor.card
a.nav-link.tab(data-hook="domain-limits-view-tab" data-toggle="tab" href="#view-domain-limits") View
- button.btn.btn-ouline-collapse(data-toggle="collapse" data-target="#domain-limits-section" data-hook="collapse-domain-limits") -
+ button.btn.btn-ouline-collapse(data-toggle="collapse" data-target="#domain-limits-section" data-hook="collapse-domain-limits") +
- div.collapse.show(id="domain-limits-section" data-hook="domain-limits-section")
+ div.card-body
- div.card-body.tab-content
+ p.mb-0
+ | Visualization limits for the domain.
+
+ div.collapse.tab-content(id="domain-limits-section" data-hook="domain-limits-section")
div.tab-pane.active(id="edit-domain-limits" data-hook="edit-domain-limits")
diff --git a/client/domain-view/templates/propertiesView.pug b/client/domain-view/templates/propertiesView.pug
index b6bc871a94..90d806f91c 100644
--- a/client/domain-view/templates/propertiesView.pug
+++ b/client/domain-view/templates/propertiesView.pug
@@ -18,37 +18,39 @@ div#domain-properties-editor.card
button.btn.btn-outline-collapse(data-toggle="collapse" data-target="#domain-properties-section" data-hook="collapse-domain-properties") -
- div.collapse.show(id="domain-properties-section" data-hook="domain-properties-section")
+ div.card-body
+ p.mb-0
+ | Set system wide domain properties/initial values.
- div.card-body.tab-content
+ div.collapse.tab-content.show(id="domain-properties-section" data-hook="domain-properties-section")
div.tab-pane.active(id="edit-domain-properties" data-hook="edit-domain-properties")
-
- div
-
- span.mr-3(for="#static-domain") Static Domain:
-
- input(type="checkbox" id="static-domain" data-hook="static-domain")
hr
div.mx-1.row.head.align-items-baseline
- div.col-sm-3
+ div.col-sm-1
+
+ h6.inline Static Domain
+
+ div.tooltip-icon(data-html="true" data-toggle="tooltip" title=this.tooltips.staticDomain)
+
+ div.col-sm-2
h6.inline Density
- div.col-sm-3
+ div.col-sm-5
h6.inline Gravity
- div.col-sm-3
+ div.col-sm-2
h6.inline Pressure
div.tooltip-icon(data-html="true" data-toggle="tooltip" title=this.tooltips.pressure)
- div.col-sm-3
+ div.col-sm-2
h6.inline Speed of Sound
@@ -56,68 +58,82 @@ div#domain-properties-editor.card
div.mt-3.mx-1.row
- div.col-sm-3
+ div.col-sm-1
+
+ input(type="checkbox" id="static-domain" data-hook="static-domain")
+
+ div.col-sm-2
div(data-hook="density")
- div.col-sm-3
+ div.col-sm-5
- div(data-target="gravity" data-hook="gravity-x" data-index="0")
-
- div(data-target="gravity" data-hook="gravity-y" data-index="1")
-
- div(data-target="gravity" data-hook="gravity-z" data-index="2")
+ span.inline.mr-2 (x:
- div.col-sm-3
+ div.inline(data-target="gravity" data-hook="gravity-x" data-index="0" style="width: 25%")
- div(data-hook="pressure")
+ span.inline.mr-2 , y:
- div.col-sm-3
+ div.inline(data-target="gravity" data-hook="gravity-y" data-index="1" style="width: 25%")
- div(data-hook="speed")
+ span.inline.mr-2 , z:
- div.tab-pane(id="view-domain-properties" data-hook="view-domain-properties")
+ div.inline(data-target="gravity" data-hook="gravity-z" data-index="2" style="width: 25%")
+
+ span.inline )
+
+ div.col-sm-2
- div
+ div(data-hook="pressure")
- span.mr-3(for="#view-static-domain") Static Domain:
+ div.col-sm-2
- input(type="checkbox" id="view-static-domain" data-hook="view-static-domain" disabled)
+ div(data-hook="speed")
+
+ div.tab-pane(id="view-domain-properties" data-hook="view-domain-properties")
hr
div.mx-1.row.head.align-items-baseline
- div.col-sm-3
+ div.col-sm-1
+
+ h6.inline Static Domain
+
+ div.col-sm-2
h6.inline Density
- div.col-sm-3
+ div.col-sm-5
h6.inline Gravity
- div.col-sm-3
+ div.col-sm-2
h6.inline Pressure
- div.col-sm-3
+ div.col-sm-2
h6.inline Speed of Sound
div.mt-3.mx-1.row
- div.col-sm-3
+ div.col-sm-1
+
+ input(type="checkbox" id="view-static-domain" data-hook="view-static-domain" disabled)
+
+ div.col-sm-2
div=this.model.rho_0
- div.col-sm-3
+ div.col-sm-5
div=`(X: ${this.model.gravity[0]}, Y: ${this.model.gravity[1]}, Z: ${this.model.gravity[2]})`
- div.col-sm-3
+ div.col-sm-2
div=this.model.p_0
- div.col-sm-3
+ div.col-sm-2
div=this.model.c_0
\ No newline at end of file
diff --git a/client/domain-view/templates/shapesView.pug b/client/domain-view/templates/shapesView.pug
new file mode 100644
index 0000000000..7939c2a959
--- /dev/null
+++ b/client/domain-view/templates/shapesView.pug
@@ -0,0 +1,98 @@
+div#domain-geometries-editor.card
+
+ div.card-header.pb-0
+
+ h3.inline.mr-3 Shapes
+
+ div.inline.mr-3
+
+ ul.nav.nav-tabs.card-header-tabs(id="shapes-section-tabs")
+
+ li.nav-item
+
+ a.nav-link.tab.active(data-hook="shapes-edit-tab" data-toggle="tab" href="#edit-shapes") Edit
+
+ li.nav-item
+
+ a.nav-link.tab(data-hook="shapes-view-tab" data-toggle="tab" href="#view-shapes") View
+
+ button.btn.btn-outline-collapse.inline(data-toggle="collapse" data-target="#collapse-shapes" data-hook="collapse") +
+
+ div.card-body
+ p.mb-0
+ | Shapes to define regions in space. Formulas define a boolean expression that evaluates as True when inside the region of space, False otherwise.
+
+ ul.mb-0
+ li Formulas for 'Standard Geometry' are in terms of the variables 'x', 'y', and 'z'.
+ li Formulas for 'Combinatory Geometry' are in terms of other 'Shapes'.
+ li To use a 'Shape' in a 'Fill Action' 'Fillable' must be checked.
+ li All 'Shapes' may be used in 'Set Actions', 'Remove Actions', or in a 'Combinatory Geometry' formula.
+
+ div.collapse.tab-content(id="collapse-shapes" data-hook="shapes-list-container")
+
+ div.tab-pane.active(id="edit-shapes" data-hook="edit-shapes")
+
+ hr
+
+ div.mb-3.mx-1.row.head.align-items-baseline
+
+ div.col-sm-1
+
+ h6.inline Edit
+
+ div.col-sm-2
+
+ h6.inline Name
+
+ div.tooltip-icon.mr-3(data-html="true" data-toggle="tooltip" title=this.tooltips.name)
+
+ div.col-sm-2
+
+ h6.inline Geometry
+
+ div.col-sm-4
+
+ h6.inline Formula
+
+ div.tooltip-icon.mr-3(data-html="true" data-toggle="tooltip" title=this.tooltips.geometry)
+
+ div.col-sm-1
+
+ h6.inline Fillable
+
+ div.col-sm-2
+
+ h6.inline Remove
+
+ div.my-3(data-hook="edit-shapes-list")
+
+ div.dropdown
+
+ button.btn.btn-outline-primary.dropdown-toggle.box-shadow#addShapeBtn(
+ data-hook="add-shape",
+ data-toggle="dropdown",
+ aria-haspopup="true",
+ aria-expanded="false",
+ type="button"
+ ) Add Shape
+
+ ul.dropdown-menu(aria-labelledby="addShapeBtn")
+ li.dropdown-item(data-hook="cartesian-lattice" data-name="Cartesian Lattice") Cartesian Lattice
+ li.dropdown-item(data-hook="spherical-lattice" data-name="Spherical Lattice") Spherical Lattice
+ li.dropdown-item(data-hook="cylindrical-lattice" data-name="Cylindrical Lattice") Cylindrical Lattice
+
+ div.tab-pane(id="view-shapes" data-hook="view-shapes")
+
+ hr
+
+ div.mx-1.row.head.align-items-baseline
+
+ div.col-sm-2: h6 Name
+
+ div.col-sm-2: h6 Geometry
+
+ div.col-sm-7: h6 Formula
+
+ div.col-sm-1: h6 Fillable
+
+ div.mt-3(data-hook="view-shapes-list")
diff --git a/client/domain-view/templates/transformationsView.pug b/client/domain-view/templates/transformationsView.pug
new file mode 100644
index 0000000000..b9f1eadb47
--- /dev/null
+++ b/client/domain-view/templates/transformationsView.pug
@@ -0,0 +1,82 @@
+div#domain-transformation-editor.card
+
+ div.card-header.pb-0
+
+ h3.inline.mr-3 Transformations
+
+ div.inline.mr-3
+
+ ul.nav.nav-tabs.card-header-tabs(id="transformations-selection-tabs")
+
+ li.nav-item
+
+ a.nav-link.tab.active(data-hook="transformations-edit-tab" data-toggle="tab" href="#edit-transformations") Edit
+
+ li.nav-item
+
+ a.nav-link.tab(data-hook="transformations-view-tab" data-toggle="tab" href="#view-transformations") View
+
+ button.btn.btn-outline-collapse.inline(data-toggle="collapse" data-target="#collapse-transformations" data-hook="collapse") +
+
+ div.card-body
+
+ p.mb-0
+ | Transformations define a method of manipulation to be performed on a region within the domain defined by a Shape.
+
+ div.collapse.tab-content(id="collapse-transformations" data-hook="transformations-list-container")
+
+ div.tab-pane.active(id="edit-transformations" data-hook="edit-transformations")
+
+ hr
+
+ div.mb-3.mx-1.row.head.align-items-baseline
+
+ div.col-sm-1
+
+ h6.inline Edit
+
+ div.col-sm-3
+
+ h6.inline Name
+
+ div.tooltip-icon.mr-3(data-html="true" data-toggle="tooltip" title=this.tooltips.name)
+
+ div.col-sm-6
+
+ h6.inline Type
+
+ div.col-sm-2
+
+ h6.inline Remove
+
+ div.my-3(data-hook="edit-transformations-list")
+
+ div.dropdown
+
+ button.btn.btn-outline-primary.dropdown-toggle.box-shadow#addLatticeBtn(
+ data-hook="add-transformation",
+ data-toggle="dropdown",
+ aria-haspopup="true",
+ aria-expanded="false",
+ type="button"
+ ) Add Transformation
+
+ ul.dropdown-menu(aria-labelledby="addLatticeBtn")
+ li.dropdown-item(data-hook="translate-transformation" data-name="Translate Transformation") Translate Transformation
+ li.dropdown-item(data-hook="rotate-transformation" data-name="Rotate Transformation") Rotate Transformation
+ li.dropdown-item(data-hook="reflect-transformation" data-name="Reflect Transformation") Reflect Transformation
+ li.dropdown-item(data-hook="scale-transformation" data-name="Scale Transformation") Scale Transformation
+
+ div.tab-pane(id="view-transformations" data-hook="view-transformations")
+
+ hr
+
+ div.mx-1.row.head.align-items-baseline
+
+ div.col-sm-4: h6 Name
+
+ div.col-sm-4: h6 Type
+
+ div.col-sm-4: h6 Nested Transformation
+
+ div.mt-3(data-hook="view-transformations-list")
diff --git a/client/domain-view/templates/typesDescriptionView.pug b/client/domain-view/templates/typesDescriptionView.pug
deleted file mode 100644
index f4f2ab190e..0000000000
--- a/client/domain-view/templates/typesDescriptionView.pug
+++ /dev/null
@@ -1,36 +0,0 @@
-div.mx-1
-
- div.text-info(data-hook="type-location-message" style="display: none")
- | There are multiple type files with that name, please select a location
-
- div
-
- div.inline.mr-3
-
- span.inline.mr-2(for="file-select") Select type description file:
-
- div.inline(id="file-select" data-hook="file-select")
-
- div.inline(data-hook="file-location-container" style="display: none")
-
- span.inlinemr-2(for="file-location-select") Location:
-
- div.inline(id="file-location-select" data-hook="file-location-select")
-
- div.my-3
-
- button.btn.btn-outline-primary.box-shadow(data-hook="set-particle-types-btn" disabled) Set Types
-
- div.mdl-edit-btn.saving-status.inline(data-hook="st-in-progress")
-
- div.spinner-grow.mr-2
-
- span Setting types ...
-
- div.mdl-edit-btn.saved-status.inline(data-hook="st-complete")
-
- span
-
- div.mdl-edit-btn.save-error-status(data-hook="st-error")
-
- span(data-hook="st-error-action")
\ No newline at end of file
diff --git a/client/domain-view/templates/typesView.pug b/client/domain-view/templates/typesView.pug
index d51f270214..442bc5459a 100644
--- a/client/domain-view/templates/typesView.pug
+++ b/client/domain-view/templates/typesView.pug
@@ -16,11 +16,15 @@ div#domain-types-editor.card
a.nav-link.tab(data-hook="domain-types-view-tab" data-toggle="tab" href="#view-domain-types") View
- button.btn.btn-outline-collapse(data-toggle="collapse" data-target="#domain-types-section" data-hook="collapse-domain-types") -
+ button.btn.btn-outline-collapse(data-toggle="collapse" data-target="#domain-types-section" data-hook="collapse-domain-types") +
- div.collapse.show(id="domain-types-section" data-hook="domain-types-section")
+ div.card-body
+ p.mb-0
+ | Classifications of matter within the domain. Types can have different properties (mass, volume, etc...), and can be set as movable or Fixed.
- div.card-body.tab-content
+ div.collapse.tab-content(id="domain-types-section" data-hook="domain-types-section")
+
+ hr
div
@@ -42,16 +46,24 @@ div#domain-types-editor.card
h6.inline Edit
- div.col-sm-3
+ div.col-sm-5
h6.inline Name
- div.col-sm-8
+ div.col-sm-4
h6.inline Number of Particles
+ div.col-sm-2
+
+ h6.inline Remove
+
div.my-3(data-hook="edit-domain-types-list")
+ div(data-hook="ev-types-collection-error")
+
+ p.text-danger Domains must have at least one type for matter classification.
+
button.btn.btn-outline-primary.box-shadow(data-hook="add-domain-type") Add Type
div.tab-pane(id="view-domain-types" data-hook="view-domain-types")
@@ -66,10 +78,14 @@ div#domain-types-editor.card
div.col-sm-3
- h6.inline Numbe of Particles
+ h6.inline Number of Particles
div.col-sm-6
h6.inline Defaults
- div.my-3(data-hook="view-domain-types-list")
\ No newline at end of file
+ div.my-3(data-hook="view-domain-types-list")
+
+ div(data-hook="vv-types-collection-error")
+
+ p.text-danger Domains must have at least one type for matter classification.
\ No newline at end of file
diff --git a/client/domain-view/templates/viewAction.pug b/client/domain-view/templates/viewAction.pug
new file mode 100644
index 0000000000..51bc203ff4
--- /dev/null
+++ b/client/domain-view/templates/viewAction.pug
@@ -0,0 +1,160 @@
+div.mx-1
+
+ if(this.model.collection.indexOf(this.model) !== 0)
+ hr
+
+ div.row
+
+ div.col-sm-4
+
+ div.pl-2=this.model.type
+
+ div.col-sm-4
+
+ div.pl-2=this.model.priority
+
+ div.col-sm-4
+
+ input(type="checkbox" checked=this.model.enable disabled)
+
+ hr
+
+ div
+
+ div.mb-3.mx-1.row.align-items-baseline
+
+ div.col-sm-4.pl-0.hidden(data-hook="action-scope")
+
+ h5.inline.mr-2 Scope:
+
+ div.inline=this.model.scope
+
+ div.col-sm-4.hidden(data-hook="multi-particle-shape")
+
+ h5.inline.mr-2 Shape:
+
+ div.inline=this.model.shape ? this.model.shape : 'None'
+
+ div.col-sm-4.hidden(data-hook="multi-particle-transformation")
+
+ h5.inline.mr-2 Transformation:
+
+ div.inline=this.model.transformation ? this.model.transformation : 'None'
+
+ div.hidden(data-hook="single-particle-scope")
+
+ div
+
+ h5.inline.mr-2 Current Location:
+
+ span.inline.mr-2 (x:
+
+ div.inline=this.model.point.x
+
+ span.inline.mr-2 , y:
+
+ div.inline=this.model.point.y
+
+ span.inline.mr-2 , z:
+
+ div.inline=this.model.point.z
+
+ span.inline )
+
+ div.hidden(data-hook="new-location")
+
+ h5.inline.mr-2 New Location:
+
+ span.inline.mr-2 (x:
+
+ div.inline=this.model.newPoint.x
+
+ span.inline.mr-2 , y:
+
+ div.inline=this.model.newPoint.y
+
+ span.inline.mr-2 , z:
+
+ div.inline=this.model.newPoint.z
+
+ span.inline )
+
+ div.hidden(data-hook="particle-properties")
+
+ h4 Particle Properties
+
+ div
+
+ h5.inline.mr-2 Type:
+
+ div.inline=this.model.collection.parent.types.get(this.model.typeID, 'typeID').name
+
+ div
+
+ hr
+
+ div.mb-3.mx-1.row.head.align-items-baseline
+
+ div.col-sm-2
+
+ h6.inline Mass
+
+ div.col-sm-2
+
+ h6.inline Volume
+
+ div.col-sm-2
+
+ h6.inline Density
+
+ div.col-sm-2
+
+ h6.inline Viscosity
+
+ div.col-sm-2
+
+ h6.inline Speed of Sound
+
+ div.col-sm-2
+
+ h6.inline Fixed
+
+ div.row
+
+ div.col-sm-2
+
+ div.pl-2=this.model.mass
+
+ div.col-sm-2
+
+ div=this.model.vol
+
+ div.col-sm-2
+
+ div=this.model.rho
+
+ div.col-sm-2
+
+ div=this.model.nu
+
+ div.col-sm-2
+
+ div=this.model.c
+
+ div.col-sm-2
+
+ input(type="checkbox" checked=this.model.fixed disabled)
+
+ div.hidden(data-hook="import-properties")
+
+ div
+
+ h6.mr-2.inline Filename:
+
+ div.inline=this.model.filename ? `'${this.model.filename}'` : 'None'
+
+ div.hidden(data-hook="type-descriptions-prop")
+
+ h6.mr-2.inline Type Descriptions:
+
+ div.inline=this.model.subdomainFile ? `'${this.model.subdomainFile}'` : 'None'
diff --git a/client/domain-view/templates/viewShape.pug b/client/domain-view/templates/viewShape.pug
new file mode 100644
index 0000000000..3b476eef7f
--- /dev/null
+++ b/client/domain-view/templates/viewShape.pug
@@ -0,0 +1,128 @@
+div.mx-1
+
+ if(this.model.collection.indexOf(this.model) !== 0)
+ hr
+
+ div.row
+
+ div.col-sm-2
+
+ div.pl-2=this.model.name
+
+ div.col-sm-2
+
+ div.pl-2=this.model.type
+
+ div.col-sm-7
+
+ div=this.model.formula
+
+ div.col-sm-1
+
+ div.pl-3
+
+ input(type="checkbox" checked=this.model.fillable disabled)
+
+ div.mx-1.pl-2(data-hook="shape-details")
+
+ div.collapse(id="view-collapse-shape-details" + this.model.cid data-hook="view-collapse-shape-details" + this.model.cid)
+
+ hr
+
+ div.mx-1.mb-2
+
+ h6.inline.mr-3 Lattice:
+
+ div.inline=this.model.lattice
+
+ div.hidden(data-hook="cartesian-lattice-props")
+
+ div.mx-1.row.head.align-items-baseline
+
+ div.col-sm-2
+
+ h6.inline Length
+
+ div.col-sm-2
+
+ h6.inline Height
+
+ div.col-sm-2
+
+ h6.inline Depth
+
+ div.col-sm-2
+
+ h6.inline X-Spacing
+
+ div.col-sm-2
+
+ h6.inline Y-Spacing
+
+ div.col-sm-2
+
+ h6.inline Z-Spacing
+
+ div.mt-3.mx-1.row
+
+ div.col-sm-2
+
+ div=this.model.length
+
+ div.col-sm-2
+
+ div=this.model.height
+
+ div.col-sm-2
+
+ div=this.model.depth
+
+ div.col-sm-2
+
+ div=this.model.deltax
+
+ div.col-sm-2
+
+ div=this.model.deltay
+
+ div.col-sm-2
+
+ div=this.model.deltaz
+
+ div.hidden(data-hook="circular-lattice-props")
+
+ div.mx-1.row.head.align-items-baseline
+
+ div.col-sm-3
+
+ h6.inline Radius
+
+ div.col-sm-3.hidden(data-hook="cylinder-length-header")
+
+ h6.inline length
+
+ div.col-sm-3
+
+ h6.inline Surface Spacing
+
+ div.col-sm-3
+
+ h6.inline Radial Spacing
+
+ div.mt-3.mx-1.row
+
+ div.col-sm-3
+
+ div=this.model.radius
+
+ div.col-sm-3.hidden(data-hook="cylinder-length-feild")
+
+ div=this.model.length
+
+ div.col-sm-3
+
+ div=this.model.deltas
+
+ div.col-sm-3
+
+ div=this.model.deltar
diff --git a/client/domain-view/templates/viewTransformation.pug b/client/domain-view/templates/viewTransformation.pug
new file mode 100644
index 0000000000..51261a5d7a
--- /dev/null
+++ b/client/domain-view/templates/viewTransformation.pug
@@ -0,0 +1,206 @@
+div.mx-1
+
+ if(this.model.collection.indexOf(this.model) !== 0)
+ hr
+
+ div.row
+
+ div.col-sm-4
+
+ div.pl-2=this.model.name
+
+ div.col-sm-4
+
+ div=this.model.type
+
+ div.col-sm-4
+
+ div=this.model.transformation ? this.model.transformation : 'None'
+
+ div.hidden(data-hook="vector-transformation-props")
+
+ hr
+
+ div.mx-1.row.head.align-items-baseline
+
+ div.col-sm-9
+
+ h6 Vector
+
+ div.col-sm-3.hidden(data-hook="rotation-angle-header")
+
+ h6 Angle (deg)
+
+ div.mx-1.mt-3.row
+
+ div.col-sm-9
+
+ div.inline
+
+ span.inline [(x:
+
+ div.ml-2.inline=this.model.vector[0].x
+
+ span.inline.mr-2 , y:
+
+ div.ml-2.inline=this.model.vector[0].y
+
+ span.inline.mr-2 , z:
+
+ div.ml-2.inline=this.model.vector[0].z
+
+ span.inline ),
+
+ div.pl-2.inline
+
+ span.inline (x:
+
+ div.ml-2.inline=this.model.vector[1].x
+
+ span.inline.mr-2 , y:
+
+ div.ml-2.inline=this.model.vector[1].y
+
+ span.inline.mr-2 , z:
+
+ div.ml-2.inline=this.model.vector[1].z
+
+ span.inline )]
+
+ div.col-sm-3.hidden(data-hook="rotation-angle-prop")
+
+ div=this.model.angle
+
+ div.hidden(data-hook="reflect-transformation-props")
+
+ hr
+
+ div.mx-1.my-3.row.head.align-items-baseline
+
+ div.col-sm-4
+
+ h6 Point 1
+
+ div.col-sm-4.hidden(data-hook="view-normal-header")
+
+ h6 Normal
+
+ div.col-sm-4.hidden(data-hook="view-point2-header")
+
+ h6 Point 2
+
+ div.col-sm-4.hidden(data-hook="view-point3-header")
+
+ h6 Point 3
+
+ div.mx-1.my-3.row.align-items-baseline
+
+ div.col-sm-4
+
+ div
+
+ span.inline (x:
+
+ div.inline=this.model.point1.x
+
+ span.inline.mr-2 , y:
+
+ div.inline=this.model.point1.y
+
+ span.inline.mr-2 , z:
+
+ div.inline=this.model.point1.z
+
+ span.inline )
+
+ div.col-sm-4.hidden(data-hook="view-reflect-normal")
+
+ div
+
+ span.inline (x:
+
+ div.inline=this.model.normal.x
+
+ span.inline.mr-2 , y:
+
+ div.inline=this.model.normal.y
+
+ span.inline.mr-2 , z:
+
+ div.inline=this.model.normal.z
+
+ span.inline )
+
+ div.col-sm-4.hidden(data-hook="view-reflect-point2")
+
+ div
+
+ span.inline (x:
+
+ div.inline=this.model.point2.x
+
+ span.inline.mr-2 , y:
+
+ div.inline=this.model.point2.y
+
+ span.inline.mr-2 , z:
+
+ div.inline=this.model.point2.z
+
+ span.inline )
+
+ div.col-sm-4.hidden(data-hook="view-reflect-point3")
+
+ div
+
+ span.inline (x:
+
+ div.inline=this.model.point3.x
+
+ span.inline.mr-2 , y:
+
+ div.inline=this.model.point3.y
+
+ span.inline.mr-2 , z:
+
+ div.inline=this.model.point3.z
+
+ span.inline )
+
+ div.hidden(data-hook="scale-transformation-props")
+
+ div.mt-3
+
+ div.mx-1.row.head.align-items-baseline
+
+ div.col-sm-3
+
+ h6 Factor
+
+ div.col-sm-9
+
+ h6 Center
+
+ div.mx-1.mt-3.row
+
+ div.col-sm-3
+
+ div=this.model.factor
+
+ div.col-sm-9
+
+ div
+
+ span.inline (x:
+
+ div.ml-2.inline=this.model.center.x
+
+ span.inline.mr-2 , y:
+
+ div.ml-2.inline=this.model.center.y
+
+ span.inline.mr-2 , z:
+
+ div.ml-2.inline=this.model.center.z
+
+ span.inline )
diff --git a/client/domain-view/templates/viewType.pug b/client/domain-view/templates/viewType.pug
index 5a0277bf4a..ab91d16c0a 100644
--- a/client/domain-view/templates/viewType.pug
+++ b/client/domain-view/templates/viewType.pug
@@ -17,12 +17,6 @@ div.mx-1
div=this.model.numParticles
- hr
-
- h6.inline.mr-2 Geometry:
-
- div.inline=this.model.geometry
-
div.col-sm-6
div.row
diff --git a/client/domain-view/views/action-view.js b/client/domain-view/views/action-view.js
new file mode 100644
index 0000000000..4fea443dba
--- /dev/null
+++ b/client/domain-view/views/action-view.js
@@ -0,0 +1,815 @@
+/*
+StochSS is a platform for simulating biochemical systems
+Copyright (C) 2019-2022 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 .
+*/
+
+let $ = require('jquery');
+let path = require('path');
+let _ = require('underscore');
+//support files
+let app = require('../../app');
+let tests = require('../../views/tests');
+//views
+let View = require('ampersand-view');
+let InputView = require('../../views/input');
+let SelectView = require('ampersand-select-view');
+//templates
+let editTemplate = require('../templates/editAction.pug');
+let viewTemplate = require('../templates/viewAction.pug');
+
+module.exports = View.extend({
+ bindings: {
+ 'model.selected' : {
+ type: function (el, value, previousValue) {
+ el.checked = value;
+ },
+ hook: 'select-action'
+ },
+ 'model.enable' : {
+ type: function (el, value, previousValue) {
+ el.checked = value;
+ },
+ hook: 'enable-action'
+ }
+ },
+ events: {
+ 'change [data-hook=select-type-container]' : 'selectActionType',
+ 'change [data-hook=select-scope-container]' : 'selectActionScope',
+ 'change [data-target=update-preview-plot]' : 'updatePreviewPlot',
+ 'change [data-hook=shape-container]' : 'selectShape',
+ 'change [data-hook=transformation-container]' : 'selectTransformation',
+ 'change [data-target=point]' : 'setPoint',
+ 'change [data-target=new-point]' : 'setNewPoint',
+ 'change [data-hook=particle-type]' : 'setParticleType',
+ 'change [data-target=particle-property-containers]' : 'updateViewers',
+ 'change [data-hook=particle-fixed]' : 'setParticleFixed',
+ 'change [data-hook=mesh-file]' : 'setMeshFile',
+ 'change [data-hook=type-file]' : 'setTypeFile',
+ 'change [data-hook=action-mesh-select]' : 'selectMeshFile',
+ 'change [data-hook=mesh-location-select]' : 'selectMeshLocation',
+ 'change [data-hook=action-type-select]' : 'selectTypeFile',
+ 'change [data-hook=type-location-select]' : 'selectFileLocation',
+ 'click [data-hook=select-action]' : 'selectAction',
+ 'click [data-hook=enable-action]' : 'enableAction',
+ 'click [data-hook=remove]' : 'removeAction',
+ 'click [data-hook=collapseImportFiles]' : 'toggleImportFiles',
+ 'click [data-hook=collapseUploadedFiles]' : 'toggleUploadedFiles',
+ 'click [data-hook=import-mesh-file]' : 'handleImportMesh'
+ },
+ initialize: function (attrs, options) {
+ View.prototype.initialize.apply(this, arguments);
+ this.viewMode = attrs.viewMode ? attrs.viewMode : false;
+ this.chevrons = {
+ hide: `
+
+ `,
+ show: `
+
+ `
+ }
+ this.accepts = {"XML Mesh": ".xml", "Mesh IO": ".msh", "StochSS Domain": ".domn"};
+ this.accept = Object.keys(this.accepts).includes(this.model.type) ? this.accepts[this.model.type] : null;
+ this.filetype = this.model.type === "StochSS Domain" ? 'domain' : 'mesh';
+ this.meshFile = null;
+ this.typeFile = null;
+ },
+ render: function () {
+ this.template = this.viewMode ? viewTemplate : editTemplate;
+ View.prototype.render.apply(this, arguments);
+ this.details = {
+ 'Multi Particle': {
+ 'Fill Action': [
+ $(this.queryByHook('action-scope')),
+ $(this.queryByHook('multi-particle-shape')),
+ $(this.queryByHook('multi-particle-transformation')),
+ $(this.queryByHook('particle-properties'))
+ ],
+ 'Set Action': [
+ $(this.queryByHook('action-scope')),
+ $(this.queryByHook('multi-particle-shape')),
+ $(this.queryByHook('multi-particle-transformation')),
+ $(this.queryByHook('particle-properties'))
+ ],
+ 'Remove Action': [
+ $(this.queryByHook('action-scope')),
+ $(this.queryByHook('multi-particle-shape')),
+ $(this.queryByHook('multi-particle-transformation'))
+ ]
+ },
+ 'Single Particle': {
+ 'Fill Action': [
+ $(this.queryByHook('action-scope')),
+ $(this.queryByHook('single-particle-scope')),
+ $(this.queryByHook('particle-properties'))
+ ],
+ 'Set Action': [
+ $(this.queryByHook('action-scope')),
+ $(this.queryByHook('single-particle-scope')),
+ $(this.queryByHook('new-location')),
+ $(this.queryByHook('particle-properties'))
+ ],
+ 'Remove Action': [
+ $(this.queryByHook('action-scope')),
+ $(this.queryByHook('single-particle-scope'))
+ ]
+ },
+ 'XML Mesh': [
+ $(this.queryByHook('multi-particle-transformation')),
+ $(this.queryByHook('import-properties')),
+ $(this.queryByHook('type-descriptions-prop'))
+ ],
+ 'Mesh IO': [
+ $(this.queryByHook('multi-particle-transformation')),
+ $(this.queryByHook('import-properties')),
+ $(this.queryByHook('type-descriptions-prop'))
+ ],
+ 'StochSS Domain': [
+ $(this.queryByHook('multi-particle-transformation')),
+ $(this.queryByHook('import-properties'))
+ ]
+ }
+ app.documentSetup();
+ if(!this.viewMode){
+ if(this.model.selected) {
+ setTimeout(_.bind(this.openDetails, this), 1);
+ }
+ this.model.on('change', _.bind(this.updateViewer, this));
+ this.renderShapeSelectView();
+ this.renderTransformationSelectView();
+ this.renderNewLocationViews();
+ this.renderTypeSelectView();
+ this.renderParticleProperties();
+ if(Object.keys(this.accepts).includes(this.model.type)) {
+ this.meshFiles = null;
+ this.typeFiles = null;
+ this.renderActionFileSelects();
+ }
+ this.toggleEnable();
+ }
+ this.displayDetails();
+ },
+ completeAction: function () {
+ $(this.queryByHook("imf-in-progress")).css("display", "none");
+ $(this.queryByHook("imf-complete")).css("display", "inline-block");
+ setTimeout(() => {
+ $(this.queryByHook("imf-complete")).css("display", "none");
+ }, 5000);
+ },
+ displayDetails: function () {
+ let importTypes = ['XML Mesh', 'Mesh IO', 'StochSS Domain']
+ let elements = importTypes.includes(this.model.type) ?
+ this.details[this.model.type] : this.details[this.model.scope][this.model.type];
+ elements.forEach((element) => {
+ element.css('display', 'block');
+ });
+ },
+ enableAction: function () {
+ this.model.enable = !this.model.enable;
+ this.collection.parent.trigger('update-plot-preview');
+ },
+ errorAction: function (action) {
+ $(this.queryByHook("imf-in-progress")).css("display", "none");
+ $(this.queryByHook("imf-action-error")).text(action);
+ $(this.queryByHook("imf-error")).css("display", "block");
+ },
+ getShapeOptions: function () {
+ let options = [];
+ if(this.model.type === "Fill Action" && this.model.scope === "Multi Particle") {
+ this.model.collection.parent.shapes.forEach((shape) => {
+ if(shape.fillable) { options.push(shape.name); }
+ });
+ }else {
+ this.model.collection.parent.shapes.forEach((shape) => {
+ options.push(shape.name);
+ });
+ }
+ return options;
+ },
+ getTransformationOptions: function () {
+ return this.model.collection.parent.transformations.map((transformation) => {
+ return transformation.name;
+ });
+ },
+ handleImportMesh: function () {
+ this.startAction();
+ let formData = new FormData();
+ var filePath = this.model.collection.parent.directory;
+ if(filePath === null) {
+ filePath = this.parent.parent.parent.model.directory;
+ }
+ formData.append("path", filePath);
+ formData.append("datafile", this.meshFile);
+ if(this.typeFile) {
+ formData.append("typefile", this.typeFile);
+ }
+ let endpoint = path.join(app.getApiPath(), 'spatial-model/import-mesh');
+ app.postXHR(endpoint, formData, {
+ success: (err, response, body) => {
+ body = JSON.parse(body);
+ this.model.filename = path.join(body.meshPath, body.meshFile);
+ if(Object.keys(body).includes("typesPath")) {
+ this.model.subdomainFile = path.join(body.typesPath, body.typesFile);
+ this.typeFiles = null;
+ }
+ this.completeAction();
+ $(this.queryByHook('collapseUploadedFiles')).click();
+ this.renderActionFileSelects();
+ },
+ error: (err, response, body) => {
+ body = JSON.parse(body);
+ this.errorAction(body.Message);
+ }
+ }, false);
+ },
+ hideDetails: function () {
+ let importTypes = ['XML Mesh', 'Mesh IO', 'StochSS Domain']
+ let elements = importTypes.includes(this.model.type) ?
+ this.details[this.model.type] : this.details[this.model.scope][this.model.type];
+ elements.forEach((element) => {
+ element.css('display', 'none');
+ });
+ },
+ openDetails: function () {
+ $("#collapse-action-details" + this.model.cid).collapse("show");
+ },
+ removeAction: function () {
+ let actions = this.collection;
+ let enabled = this.model.enable;
+ actions.removeAction(this.model);
+ actions.parent.trigger('update-shape-deps');
+ if(enabled) {
+ actions.parent.trigger('update-plot-preview');
+ }
+ },
+ renderActionFileSelects: function () {
+ var queryStr = `?ext=${this.accept}`;
+ if(this.typeFiles === null) {
+ queryStr += `${queryStr}&includeTypes=True`;
+ }
+ let endpoint = `${path.join(app.getApiPath(), 'spatial-model/lattice-files')}${queryStr}`;
+ app.getXHR(endpoint, {success: (err, response, body) => {
+ this.meshFiles = body.meshFiles;
+ this.renderMeshSelectView();
+ if(Object.keys(body).includes('typeFiles')) {
+ this.typeFiles = body.typeFiles;
+ this.renderTypeFileSelectView();
+ }
+ }});
+ },
+ renderDensityPropertyView: function () {
+ if(this.densityPropertyView) {
+ this.densityPropertyView.remove();
+ }
+ this.densityPropertyView = new InputView({
+ parent: this,
+ required: true,
+ name: 'density',
+ tests: tests.valueTests,
+ valueType: 'number',
+ modelKey: 'rho',
+ value: this.model.rho
+ });
+ let hook = "particle-rho";
+ app.registerRenderSubview(this, this.densityPropertyView, hook);
+ },
+ renderMassPropertyView: function () {
+ if(this.massPropertyView) {
+ this.massPropertyView.remove();
+ }
+ this.massPropertyView = new InputView({
+ parent: this,
+ required: true,
+ name: 'mass',
+ tests: tests.valueTests,
+ valueType: 'number',
+ modelKey: 'mass',
+ value: this.model.mass
+ });
+ let hook = "particle-mass";
+ app.registerRenderSubview(this, this.massPropertyView, hook);
+ },
+ renderMeshLocationSelectView: function (index) {
+ if(this.meshLocationSelectView) {
+ this.meshLocationSelectView.remove();
+ }
+ let value = Boolean(this.model.filename) ? this.model.filename : "";
+ this.meshLocationSelectView = new SelectView({
+ name: 'mesh-locations',
+ required: false,
+ idAttributes: 'cid',
+ options: this.meshFiles.paths[index],
+ value: value,
+ unselectedText: "-- Select Mesh File Location --"
+ });
+ let hook = "mesh-location-select";
+ app.registerRenderSubview(this, this.meshLocationSelectView, hook);
+ },
+ renderMeshSelectView: function () {
+ if(this.meshSelectView) {
+ this.meshSelectView.remove();
+ }
+ let files = this.meshFiles.files.filter((file) => {
+ if(file[1] === this.model.filename.split('/').pop()) {
+ return file;
+ }
+ });
+ let value = files.length > 0 ? files[0] : "";
+ this.meshSelectView = new SelectView({
+ name: 'mesh-files',
+ required: false,
+ idAttributes: 'cid',
+ options: this.meshFiles.files,
+ value: value,
+ unselectedText: "-- Select Mesh File --"
+ });
+ let hook = "action-mesh-select";
+ app.registerRenderSubview(this, this.meshSelectView, hook);
+ if(value !== "" && this.meshFiles.paths[value[0]].length > 1) {
+ this.renderMeshLocationSelectView(value[0]);
+ $(this.queryByHook("mesh-location-container")).css("display", "inline-block");
+ }
+ },
+ renderNewLocationViews: function () {
+ if(this.newLocationViews) {
+ this.newLocationViews['x'].remove();
+ this.newLocationViews['y'].remove();
+ this.newLocationViews['z'].remove();
+ }else{
+ this.newLocationViews = {};
+ }
+ this.newLocationViews['x'] = new InputView({
+ parent: this,
+ required: true,
+ name: 'new-point-x',
+ tests: [tests.nanValue],
+ valueType: 'number',
+ value: this.model.newPoint.x
+ });
+ let hookX = "new-point-x-container";
+ app.registerRenderSubview(this, this.newLocationViews['x'], hookX);
+ this.newLocationViews['y'] = new InputView({
+ parent: this,
+ required: true,
+ name: 'new-point-y',
+ tests: [tests.nanValue],
+ valueType: 'number',
+ value: this.model.newPoint.y
+ });
+ let hookY = "new-point-y-container";
+ app.registerRenderSubview(this, this.newLocationViews['y'], hookY);
+ this.newLocationViews['z'] = new InputView({
+ parent: this,
+ required: true,
+ name: 'new-point-z',
+ tests: [tests.nanValue],
+ valueType: 'number',
+ value: this.model.newPoint.z
+ });
+ let hookZ = "new-point-z-container";
+ app.registerRenderSubview(this, this.newLocationViews['z'], hookZ);
+ },
+ renderParticleProperties: function () {
+ this.renderMassPropertyView();
+ this.renderVolumePropertyView();
+ this.renderDensityPropertyView();
+ this.renderViscosityPropertyView();
+ this.renderSOSPropertyView();
+ },
+ renderShapeSelectView: function () {
+ if(this.shapeSelectView) {
+ this.shapeSelectView.remove();
+ }
+ let options = this.getShapeOptions();
+ this.shapeSelectView = new SelectView({
+ name: 'shape',
+ required: true,
+ options: options,
+ value: this.model.shape,
+ unselectedText: "-- Select Shape --"
+ });
+ let hook = "shape-container";
+ app.registerRenderSubview(this, this.shapeSelectView, hook);
+ },
+ renderSOSPropertyView: function () {
+ if(this.sOSPropertyView) {
+ this.sOSPropertyView.remove();
+ }
+ this.sOSPropertyView = new InputView({
+ parent: this,
+ required: true,
+ name: 'speed-of-sound',
+ tests: tests.valueTests,
+ valueType: 'number',
+ modelKey: 'c',
+ value: this.model.c
+ });
+ let hook = "particle-c";
+ app.registerRenderSubview(this, this.sOSPropertyView, hook);
+ },
+ renderTransformationSelectView: function () {
+ if(this.transformationSelectView) {
+ this.transformationSelectView.remove();
+ }
+ let options = this.getTransformationOptions();
+ this.transformationSelectView = new SelectView({
+ name: 'transformation',
+ required: true,
+ options: options,
+ value: this.model.transformation,
+ unselectedText: "-- Select Transformation --"
+ });
+ let hook = "transformation-container";
+ app.registerRenderSubview(this, this.transformationSelectView, hook);
+ },
+ renderTypeLocationSelectView: function (index) {
+ if(this.typeLocationSelectView) {
+ this.typeLocationSelectView.remove();
+ }
+ let value = Boolean(this.model.subdomainFile) ? this.model.subdomainFile : "";
+ this.typeLocationSelectView = new SelectView({
+ name: 'type-locations',
+ required: false,
+ idAttributes: 'cid',
+ options: this.typeFiles.paths[index],
+ value: value,
+ unselectedText: "-- Select Type File Location --"
+ });
+ let hook = "type-location-select";
+ app.registerRenderSubview(this, this.typeLocationSelectView, hook);
+ },
+ renderTypeFileSelectView: function () {
+ if(this.typeSelectView) {
+ this.typeSelectView.remove();
+ }
+ var file = this.typeFiles.files.filter((file) => {
+ if(file[1] === this.model.subdomainFile.split('/').pop()) {
+ return file;
+ }
+ });
+ let value = file.length > 0 ? file[0] : "";
+ this.typeSelectView = new SelectView({
+ name: 'type-files',
+ required: false,
+ idAttributes: 'cid',
+ options: this.typeFiles.files,
+ value: value,
+ unselectedText: "-- Select Type File --"
+ });
+ let hook = "action-type-select";
+ app.registerRenderSubview(this, this.typeSelectView, hook);
+ if(value !== "" && this.typeFiles.paths[value[0]].length > 1) {
+ this.renderTypeLocationSelectView(value[0]);
+ $(this.queryByHook("types-location-container")).css("display", "inline-block");
+ }
+ },
+ renderTypeSelectView: function () {
+ if(this.typeSelectView) {
+ this.typeSelectView.remove();
+ }
+ let options = this.model.collection.parent.types.map((type) => {
+ return [type.typeID, type.name];
+ });
+ this.typeSelectView = new SelectView({
+ name: 'type',
+ required: true,
+ options: options,
+ value: this.model.typeID,
+ });
+ let hook = "particle-type";
+ app.registerRenderSubview(this, this.typeSelectView, hook);
+ },
+ renderViscosityPropertyView: function () {
+ if(this.viscosityPropertyView) {
+ this.viscosityPropertyView.remove();
+ }
+ this.viscosityPropertyView = new InputView({
+ parent: this,
+ required: true,
+ name: 'viscosity',
+ tests: tests.valueTests,
+ valueType: 'number',
+ modelKey: 'nu',
+ value: this.model.nu
+ });
+ let hook = "particle-nu";
+ app.registerRenderSubview(this, this.viscosityPropertyView, hook);
+ },
+ renderVolumePropertyView: function () {
+ if(this.volumePropertyView) {
+ this.volumePropertyView.remove();
+ }
+ this.volumePropertyView = new InputView({
+ parent: this,
+ required: true,
+ name: 'volume',
+ tests: tests.valueTests,
+ valueType: 'number',
+ modelKey: 'vol',
+ value: this.model.vol
+ });
+ let hook = "particle-vol";
+ app.registerRenderSubview(this, this.volumePropertyView, hook);
+ },
+ selectAction: function () {
+ this.model.selected = !this.model.selected;
+ },
+ selectActionScope: function (e) {
+ this.hideDetails();
+ this.model.scope = e.target.value;
+ this.displayDetails();
+ this.toggleEnable();
+ this.updatePreviewPlot();
+ },
+ selectActionType: function (e) {
+ this.hideDetails();
+ this.model.type = e.target.value;
+ this.displayDetails();
+ let types = {
+ 'XML Mesh': '.xml', 'Mesh IO': '.msh', 'StochSS Domain': '.domn'
+ };
+ if(Object.keys(types).includes(this.model.type)) {
+ this.accept = types[this.model.type]
+ $(this.queryByHook('mesh-file')).prop('accept', this.accept);
+ this.filetype = this.model.type === "StochSS Domain" ? 'domain' : 'mesh';
+ $(this.queryByHook('meshfile-label')).text(`Please specify a ${this.filetype} to import: `);
+ $(this.queryByHook('action-filename-label')).text(`Please specify a ${this.filetype} to import: `);
+ $(this.queryByHook('mesh-location-message')).text(
+ `There are multiple ${this.filetype} files with that name, please select a location`
+ );
+ this.renderActionFileSelects();
+ }
+ this.toggleEnable();
+ this.updatePreviewPlot();
+ },
+ selectFileLocation: function (e) {
+ this.model.subdomainFile = e.target.value ? e.target.value : "";
+ },
+ selectMeshFile: function (e) {
+ let value = e.target.value;
+ var msgDisplay = "none";
+ var contDisplay = "none";
+ if(value) {
+ if(this.meshFiles.paths[value].length > 1) {
+ msgDisplay = "block";
+ contDisplay = "inline-block";
+ this.renderMeshLocationSelectView(value);
+ this.model.filename = "";
+ }else{
+ this.model.filename = this.meshFiles.paths[value][0];
+ }
+ }else{
+ this.model.filename = "";
+ }
+ $(this.queryByHook("mesh-location-message")).css('display', msgDisplay);
+ $(this.queryByHook("mesh-location-container")).css("display", contDisplay);
+ },
+ selectMeshLocation: function (e) {
+ this.model.filename = e.target.value ? e.target.value : "";
+ },
+ selectShape: function (e) {
+ this.model.shape = e.target.value;
+ this.toggleEnable();
+ this.model.collection.parent.trigger('update-shape-deps');
+ this.updateViewer();
+ this.updatePreviewPlot();
+ },
+ selectTypeFile: function (e) {
+ let value = e.target.value;
+ var msgDisplay = "none";
+ var contDisplay = "none";
+ if(value) {
+ if(this.typeFiles.paths[value].length > 1) {
+ msgDisplay = "block";
+ contDisplay = "inline-block";
+ this.renderTypeLocationSelectView(value);
+ this.model.subdomainFile = "";
+ }else{
+ this.model.subdomainFile = this.typeFiles.paths[value][0];
+ }
+ }else{
+ this.model.subdomainFile = "";
+ }
+ $(this.queryByHook("types-location-message")).css('display', msgDisplay);
+ $(this.queryByHook("types-location-container")).css("display", contDisplay);
+ },
+ selectTransformation: function (e) {
+ this.model.transformation = e.target.value;
+ this.model.collection.parent.trigger('update-transformation-deps');
+ this.updateViewer();
+ this.updatePreviewPlot();
+ },
+ setMeshFile: function (e) {
+ this.meshFile = e.target.files[0];
+ this.updateViewer();
+ $(this.queryByHook("import-mesh-file")).prop('disabled', !this.meshFile);
+ },
+ setNewPoint: function (e) {
+ let key = e.target.parentElement.parentElement.dataset.name;
+ let value = Number(e.target.value);
+ this.model.newPoint[key] = value;
+ this.updateViewer();
+ this.updatePreviewPlot();
+ },
+ setParticleFixed: function (e) {
+ this.model.fixed = !this.model.fixed;
+ this.updateViewer();
+ this.updatePreviewPlot();
+ },
+ setParticleType: function (e) {
+ let value = Number(e.target.value);
+ let currType = this.model.collection.parent.types.get(this.model.typeID, "typeID");
+ let newType = this.model.collection.parent.types.get(value, "typeID");
+ this.updatePropertyDefaults(currType, newType);
+ this.model.typeID = value;
+ this.model.collection.parent.trigger('update-type-deps');
+ this.updateViewer();
+ this.updatePreviewPlot();
+ },
+ setPoint: function (e) {
+ let key = e.target.parentElement.parentElement.dataset.name;
+ let value = Number(e.target.value);
+ if(this.model.newPoint[key] === this.model.point[key]) {
+ this.model.newPoint[key] = value;
+ this.renderNewLocationViews();
+ }
+ this.model.point[key] = value;
+ this.updateViewer();
+ this.updatePreviewPlot();
+ },
+ setTypeFile: function (e) {
+ this.typeFile = e.target.files[0];
+ this.updateViewer();
+ },
+ startAction: function () {
+ $(this.queryByHook("imf-complete")).css("display", "none");
+ $(this.queryByHook("imf-error")).css("display", "none");
+ $(this.queryByHook("imf-in-progress")).css("display", "inline-block");
+ },
+ toggleEnable: function () {
+ let noShape = this.model.shape === "";
+ let mltPart = this.model.scope === "Multi Particle";
+ let noImprt = ["Fill Action", "Set Action", "Remove Action"].includes(this.model.type);
+ let disable = noShape && mltPart && noImprt
+ $(this.queryByHook("enable-action")).prop("disabled", disable);
+ },
+ toggleImportFiles: function (e) {
+ let classes = $(this.queryByHook('collapseImportFiles')).attr("class").split(/\s+/);
+ $(this.queryByHook('uploaded-chevron')).html(this.chevrons.hide);
+ if(classes.includes('collapsed')) {
+ $(this.queryByHook('import-chevron')).html(this.chevrons.show);
+ }else{
+ $(this.queryByHook('import-chevron')).html(this.chevrons.hide);
+ }
+ },
+ toggleUploadedFiles: function (e) {
+ let classes = $(this.queryByHook('collapseUploadedFiles')).attr("class").split(/\s+/);
+ $(this.queryByHook('import-chevron')).html(this.chevrons.hide);
+ if(classes.includes('collapsed')) {
+ $(this.queryByHook('uploaded-chevron')).html(this.chevrons.show);
+ }else{
+ $(this.queryByHook('uploaded-chevron')).html(this.chevrons.hide);
+ }
+ },
+ update: function () {},
+ updatePreviewPlot: function () {
+ if(!this.model.enable) { return }
+ if(this.model.type === "Fill Action" && this.model.scope === "Multi Particle" && this.model.shape === "") {
+ return
+ }
+ this.collection.parent.trigger('update-plot-preview');
+ },
+ updatePropertyDefaults: function (currType, newType) {
+ if(this.model.mass === currType.mass) {
+ this.model.mass = newType.mass;
+ }
+ if(this.model.vol === currType.volume) {
+ this.model.vol = newType.volume;
+ }
+ if(this.model.rho === currType.rho) {
+ this.model.rho = newType.rho;
+ }
+ if(this.model.nu === currType.nu) {
+ this.model.nu = newType.nu;
+ }
+ if(this.model.c === currType.c) {
+ this.model.c = newType.c;
+ }
+ if(this.model.fixed === currType.fixed) {
+ this.model.fixed = newType.fixed;
+ }
+ this.renderParticleProperties();
+ $(this.queryByHook('particle-fixed')).prop('checked', this.model.fixed);
+ },
+ updateValid: function () {},
+ updateViewer: function () {
+ this.parent.renderViewActionsView();
+ },
+ updateViewers: function () {
+ this.updatePreviewPlot();
+ this.updateViewer();
+ },
+ subviews: {
+ inputPriority: {
+ hook: 'input-priority-container',
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ required: true,
+ name: 'priority',
+ tests: tests.valueTests,
+ modelKey: 'priority',
+ valueType: 'number',
+ value: this.model.priority
+ });
+ }
+ },
+ selectType: {
+ hook: 'select-type-container',
+ prepareView: function (el) {
+ let groupOptions = [
+ {groupName: "Shape Actions", options: ['Fill Action', 'Set Action', 'Remove Action']},
+ {groupName: "Import Actions", options: ['XML Mesh', 'Mesh IO', 'StochSS Domain']}
+ ]
+ return new SelectView({
+ name: 'type',
+ required: true,
+ idAttributes: 'cid',
+ groupOptions: groupOptions,
+ value: this.model.type
+ });
+ }
+ },
+ selectScope: {
+ hook: 'select-scope-container',
+ prepareView: function (el) {
+ let options = [
+ 'Multi Particle',
+ 'Single Particle'
+ ];
+ return new SelectView({
+ name: 'scope',
+ required: true,
+ idAttributes: 'cid',
+ options: options,
+ value: this.model.scope
+ });
+ }
+ },
+ inputPointX: {
+ hook: 'point-x-container',
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ required: true,
+ name: 'point-x',
+ tests: [tests.nanValue],
+ valueType: 'number',
+ value: this.model.point.x
+ });
+ }
+ },
+ inputPointY: {
+ hook: 'point-y-container',
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ required: true,
+ name: 'point-y',
+ tests: [tests.nanValue],
+ valueType: 'number',
+ value: this.model.point.y
+ });
+ }
+ },
+ inputPointZ: {
+ hook: 'point-z-container',
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ required: true,
+ name: 'point-z',
+ tests: [tests.nanValue],
+ valueType: 'number',
+ value: this.model.point.z
+ });
+ }
+ }
+ }
+});
\ No newline at end of file
diff --git a/client/domain-view/views/actions-view.js b/client/domain-view/views/actions-view.js
new file mode 100644
index 0000000000..9122f820f3
--- /dev/null
+++ b/client/domain-view/views/actions-view.js
@@ -0,0 +1,148 @@
+/*
+StochSS is a platform for simulating biochemical systems
+Copyright (C) 2019-2022 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 .
+*/
+
+let $ = require('jquery');
+//support files
+let app = require('../../app');
+let Tooltips = require('../../tooltips');
+//views
+let View = require('ampersand-view');
+let EditActionView = require('./action-view');
+//templates
+let template = require('../templates/actionsView.pug');
+
+module.exports = View.extend({
+ template: template,
+ events: {
+ 'click [data-hook=collapse]' : 'changeCollapseButtonText',
+ 'click [data-hook=fill-action]' : 'addAction',
+ 'click [data-hook=set-action]' : 'addAction',
+ 'click [data-hook=remove-action]' : 'addAction',
+ 'click [data-hook=xml-mesh-action]' : 'addAction',
+ 'click [data-hook=mesh-io-action]' : 'addAction',
+ 'click [data-hook=stochss-domain-action]' : 'addAction'
+ },
+ initialize: function (attrs, options) {
+ View.prototype.initialize.apply(this, arguments);
+ this.readOnly = attrs.readOnly ? attrs.readOnly : false;
+ this.tooltips = Tooltips.domainAction;
+ },
+ render: function (attrs, options) {
+ View.prototype.render.apply(this, arguments);
+ if(this.readOnly) {
+ $(this.queryByHook('actions-edit-tab')).addClass("disabled");
+ $(".nav .disabled>a").on("click", function(e) {
+ e.preventDefault();
+ return false;
+ });
+ $(this.queryByHook('actions-view-tab')).tab('show');
+ $(this.queryByHook('edit-actions')).removeClass('active');
+ $(this.queryByHook('view-actions')).addClass('active');
+ }else{
+ this.renderEditActionsView();
+ this.toggleActionsCollectionError("ev");
+ this.collection.on('add remove', () => {
+ this.toggleActionsCollectionError("ev");
+ this.toggleActionsCollectionError("vv");
+ }, this);
+ this.collection.on('update-shape-options', this.updateShapeOptions, this);
+ this.collection.on('update-transformation-options', this.updateTransformationOptions, this);
+ this.collection.on('update-type-options', this.updateTypeOptions, this);
+ }
+ this.renderViewActionsView();
+ this.toggleActionsCollectionError("vv");
+ },
+ addAction: function (e) {
+ let type = e.target.dataset.name;
+ this.collection.addAction(type);
+ },
+ changeCollapseButtonText: function (e) {
+ app.changeCollapseButtonText(this, e);
+ },
+ openSection: function () {
+ if(!$(this.queryByHook("actions-list-container")).hasClass("show")) {
+ let actionsCollapseBtn = $(this.queryByHook("collapse"));
+ actionsCollapseBtn.click();
+ actionsCollapseBtn.html('-');
+ }
+ },
+ renderEditActionsView: function ({key=null, attr=null}={}) {
+ if(this.editActionsView) {
+ this.editActionsView.remove();
+ }
+ let options = {filter: (model) => { return model.contains(attr, key); }}
+ this.editActionsView = this.renderCollection(
+ this.collection,
+ EditActionView,
+ this.queryByHook('edit-actions-list'),
+ options
+ );
+ },
+ renderViewActionsView: function ({key=null, attr=null}={}) {
+ if(this.viewActionsView) {
+ this.viewActionsView.remove();
+ }
+ let options = {
+ viewOptions: {viewMode: true},
+ filter: (model) => { return model.contains(attr, key); }
+ }
+ this.viewActionsView = this.renderCollection(
+ this.collection,
+ EditActionView,
+ this.queryByHook('view-actions-list'),
+ options
+ );
+ },
+ toggleActionsCollectionError: function (viewCode) {
+ let errorMsg = $(this.queryByHook(`${viewCode}-actions-collection-error`));
+ if(!this.collection.validateCollection()) {
+ errorMsg.addClass('component-invalid');
+ errorMsg.removeClass('component-valid');
+ }else{
+ errorMsg.addClass('component-valid');
+ errorMsg.removeClass('component-invalid');
+ }
+ },
+ updateShapeOptions: function ({currName=null, newName=null}={}) {
+ if(currName === null && newName === null) { return; }
+ this.editActionsView.views.forEach((view) => {
+ if(view.model.shape === currName) {
+ view.model.shape = newName;
+ }
+ view.renderShapeSelectView();
+ });
+ },
+ updateTransformationOptions: function ({currName=null, newName=null}={}) {
+ if(currName === null && newName === null) { return; }
+ this.editActionsView.views.forEach((view) => {
+ if(view.model.transformation === currName && newName !== null) {
+ view.model.transformation = newName;
+ }
+ view.renderTransformationSelectView();
+ });
+ },
+ updateTypeOptions: function ({currName=null, newName=null}={}) {
+ if(currName === null && newName === null) { return; }
+ this.editActionsView.views.forEach((view) => {
+ if(view.model.typeID === currName) {
+ view.model.typeID = newName;
+ }
+ view.renderTypeSelectView();
+ });
+ }
+});
\ No newline at end of file
diff --git a/client/domain-view/views/edit-3D-domain-view.js b/client/domain-view/views/edit-3D-domain-view.js
deleted file mode 100644
index cfd4c503b4..0000000000
--- a/client/domain-view/views/edit-3D-domain-view.js
+++ /dev/null
@@ -1,302 +0,0 @@
-/*
-StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 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 .
-*/
-
-let $ = require('jquery');
-let path = require('path');
-//support files
-let app = require('../../app');
-let tests = require('../../views/tests');
-//views
-let View = require('ampersand-view');
-let InputView = require('../../views/input');
-let SelectView = require('ampersand-select-view');
-//templates
-let template = require('../templates/edit3DDomainView.pug');
-
-module.exports = View.extend({
- template: template,
- events: {
- 'change [data-target=edit-3d-domain-n]' : 'handleNumParticlesUpdate',
- 'change [data-target=edit-3d-domain-limitation]' : 'handleLimitsUpdate',
- 'change [data-hook=edit-3d-domain-type-select]' : 'handleTypeUpdate',
- 'change [data-target=edit-3d-domain-trans]' : 'handleTransformationupdate',
- 'click [data-hook=collapse-3D-domain-advanced]' : 'changeCollapseButtonText',
- 'click [data-hook=build-3d-domain]' : 'handleBuildDomain'
- },
- initialize: function (attrs, options) {
- View.prototype.initialize.apply(this, arguments);
- this.data = {
- "nx":1, "ny":1, "nz":1,
- "xLim":[0, 0], "yLim":[0, 0], "zLim":[0, 0],
- "type": null, "transformation": null
- }
- this.transformation = [0, 0, 0];
- },
- render: function (attrs, options) {
- View.prototype.render.apply(this, arguments);
- this.type = this.parent.model.types.get(0, "typeID");
- this.updateTotalParticles();
- this.updateTypeDefaults();
- },
- changeCollapseButtonText: function (e) {
- app.changeCollapseButtonText(this, e);
- },
- completeAction: function () {
- $(this.queryByHook("c3dd-in-progress")).css("display", "none");
- $(this.queryByHook("c3dd-complete")).css("display", "inline-block");
- setTimeout(() => {
- $(this.queryByHook("c3dd-complete")).css("display", "none");
- }, 5000);
- },
- errorAction: function (action) {
- $(this.queryByHook("c3dd-in-progress")).css("display", "none");
- $(this.queryByHook("c3dd-action-error")).text(action);
- $(this.queryByHook("c3dd-error")).css("display", "block");
- },
- handleBuildDomain: function () {
- this.startAction();
- this.transformation.every((value) => {
- if(value !== 0) {
- this.data.transformation = this.transformation;
- return false;
- }
- return true;
- });
- this.data.domainExists = this.parent.model.particles.length > 0;
- let endpoint = path.join(app.getApiPath(), "spatial-model/3d-domain");
- app.postXHR(endpoint, this.data, {
- success: (err, response, body) => {
- this.parent.add3DDomain(body.limits, body.particles);
- this.completeAction();
- $('html, body').animate({
- scrollTop: $("#domain-figure").offset().top
- }, 20);
- },
- error: (err, response, body) => {
- this.errorAction(body.Message);
- }
- });
- },
- handleLimitsUpdate: function (e) {
- let key = e.target.parentElement.parentElement.dataset.name;
- let index = Number(e.target.parentElement.parentElement.dataset.index);
- this.data[key][index] = Number(e.target.value);
- },
- handleNumParticlesUpdate: function (e) {
- let key = e.target.parentElement.parentElement.dataset.key;
- this.data[key] = Number(e.target.value);
- this.updateTotalParticles();
- },
- handleTransformationupdate: function (e) {
- let index = e.target.parentElement.parentElement.dataset.index;
- this.transformation[index] = Number(e.target.value);
- },
- handleTypeUpdate: function (e) {
- let typeID = Number(e.target.value);
- this.type = this.parent.model.types.get(typeID, "typeID");
- this.updateTypeDefaults();
- },
- startAction: function () {
- $(this.queryByHook("c3dd-complete")).css("display", "none");
- $(this.queryByHook("c3dd-error")).css("display", "none");
- $(this.queryByHook("c3dd-in-progress")).css("display", "inline-block");
- },
- update: function () {},
- updateValid: function () {},
- updateTotalParticles: function () {
- let n = this.data.nx * this.data.ny * this.data.nz;
- $(this.queryByHook("3d-domain-n")).text(n);
- $(this.queryByHook("build-3d-domain")).prop("disabled", n <= 0);
- },
- updateTypeDefaults: function () {
- this.data.typeID = this.type.typeID;
- this.data.type = {
- "type_id": this.type.name, "mass": this.type.mass, "rho": this.type.rho,
- "nu": this.type.nu, "c": this.type.c, "fixed": this.type.fixed
- }
- $(this.queryByHook("edit-3d-domain-td-mass")).text(this.type.mass);
- $(this.queryByHook("edit-3d-domain-td-vol")).text(this.type.volume);
- $(this.queryByHook("edit-3d-domain-td-rho")).text(this.type.rho);
- $(this.queryByHook("edit-3d-domain-td-nu")).text(this.type.nu);
- $(this.queryByHook("edit-3d-domain-td-c")).text(this.type.c);
- $(this.queryByHook("edit-3d-domain-td-fixed")).prop('checked', this.type.fixed);
- },
- subviews: {
- nXInputView: {
- hook: "edit-3d-domain-nx",
- prepareView: function (el) {
- return new InputView({
- parent: this,
- required: true,
- name: 'nx',
- valueType: 'number',
- tests: tests.valueTests,
- value: this.data.nx
- });
- }
- },
- nYInputView: {
- hook: "edit-3d-domain-ny",
- prepareView: function (el) {
- return new InputView({
- parent: this,
- required: true,
- name: 'ny',
- valueType: 'number',
- tests: tests.valueTests,
- value: this.data.ny
- });
- }
- },
- nZInputView: {
- hook: "edit-3d-domain-nz",
- prepareView: function (el) {
- return new InputView({
- parent: this,
- required: true,
- name: 'nz',
- valueType: 'number',
- tests: tests.valueTests,
- value: this.data.nz
- });
- }
- },
- xMinLimInputView: {
- hook: "edit-3d-domain-x-lim-min",
- prepareView: function (el) {
- return new InputView({
- parent: this,
- required: true,
- name: 'x-lim-min',
- valueType: 'number',
- value: this.data.xLim[0]
- });
- }
- },
- yMinLimInputView: {
- hook: "edit-3d-domain-y-lim-min",
- prepareView: function (el) {
- return new InputView({
- parent: this,
- required: true,
- name: 'y-lim-min',
- valueType: 'number',
- value: this.data.yLim[0]
- });
- }
- },
- zMinLimInputView: {
- hook: "edit-3d-domain-z-lim-min",
- prepareView: function (el) {
- return new InputView({
- parent: this,
- required: true,
- name: 'z-lim-min',
- valueType: 'number',
- value: this.data.zLim[0]
- });
- }
- },
- xMaxLimInputView: {
- hook: "edit-3d-domain-x-lim-max",
- prepareView: function (el) {
- return new InputView({
- parent: this,
- required: true,
- name: 'x-lim-max',
- valueType: 'number',
- value: this.data.xLim[1]
- });
- }
- },
- yMaxLimInputView: {
- hook: "edit-3d-domain-y-lim-max",
- prepareView: function (el) {
- return new InputView({
- parent: this,
- required: true,
- name: 'y-lim-max',
- valueType: 'number',
- value: this.data.yLim[1]
- });
- }
- },
- zMaxLimInputView: {
- hook: "edit-3d-domain-z-lim-max",
- prepareView: function (el) {
- return new InputView({
- parent: this,
- required: true,
- name: 'z-lim-max',
- valueType: 'number',
- value: this.data.zLim[1]
- });
- }
- },
- typeSelectView: {
- hook: "edit-3d-domain-type-select",
- prepareView: function (el) {
- return new SelectView({
- name: 'type',
- required: true,
- idAttribute: 'typeID',
- textAttribute: 'name',
- eagerValidate: true,
- options: this.parent.model.types,
- value: this.type
- });
- }
- },
- xTransInputView: {
- hook: "edit-3d-domain-x-trans",
- prepareView: function (el) {
- return new InputView({
- parent: this,
- required: true,
- name: 'x-transformation',
- valueType: 'number',
- value: this.transformation[0]
- });
- }
- },
- yTransInputView: {
- hook: "edit-3d-domain-y-trans",
- prepareView: function (el) {
- return new InputView({
- parent: this,
- required: true,
- name: 'y-transformation',
- valueType: 'number',
- value: this.transformation[1]
- });
- }
- },
- zTransInputView: {
- hook: "edit-3d-domain-z-trans",
- prepareView: function (el) {
- return new InputView({
- parent: this,
- required: true,
- name: 'z-transformation',
- valueType: 'number',
- value: this.transformation[2]
- });
- }
- }
- }
-});
\ No newline at end of file
diff --git a/client/domain-view/views/fill-geometry-view.js b/client/domain-view/views/fill-geometry-view.js
deleted file mode 100644
index f9f507f3a9..0000000000
--- a/client/domain-view/views/fill-geometry-view.js
+++ /dev/null
@@ -1,267 +0,0 @@
-/*
-StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 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 .
-*/
-
-let $ = require('jquery');
-let path = require('path');
-//support files
-let app = require('../../app');
-let tests = require('../../views/tests');
-//views
-let View = require('ampersand-view');
-let InputView = require('../../views/input');
-let SelectView = require('ampersand-select-view');
-//templates
-let template = require('../templates/fillGeometryView.pug');
-
-module.exports = View.extend({
- template: template,
- events: {
- 'change [data-hook=fill-geometry-type-select]' : 'handleTypeUpdate',
- 'change [data-target=fill-geometry-limitation]' : 'handleDataUpdate',
- 'change [data-target=fill-geometry-delta]' : 'handleDataUpdate',
- 'click [data-hook=fill-geometry]' : 'handleFillGeometry'
- },
- initialize: function (attrs, options) {
- View.prototype.initialize.apply(this, arguments);
- this.data = {
- "xmin":0, "ymin":0, "zmin":0, "xmax":0, "ymax":0,
- "zmax":0, "deltax": 0, "deltay": 0, "deltaz": 0
- }
- this.type = null;
- },
- render: function (attrs, options) {
- View.prototype.render.apply(this, arguments);
- this.getTypes();
- this.renderTypeSelectView();
- },
- completeAction: function () {
- $(this.queryByHook("fg-in-progress")).css("display", "none");
- $(this.queryByHook("fg-complete")).css("display", "inline-block");
- setTimeout(() => {
- $(this.queryByHook("fg-complete")).css("display", "none");
- }, 5000);
- },
- disable: function () {
- if(this.data.xmin === this.data.xmax) { return true; }
- if(this.data.ymin === this.data.ymax) { return true; }
- if(this.data.zmin === this.data.zmax) { return true; }
- if(this.data.deltax === 0) { return true; }
- if(this.data.deltay === 0) { return true; }
- if(this.data.deltaz === 0) { return true; }
- if(!this.type) { return true; }
- return false;
- },
- errorAction: function (action) {
- $(this.queryByHook("fg-in-progress")).css("display", "none");
- $(this.queryByHook("fg-action-error")).html(action);
- $(this.queryByHook("fg-error")).css("display", "block");
- },
- getTypes: function () {
- this.types = this.parent.model.types.filter((type) => {
- return Boolean(type.geometry);
- });
- },
- handleDataUpdate: function (e) {
- let key = e.target.parentElement.parentElement.dataset.name;
- this.data[key] = Number(e.target.value);
- this.toggleFillGeometry();
- },
- handleFillGeometry: function () {
- this.startAction();
- let data = {kwargs: this.data, type: this.type}
- let endpoint = path.join(app.getApiPath(), 'spatial-model/fill-geometry');
- app.postXHR(endpoint, data, {
- success: (err, response, body) => {
- this.parent.add3DDomain(body.limits, body.particles);
- this.completeAction();
- },
- error: (err, response, body) => {
- if(body.Traceback.includes("SyntaxError")) {
- var tracePart = body.Traceback.split('\n').slice(6)
- tracePart.splice(2, 2)
- tracePart[1] = tracePart[1].replace(new RegExp(' ', 'g'), ' ')
- var errorBlock = `
${body.Message}
${tracePart.join('
')}
`
- }else{
- var errorBlock = body.Message
- }
- this.errorAction(errorBlock);
- }
- });
- },
- handleTypeUpdate: function (e) {
- if(e.target.value) {
- let typeID = Number(e.target.value);
- this.type = this.parent.model.types.get(typeID, "typeID");
- }else{
- this.type = null;
- }
- this.updateTypeDefaults();
- this.toggleFillGeometry();
- },
- renderTypeSelectView: function () {
- if(this.typeSelectView) {
- this.typeSelectView.remove();
- }
- if(this.types) {
- var options = this.types.map((type) => {
- return [type.typeID, type.name];
- });
- }else{
- var options = [];
- }
- this.typeSelectView = new SelectView({
- name: 'type',
- required: false,
- idAttribute: 'typeID',
- textAttribute: 'name',
- eagerValidate: true,
- options: options,
- unselectedText: "Select a Type"
- });
- app.registerRenderSubview(this, this.typeSelectView, "fill-geometry-type-select");
- },
- startAction: function () {
- $(this.queryByHook("fg-complete")).css("display", "none");
- $(this.queryByHook("fg-error")).css("display", "none");
- $(this.queryByHook("fg-in-progress")).css("display", "inline-block");
- },
- toggleFillGeometry: function () {
- $(this.queryByHook('fill-geometry')).prop('disabled', this.disable());
- },
- update: function() {},
- updateTypeDefaults: function () {
- $(this.queryByHook("fill-geometry-type-geometry")).text(this.type.geometry || "");
- $(this.queryByHook("fill-geometry-td-mass")).text(this.type.mass || "");
- $(this.queryByHook("fill-geometry-td-vol")).text(this.type.volume || "");
- $(this.queryByHook("fill-geometry-td-rho")).text(this.type.rho || "");
- $(this.queryByHook("fill-geometry-td-nu")).text(this.type.nu || "0");
- $(this.queryByHook("fill-geometry-td-c")).text(this.type.c || "");
- $(this.queryByHook("fill-geometry-td-fixed")).prop('checked', this.type.fixed || false);
- },
- updateValid: function () {},
- subviews: {
- xMinLimInputView: {
- hook: "fill-geometry-x-lim-min",
- prepareView: function (el) {
- return new InputView({
- parent: this,
- required: true,
- name: 'x-lim-min',
- valueType: 'number',
- value: this.data.xmin
- });
- }
- },
- yMinLimInputView: {
- hook: "fill-geometry-y-lim-min",
- prepareView: function (el) {
- return new InputView({
- parent: this,
- required: true,
- name: 'y-lim-min',
- valueType: 'number',
- value: this.data.ymin
- });
- }
- },
- zMinLimInputView: {
- hook: "fill-geometry-z-lim-min",
- prepareView: function (el) {
- return new InputView({
- parent: this,
- required: true,
- name: 'z-lim-min',
- valueType: 'number',
- value: this.data.zmin
- });
- }
- },
- xMaxLimInputView: {
- hook: "fill-geometry-x-lim-max",
- prepareView: function (el) {
- return new InputView({
- parent: this,
- required: true,
- name: 'x-lim-max',
- valueType: 'number',
- value: this.data.xmax
- });
- }
- },
- yMaxLimInputView: {
- hook: "fill-geometry-y-lim-max",
- prepareView: function (el) {
- return new InputView({
- parent: this,
- required: true,
- name: 'y-lim-max',
- valueType: 'number',
- value: this.data.ymax
- });
- }
- },
- zMaxLimInputView: {
- hook: "fill-geometry-z-lim-max",
- prepareView: function (el) {
- return new InputView({
- parent: this,
- required: true,
- name: 'z-lim-max',
- valueType: 'number',
- value: this.data.zmax
- });
- }
- },
- deltaxInputView: {
- hook: "fill-geometry-deltax",
- prepareView: function (el) {
- return new InputView({
- parent: this,
- required: true,
- name: 'deltax',
- valueType: 'number',
- value: this.data.deltax
- });
- }
- },
- deltayInputView: {
- hook: "fill-geometry-deltay",
- prepareView: function (el) {
- return new InputView({
- parent: this,
- required: true,
- name: 'deltay',
- valueType: 'number',
- value: this.data.deltay
- });
- }
- },
- deltazInputView: {
- hook: "fill-geometry-deltaz",
- prepareView: function (el) {
- return new InputView({
- parent: this,
- required: true,
- name: 'deltaz',
- valueType: 'number',
- value: this.data.deltaz
- });
- }
- },
- }
-});
\ No newline at end of file
diff --git a/client/domain-view/views/import-mesh-view.js b/client/domain-view/views/import-mesh-view.js
deleted file mode 100644
index 29bf950f57..0000000000
--- a/client/domain-view/views/import-mesh-view.js
+++ /dev/null
@@ -1,187 +0,0 @@
-/*
-StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 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 .
-*/
-
-let $ = require('jquery');
-let path = require('path');
-//support files
-let app = require('../../app');
-let tests = require('../../views/tests');
-//views
-let View = require('ampersand-view');
-let InputView = require('../../views/input');
-let SelectView = require('ampersand-select-view');
-//templates
-let template = require('../templates/importMeshView.pug');
-
-module.exports = View.extend({
- template: template,
- events: {
- 'change #meshfile' : 'setMeshFile',
- 'change #typefile' : 'setTypeFile',
- 'change [data-hook=import-mesh-type-select]' : 'handleTypeUpdate',
- 'change [data-target=import-mesh-trans]' : 'handleTransformationUpdate',
- 'click [data-hook=collapse-import-mesh-advanced]' : 'changeCollapseButtonText',
- 'click [data-hook=import-mesh-particles]' : 'handleImportMesh'
- },
- initialize: function (attrs, options) {
- View.prototype.initialize.apply(this, arguments);
- this.meshFile = null;
- this.typeFile = null;
- this.data = {"type": null, "transformation": null};
- this.transformation = [0, 0, 0];
- },
- render: function (attrs, options) {
- View.prototype.render.apply(this, arguments);
- this.type = this.parent.model.types.get(0, "typeID");
- this.updateTypeDefaults();
- },
- changeCollapseButtonText: function (e) {
- app.changeCollapseButtonText(this, e);
- },
- completeAction: function () {
- $(this.queryByHook("imp-in-progress")).css("display", "none");
- $(this.queryByHook("imp-complete")).css("display", "inline-block");
- setTimeout(() => {
- $(this.queryByHook("imp-complete")).css("display", "none");
- }, 5000);
- },
- errorAction: function (action) {
- $(this.queryByHook("imp-in-progress")).css("display", "none");
- $(this.queryByHook("imp-action-error")).text(action);
- $(this.queryByHook("imp-error")).css("display", "block");
- },
- handleImportMesh: function () {
- this.startAction();
- this.transformation.every((value) => {
- if(value !== 0) {
- this.data.transformation = this.transformation;
- return false;
- }
- return true;
- });
- let formData = new FormData();
- formData.append("datafile", this.meshFile);
- formData.append("particleData", JSON.stringify(this.data));
- if(this.typeFile) {
- formData.append("typefile", this.typeFile);
- }
- let endpoint = path.join(app.getApiPath(), 'spatial-model/import-mesh');
- app.postXHR(endpoint, formData, {
- success: (err, response, body) => {
- body = JSON.parse(body);
- this.parent.addMeshDomain(body.limits, body.particles, body.types, this.parent.model.particles.length > 0);
- this.completeAction();
- $('html, body').animate({
- scrollTop: $("#domain-figure").offset().top
- }, 20);
- },
- error: (err, response, body) => {
- body = JSON.parse(body);
- this.errorAction(body.Message);
- }
- }, false);
- },
- handleTransformationUpdate: function (e) {
- let index = e.target.parentElement.parentElement.dataset.index;
- this.transformation[index] = Number(e.target.value);
- },
- handleTypeUpdate: function (e) {
- let typeID = Number(e.target.value);
- this.type = this.parent.model.types.get(typeID, "typeID");
- this.updateTypeDefaults();
- },
- setMeshFile: function (e) {
- this.meshFile = e.target.files[0];
- $(this.queryByHook("import-mesh-particles")).prop('disabled', !this.meshFile);
- },
- setTypeFile: function (e) {
- this.typeFile = e.target.files[0];
- },
- startAction: function () {
- $(this.queryByHook("imp-complete")).css("display", "none");
- $(this.queryByHook("imp-error")).css("display", "none");
- $(this.queryByHook("imp-in-progress")).css("display", "inline-block");
- },
- update: function () {},
- updateValid: function () {},
- updateTypeDefaults: function () {
- this.data.typeID = this.type.typeID;
- this.data.type = {
- "type_id": this.type.name, "mass": this.type.mass, "rho": this.type.rho,
- "nu": this.type.nu, "c": this.type.c, "fixed": this.type.fixed
- }
- $(this.queryByHook("import-mesh-td-mass")).text(this.type.mass);
- $(this.queryByHook("import-mesh-td-vol")).text(this.type.volume);
- $(this.queryByHook("import-mesh-td-rho")).text(this.type.rho);
- $(this.queryByHook("import-mesh-td-nu")).text(this.type.nu);
- $(this.queryByHook("import-mesh-td-c")).text(this.type.c);
- $(this.queryByHook("import-mesh-td-fixed")).prop('checked', this.type.fixed);
- },
- subviews: {
- typeSelectView: {
- hook: "import-mesh-type-select",
- prepareView: function (el) {
- return new SelectView({
- name: 'type',
- required: true,
- idAttribute: 'typeID',
- textAttribute: 'name',
- eagerValidate: true,
- options: this.parent.model.types,
- value: this.type
- });
- }
- },
- xTransInputView: {
- hook: "import-mesh-x-trans",
- prepareView: function (el) {
- return new InputView({
- parent: this,
- required: true,
- name: 'x-transformation',
- valueType: 'number',
- value: this.transformation[0]
- });
- }
- },
- yTransInputView: {
- hook: "import-mesh-y-trans",
- prepareView: function (el) {
- return new InputView({
- parent: this,
- required: true,
- name: 'y-transformation',
- valueType: 'number',
- value: this.transformation[1]
- });
- }
- },
- zTransInputView: {
- hook: "import-mesh-z-trans",
- prepareView: function (el) {
- return new InputView({
- parent: this,
- required: true,
- name: 'z-transformation',
- valueType: 'number',
- value: this.transformation[2]
- });
- }
- }
- }
-});
\ No newline at end of file
diff --git a/client/domain-view/views/limits-view.js b/client/domain-view/views/limits-view.js
index 09f740b160..c1c78089a8 100644
--- a/client/domain-view/views/limits-view.js
+++ b/client/domain-view/views/limits-view.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -74,7 +74,12 @@ module.exports = View.extend({
required: true,
name: 'x-lim-min',
valueType: 'number',
- value: this.model.x_lim[0] || 0
+ value: this.model.x_lim[0] || 0,
+ tests: [(value) => {
+ if(value > this.model.x_lim[1]) {
+ return "X-Min cannot be greater than X-Max";
+ }
+ }]
});
app.registerRenderSubview(this, this.xMinLimitInputView, "x-lim-min");
},
@@ -87,7 +92,12 @@ module.exports = View.extend({
required: true,
name: 'y-lim-min',
valueType: 'number',
- value: this.model.y_lim[0] || 0
+ value: this.model.y_lim[0] || 0,
+ tests: [(value) => {
+ if(value > this.model.y_lim[1]) {
+ return "Y-Min cannot be greater than Y-Max";
+ }
+ }]
});
app.registerRenderSubview(this, this.yMinLimitInputView, "y-lim-min");
},
@@ -100,7 +110,12 @@ module.exports = View.extend({
required: true,
name: 'z-lim-min',
valueType: 'number',
- value: this.model.z_lim[0] || 0
+ value: this.model.z_lim[0] || 0,
+ tests: [(value) => {
+ if(value > this.model.z_lim[1]) {
+ return "Z-Min cannot be greater than Z-Max";
+ }
+ }]
});
app.registerRenderSubview(this, this.zMinLimitInputView, "z-lim-min");
},
@@ -113,7 +128,12 @@ module.exports = View.extend({
required: true,
name: 'x-lim-max',
valueType: 'number',
- value: this.model.x_lim[1] || 0
+ value: this.model.x_lim[1] || 0,
+ tests: [(value) => {
+ if(value < this.model.x_lim[0]) {
+ return "X-Max cannot be less than X-Min";
+ }
+ }]
});
app.registerRenderSubview(this, this.xMaxLimitInputView, "x-lim-max");
},
@@ -126,7 +146,12 @@ module.exports = View.extend({
required: true,
name: 'y-lim-max',
valueType: 'number',
- value: this.model.y_lim[1] || 0
+ value: this.model.y_lim[1] || 0,
+ tests: [(value) => {
+ if(value < this.model.y_lim[0]) {
+ return "Y-Max cannot be less than Y-Min";
+ }
+ }]
});
app.registerRenderSubview(this, this.yMaxLimitInputView, "y-lim-max");
},
@@ -139,7 +164,12 @@ module.exports = View.extend({
required: true,
name: 'z-lim-max',
valueType: 'number',
- value: this.model.z_lim[1] || 0
+ value: this.model.z_lim[1] || 0,
+ tests: [(value) => {
+ if(value < this.model.z_lim[0]) {
+ return "Z-Max cannot be less than Z-Min";
+ }
+ }]
});
app.registerRenderSubview(this, this.zMaxLimitInputView, "z-lim-max");
},
diff --git a/client/domain-view/views/particle-view.js b/client/domain-view/views/particle-view.js
index 87f1a0b424..8b73b3f620 100644
--- a/client/domain-view/views/particle-view.js
+++ b/client/domain-view/views/particle-view.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -19,6 +19,7 @@ along with this program. If not, see .
let $ = require('jquery');
//support files
let app = require('../../app');
+let tests = require('../../views/tests');
//views
let View = require('ampersand-view');
let InputView = require('../../views/input');
@@ -28,202 +29,211 @@ let template = require('../templates/editParticleView.pug');
module.exports = View.extend({
template: template,
- events: function () {
- let events = {};
- events[`change [data-hook=particle-type-${this.viewIndex}]`] = 'handleSelectType';
- events[`change [data-target=location-${this.viewIndex}]`] = 'handleUpdateLocation';
- events[`change [data-hook=particle-fixed=${this.viewIndex}]`] = 'handleUpdateFixed';
- return events;
+ events: {
+ 'change [data-target=new-point]' : 'setNewPoint',
+ 'change [data-hook=particle-type]' : 'setParticleType',
+ 'change [data-hook=particle-fixed]' : 'setParticleFixed'
},
initialize: function (attrs, options) {
View.prototype.initialize.apply(this, arguments);
this.viewIndex = attrs.viewIndex;
this.defaultType = attrs.defaultType;
this.disable = attrs.disable ? attrs.disable : false;
- this.origType = this.model.type;
+ this.origType = this.model.typeID;
this.origPoint = JSON.parse(JSON.stringify(this.model.point));
},
render: function (attrs, options) {
View.prototype.render.apply(this, arguments);
this.renderTypeSelectView();
this.renderParticleProperties();
- this.renderXCoordView();
- this.renderYCoordView();
- this.renderZCoordView();
+ $(this.queryByHook('particle-fixed')).prop('disabled', this.disable);
},
- handleSelectType: function (e) {
- this.defaultType = this.parent.model.types.get(this.model.type, "typeID")
- this.model.type = Number(e.target.value);
- let type = this.parent.model.types.get(this.model.type, "typeID")
- if(this.model.mass === this.defaultType.mass) {
- this.model.mass = type.mass;
+ renderDensityPropertyView: function () {
+ if(this.densityPropertyView) {
+ this.densityPropertyView.remove();
}
- if(this.model.volume === this.defaultType.volume) {
- this.model.volume = type.volume;
- }
- if(this.model.rho === this.defaultType.rho) {
- this.model.rho = type.rho;
- }
- if(this.model.nu === this.defaultType.nu) {
- this.model.nu = type.nu;
- }
- if(this.model.c === this.defaultType.c) {
- this.model.c = type.c;
- }
- if(this.model.fixed === this.defaultType.fixed) {
- this.model.fixed = type.fixed;
- }
- this.renderParticleProperties();
- },
- handleUpdateFixed: function (e) {
- this.model.fixed = e.target.checked;
- },
- handleUpdateLocation: function (e) {
- let index = Number(e.target.parentElement.parentElement.dataset.index);
- this.model.point[index] = Number(e.target.value);
- },
- renderCInputView: function () {
- if(this.cInputView) {
- this.cInputView.remove();
- }
- this.cInputView = new InputView({
+ this.densityPropertyView = new InputView({
parent: this,
required: true,
- name: 'speed-of-sound',
- disabled: this.disable,
- modelKey: 'c',
+ name: 'density',
+ tests: tests.valueTests,
valueType: 'number',
- value: this.model.c
+ modelKey: 'rho',
+ value: this.model.rho
});
- app.registerRenderSubview(this, this.cInputView, `particle-c-${this.viewIndex}`);
+ let hook = "particle-rho";
+ app.registerRenderSubview(this, this.densityPropertyView, hook);
},
- renderParticleProperties: function () {
- this.renderCInputView();
- this.renderMassInputView();
- this.renderNuInputView();
- this.renderRhoInputView();
- this.renderVolumeInputView();
- $(this.queryByHook(`particle-fixed-${this.viewIndex}`)).prop('checked', this.model.fixed);
- $(this.queryByHook(`particle-fixed-${this.viewIndex}`)).prop('disabled', this.disable);
- },
- renderMassInputView: function () {
- if(this.massInputView) {
- this.massInputView.remove();
+ renderMassPropertyView: function () {
+ if(this.massPropertyView) {
+ this.massPropertyView.remove();
}
- this.massInputView = new InputView({
+ this.massPropertyView = new InputView({
parent: this,
required: true,
name: 'mass',
- disabled: this.disable,
- modelKey: 'mass',
+ tests: tests.valueTests,
valueType: 'number',
+ modelKey: 'mass',
value: this.model.mass
});
- app.registerRenderSubview(this, this.massInputView, `particle-mass-${this.viewIndex}`);
+ let hook = "particle-mass";
+ app.registerRenderSubview(this, this.massPropertyView, hook);
},
- renderNuInputView: function () {
- if(this.nuInputView) {
- this.nuInputView.remove();
- }
- this.nuInputView = new InputView({
- parent: this,
- required: true,
- name: 'viscosity',
- disabled: this.disable,
- modelKey: 'nu',
- valueType: 'number',
- value: this.model.nu
- });
- app.registerRenderSubview(this, this.nuInputView, `particle-nu-${this.viewIndex}`);
- },
- renderRhoInputView: function () {
- if(this.rhoInputView) {
- this.rhoInputView.remove();
+ renderParticleProperties: function () {
+ this.renderMassPropertyView();
+ this.renderVolumePropertyView();
+ this.renderDensityPropertyView();
+ this.renderViscosityPropertyView();
+ this.renderSOSPropertyView();
+ },
+ renderSOSPropertyView: function () {
+ if(this.sOSPropertyView) {
+ this.sOSPropertyView.remove();
}
- this.rhoInputView = new InputView({
+ this.sOSPropertyView = new InputView({
parent: this,
required: true,
- name: 'density',
- disabled: this.disable,
- modelKey: 'rho',
+ name: 'speed-of-sound',
+ tests: tests.valueTests,
valueType: 'number',
- value: this.model.rho
+ modelKey: 'c',
+ value: this.model.c
});
- app.registerRenderSubview(this, this.rhoInputView, `particle-rho-${this.viewIndex}`);
+ let hook = "particle-c";
+ app.registerRenderSubview(this, this.sOSPropertyView, hook);
},
renderTypeSelectView: function () {
if(this.typeSelectView) {
this.typeSelectView.remove();
}
+ let options = this.parent.model.types.map((type) => {
+ return [type.typeID, type.name];
+ });
this.typeSelectView = new SelectView({
name: 'type',
required: true,
- idAttribute: 'typeID',
- textAttribute: 'name',
- eagerValidate: true,
- options: this.parent.model.types,
- value: this.parent.model.types.get(this.model.type, "typeID")
+ options: options,
+ value: this.model.typeID,
});
- app.registerRenderSubview(this, this.typeSelectView, `particle-type-${this.viewIndex}`)
- $(this.queryByHook(`particle-type-${this.viewIndex}`)).find('select').prop('disabled', this.disable);
+ let hook = "particle-type";
+ app.registerRenderSubview(this, this.typeSelectView, hook);
+ $(this.queryByHook(hook)).find('select').prop('disabled', this.disable);
},
- renderVolumeInputView: function () {
- if(this.volumeInputView) {
- this.volumeInputView.remove();
+ renderViscosityPropertyView: function () {
+ if(this.viscosityPropertyView) {
+ this.viscosityPropertyView.remove();
}
- this.volumeInputView = new InputView({
+ this.viscosityPropertyView = new InputView({
parent: this,
required: true,
- name: 'volume',
- disabled: this.disable,
- modelKey: 'volume',
+ name: 'viscosity',
+ tests: tests.valueTests,
valueType: 'number',
- value: this.model.volume
+ modelKey: 'nu',
+ value: this.model.nu
});
- app.registerRenderSubview(this, this.volumeInputView, `particle-vol-${this.viewIndex}`);
+ let hook = "particle-nu";
+ app.registerRenderSubview(this, this.viscosityPropertyView, hook);
},
- renderXCoordView: function () {
- if(this.xCoordView) {
- this.xCoordView.remove();
+ renderVolumePropertyView: function () {
+ if(this.volumePropertyView) {
+ this.volumePropertyView.remove();
}
- this.xCoordView = new InputView({
+ this.volumePropertyView = new InputView({
parent: this,
required: true,
- name: 'x-coord',
- disabled: this.disable,
+ name: 'volume',
+ tests: tests.valueTests,
valueType: 'number',
- value: this.model.point[0]
+ modelKey: 'vol',
+ value: this.model.vol
});
- app.registerRenderSubview(this, this.xCoordView, `x-coord-${this.viewIndex}`);
+ let hook = "particle-vol";
+ app.registerRenderSubview(this, this.volumePropertyView, hook);
},
- renderYCoordView: function () {
- if(this.yCoordView) {
- this.yCoordView.remove();
- }
- this.yCoordView = new InputView({
- parent: this,
- required: true,
- name: 'y-coord',
- disabled: this.disable,
- valueType: 'number',
- value: this.model.point[1]
- });
- app.registerRenderSubview(this, this.yCoordView, `y-coord-${this.viewIndex}`);
+ setNewPoint: function (e) {
+ let key = e.target.parentElement.parentElement.dataset.name;
+ let value = Number(e.target.value);
+ this.model.newPoint[key] = value;
},
- renderZCoordView: function () {
- if(this.zCoordView) {
- this.zCoordView.remove();
- }
- this.zCoordView = new InputView({
- parent: this,
- required: true,
- name: 'z-coord',
- disabled: this.disable,
- valueType: 'number',
- value: this.model.point[2]
- });
- app.registerRenderSubview(this, this.zCoordView, `z-coord-${this.viewIndex}`);
+ setParticleFixed: function (e) {
+ this.model.fixed = !this.model.fixed;
+ },
+ setParticleType: function (e) {
+ let value = Number(e.target.value);
+ console.log(value);
+ let currType = this.parent.model.types.get(this.model.typeID, "typeID");
+ let newType = this.parent.model.types.get(value, "typeID");
+ this.updatePropertyDefaults(currType, newType);
+ this.model.typeID = value;
},
update: function (e) {},
- updateValid: function (e) {}
+ updatePropertyDefaults: function (currType, newType) {
+ if(this.model.mass === currType.mass) {
+ this.model.mass = newType.mass;
+ }
+ if(this.model.vol === currType.volume) {
+ this.model.vol = newType.volume;
+ }
+ if(this.model.rho === currType.rho) {
+ this.model.rho = newType.rho;
+ }
+ if(this.model.nu === currType.nu) {
+ this.model.nu = newType.nu;
+ }
+ if(this.model.c === currType.c) {
+ this.model.c = newType.c;
+ }
+ if(this.model.fixed === currType.fixed) {
+ this.model.fixed = newType.fixed;
+ }
+ this.renderParticleProperties();
+ $(this.queryByHook('particle-fixed')).prop('checked', this.model.fixed);
+ },
+ updateValid: function (e) {},
+ subviews: {
+ inputNewPointX: {
+ hook: 'new-point-x-container',
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ required: true,
+ name: 'new-point-x',
+ disabled: this.disable,
+ tests: [tests.nanValue],
+ valueType: 'number',
+ value: this.model.newPoint.x
+ });
+ }
+ },
+ inputNewPointY: {
+ hook: 'new-point-y-container',
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ required: true,
+ name: 'new-point-y',
+ disabled: this.disable,
+ tests: [tests.nanValue],
+ valueType: 'number',
+ value: this.model.newPoint.y
+ });
+ }
+ },
+ inputNewPointZ: {
+ hook: 'new-point-z-container',
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ required: true,
+ name: 'new-point-z',
+ disabled: this.disable,
+ tests: [tests.nanValue],
+ valueType: 'number',
+ value: this.model.newPoint.z
+ });
+ }
+ }
+ }
});
\ No newline at end of file
diff --git a/client/domain-view/views/properties-view.js b/client/domain-view/views/properties-view.js
index f45575c57c..9b030d8eec 100644
--- a/client/domain-view/views/properties-view.js
+++ b/client/domain-view/views/properties-view.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -114,8 +114,7 @@ module.exports = View.extend({
required: true,
name: 'gravity-x',
valueType: 'number',
- value: this.model.gravity[0],
- label: "X: "
+ value: this.model.gravity[0]
});
app.registerRenderSubview(this, this.xGravityInputView, "gravity-x");
},
@@ -128,8 +127,7 @@ module.exports = View.extend({
required: true,
name: 'gravity-y',
valueType: 'number',
- value: this.model.gravity[1],
- label: "Y: "
+ value: this.model.gravity[1]
});
app.registerRenderSubview(this, this.yGravityInputView, "gravity-y");
},
@@ -142,8 +140,7 @@ module.exports = View.extend({
required: true,
name: 'gravity-z',
valueType: 'number',
- value: this.model.gravity[2],
- label: "Z: "
+ value: this.model.gravity[2]
});
app.registerRenderSubview(this, this.zGravityInputView, "gravity-z");
},
diff --git a/client/domain-view/views/quickview-type.js b/client/domain-view/views/quickview-type.js
index 1e0f17bc6c..dd986ff802 100644
--- a/client/domain-view/views/quickview-type.js
+++ b/client/domain-view/views/quickview-type.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/domain-view/views/shape-view.js b/client/domain-view/views/shape-view.js
new file mode 100644
index 0000000000..4aa4450146
--- /dev/null
+++ b/client/domain-view/views/shape-view.js
@@ -0,0 +1,363 @@
+/*
+StochSS is a platform for simulating biochemical systems
+Copyright (C) 2019-2022 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 .
+*/
+let $ = require('jquery');
+let _ = require('underscore');
+//support files
+let app = require('../../app');
+let tests = require('../../views/tests');
+//views
+let View = require('ampersand-view');
+let InputView = require('../../views/input');
+let SelectView = require('ampersand-select-view');
+//templates
+let editTemplate = require('../templates/editShape.pug');
+let viewTemplate = require('../templates/viewShape.pug');
+
+module.exports = View.extend({
+ events: {
+ 'change [data-hook=input-name-container]' : 'updateDepsOptions',
+ 'change [data-hook=select-geometry-container]' : 'selectGeometryType',
+ 'change [data-hook=input-formula-container]' : 'updateGeometriesInUse',
+ 'change [data-hook=select-lattice-container]' : 'selectLatticeType',
+ 'change [data-target=shape-property]' : 'updateViewer',
+ 'click [data-hook=select-shape]' : 'selectShape',
+ 'click [data-hook=fillable-shape]' : 'selectFillable',
+ 'click [data-hook=remove]' : 'removeShape'
+ },
+ bindings: {
+ 'model.selected' : {
+ type: function (el, value, previousValue) {
+ el.checked = value;
+ },
+ hook: 'select-shape'
+ },
+ 'model.fillable' : {
+ type: function (el, value, previousValue) {
+ el.checked = value;
+ },
+ hook: 'fillable-shape'
+ },
+ 'model.notEditable' : {
+ hook: 'select-shape',
+ type: function (el, value, previousValue) {
+ el.disabled = !value;
+ },
+ name: 'disabled'
+ },
+ 'model.inUse': {
+ hook: 'remove',
+ type: 'booleanAttribute',
+ name: 'disabled'
+ }
+ },
+ initialize: function (attrs, options) {
+ View.prototype.initialize.apply(this, arguments);
+ this.viewMode = attrs.viewMode ? attrs.viewMode : false;
+ },
+ render: function () {
+ this.template = this.viewMode ? viewTemplate : editTemplate;
+ View.prototype.render.apply(this, arguments);
+ this.details = {
+ 'Cartesian Lattice': [
+ $(this.queryByHook('cartesian-lattice-props'))
+ ],
+ 'Spherical Lattice': [
+ $(this.queryByHook('circular-lattice-props'))
+ ],
+ 'Cylindrical Lattice': [
+ $(this.queryByHook('circular-lattice-props')),
+ $(this.queryByHook('cylinder-length-header')),
+ $(this.queryByHook('cylinder-length-feild'))
+ ]
+ }
+ app.documentSetup();
+ if(!this.viewMode){
+ if(this.model.selected) {
+ setTimeout(_.bind(this.openDetails, this), 1);
+ }
+ this.renderFormulaInputView();
+ this.displayDetails();
+ }else if(this.model.fillable) {
+ setTimeout(_.bind(this.openDetails, this), 1);
+ this.displayDetails();
+ }
+ },
+ displayDetails: function () {
+ this.details[this.model.lattice].forEach((element) => {
+ element.css('display', 'block');
+ });
+ },
+ hideDetails: function () {
+ this.details[this.model.lattice].forEach((element) => {
+ element.css('display', 'none');
+ });
+ },
+ openDetails: function () {
+ $(this.queryByHook("view-collapse-shape-details" + this.model.cid)).collapse("show");
+ },
+ removeShape: function () {
+ let name = this.model.name;
+ let actions = this.model.collection.parent.actions;
+ this.collection.removeShape(this.model);
+ actions.trigger('update-shape-options', {currName: name});
+ },
+ renderFormulaInputView: function () {
+ if(this.formulaInputView) {
+ this.formulaInputView.remove();
+ }
+ let placeholder = this.model.type === "Standard" ?
+ "-- Boolean Formula in terms of 'x', 'y', 'z' --" :
+ "-- Boolean Formula in terms of other shapes --";
+ this.formulaInputView = new InputView({
+ parent: this, required: this.model.type === "Combinatory", name: 'formula', modelKey: 'formula',
+ valueType: 'string', value: this.model.formula, placeholder: placeholder
+ });
+ let hook = 'input-formula-container';
+ app.registerRenderSubview(this, this.formulaInputView, hook);
+ },
+ selectGeometryType: function (e) {
+ this.model.type = e.target.value;
+ this.renderFormulaInputView();
+ this.updateViewer();
+ },
+ selectShape: function () {
+ this.model.selected = !this.model.selected;
+ },
+ selectFillable: function () {
+ this.model.fillable = !this.model.fillable;
+ if(!this.model.fillable && this.model.selected) {
+ this.model.selected = false;
+ $(this.queryByHook("edit-collapse-shape-details" + this.model.cid)).collapse("hide");
+ }
+ this.updateViewer();
+ this.model.collection.parent.actions.trigger('update-shape-options', {currName: name});
+ },
+ selectLatticeType: function (e) {
+ this.hideDetails();
+ this.model.lattice = e.target.value;
+ this.displayDetails();
+ this.updateViewer();
+ },
+ update: function () {},
+ updateDepsOptions: function (e) {
+ let name = this.model.name;
+ this.model.name = e.target.value;
+ this.updateViewer();
+ this.model.collection.parent.actions.trigger(
+ 'update-shape-options', {currName: name, newName: this.model.name}
+ );
+ },
+ updateGeometriesInUse: function () {
+ if(this.model.type === "Combinatory") {
+ this.model.collection.parent.trigger('update-shape-deps');
+ }
+ this.updateViewer();
+ },
+ updateValid: function () {},
+ updateViewer: function () {
+ this.parent.renderViewShapesView();
+ },
+ subviews: {
+ inputName: {
+ hook: 'input-name-container',
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ required: true,
+ name: 'name',
+ tests: tests.nameTests,
+ valueType: 'string',
+ value: this.model.name
+ });
+ }
+ },
+ selectGeometry: {
+ hook: 'select-geometry-container',
+ prepareView: function (el) {
+ let options = [
+ 'Standard',
+ 'Combinatory'
+ ];
+ return new SelectView({
+ name: 'geometry',
+ required: true,
+ idAttributes: 'cid',
+ options: options,
+ value: this.model.type
+ });
+ }
+ },
+ selectLattice: {
+ hook: 'select-lattice-container',
+ prepareView: function (el) {
+ let options = [
+ 'Cartesian Lattice',
+ 'Spherical Lattice',
+ 'Cylindrical Lattice'
+ ];
+ return new SelectView({
+ name: 'lattice',
+ required: true,
+ idAttributes: 'cid',
+ options: options,
+ value: this.model.lattice
+ });
+ }
+ },
+ lengthInputView: {
+ hook: "length-container",
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ required: true,
+ name: 'length',
+ modelKey: 'length',
+ valueType: 'number',
+ value: this.model.length,
+ tests: tests.valueTests
+ });
+ }
+ },
+ heightInputView: {
+ hook: "height-container",
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ required: false,
+ name: 'height',
+ modelKey: 'height',
+ valueType: 'number',
+ value: this.model.height,
+ tests: tests.valueTests
+ });
+ }
+ },
+ depthInputView: {
+ hook: "depth-container",
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ required: false,
+ name: 'depth',
+ modelKey: 'depth',
+ valueType: 'number',
+ value: this.model.depth,
+ tests: tests.valueTests
+ });
+ }
+ },
+ deltaxInputView: {
+ hook: "delta-x-container",
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ required: true,
+ name: 'deltax',
+ modelKey: 'deltax',
+ valueType: 'number',
+ value: this.model.deltax,
+ tests: tests.valueTests
+ });
+ }
+ },
+ deltayInputView: {
+ hook: "delta-y-container",
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ required: false,
+ name: 'deltay',
+ modelKey: 'deltay',
+ valueType: 'number',
+ value: this.model.deltay,
+ tests: tests.valueTests
+ });
+ }
+ },
+ deltazInputView: {
+ hook: "delta-z-container",
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ required: false,
+ name: 'deltaz',
+ modelKey: 'deltaz',
+ valueType: 'number',
+ value: this.model.deltaz,
+ tests: tests.valueTests
+ });
+ }
+ },
+ radiusInputView: {
+ hook: "radius-container",
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ required: true,
+ name: 'radius',
+ modelKey: 'radius',
+ valueType: 'number',
+ value: this.model.radius,
+ tests: tests.valueTests
+ });
+ }
+ },
+ cylinderLengthInputView: {
+ hook: "cylinder-length-container",
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ required: true,
+ name: 'length',
+ modelKey: 'length',
+ valueType: 'number',
+ value: this.model.length,
+ tests: tests.valueTests
+ });
+ }
+ },
+ deltasInputView: {
+ hook: "delta-s-container",
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ required: true,
+ name: 'deltas',
+ modelKey: 'deltas',
+ valueType: 'number',
+ value: this.model.deltas,
+ tests: tests.valueTests
+ });
+ }
+ },
+ deltarInputView: {
+ hook: "delta-r-container",
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ required: false,
+ name: 'deltar',
+ modelKey: 'deltar',
+ valueType: 'number',
+ value: this.model.deltar,
+ tests: tests.valueTests
+ });
+ }
+ }
+ }
+});
diff --git a/client/domain-view/views/shapes-view.js b/client/domain-view/views/shapes-view.js
new file mode 100644
index 0000000000..26aa414bc1
--- /dev/null
+++ b/client/domain-view/views/shapes-view.js
@@ -0,0 +1,101 @@
+/*
+StochSS is a platform for simulating biochemical systems
+Copyright (C) 2019-2022 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 .
+*/
+
+let $ = require('jquery');
+//support files
+let app = require('../../app');
+let Tooltips = require('../../tooltips');
+//views
+let View = require('ampersand-view');
+let EditShapesView = require('./shape-view');
+//templates
+let template = require('../templates/shapesView.pug');
+
+module.exports = View.extend({
+ template: template,
+ events: {
+ 'click [data-hook=collapse]' : 'changeCollapseButtonText',
+ 'click [data-hook=cartesian-lattice]' : 'addShape',
+ 'click [data-hook=spherical-lattice]' : 'addShape',
+ 'click [data-hook=cylindrical-lattice]' : 'addShape'
+ },
+ initialize: function (attrs, options) {
+ View.prototype.initialize.apply(this, arguments);
+ this.readOnly = attrs.readOnly ? attrs.readOnly : false;
+ this.tooltips = Tooltips.domainGeometry;
+ },
+ render: function (attrs, options) {
+ View.prototype.render.apply(this, arguments);
+ if(this.readOnly) {
+ $(this.queryByHook('shapes-edit-tab')).addClass("disabled");
+ $(".nav .disabled>a").on("click", function(e) {
+ e.preventDefault();
+ return false;
+ });
+ $(this.queryByHook('shapes-view-tab')).tab('show');
+ $(this.queryByHook('edit-shapes')).removeClass('active');
+ $(this.queryByHook('view-shapes')).addClass('active');
+ }else{
+ this.renderEditShapesView();
+ this.collection.on("update-inuse", this.updateInUse, this);
+ this.collection.parent.trigger('update-shape-deps');
+ }
+ this.renderViewShapesView();
+ },
+ addShape: function (e) {
+ let type = e.target.dataset.name;
+ let name = this.collection.addShape(type);
+ this.collection.parent.actions.trigger('update-shape-options', {currName: name});
+ },
+ changeCollapseButtonText: function (e) {
+ app.changeCollapseButtonText(this, e);
+ },
+ renderEditShapesView: function ({key=null, attr=null}={}) {
+ if(this.editShapesView) {
+ this.editShapesView.remove();
+ }
+ let options = {filter: (model) => { return model.contains(attr, key); }}
+ this.editShapesView = this.renderCollection(
+ this.collection,
+ EditShapesView,
+ this.queryByHook('edit-shapes-list'),
+ options
+ );
+ },
+ renderViewShapesView: function ({key=null, attr=null}={}) {
+ if(this.viewShapesView) {
+ this.viewShapesView.remove();
+ }
+ let options = {
+ viewOptions: {viewMode: true},
+ filter: (model) => { return model.contains(attr, key); }
+ }
+ this.viewShapesView = this.renderCollection(
+ this.collection,
+ EditShapesView,
+ this.queryByHook('view-shapes-list'),
+ options
+ );
+ },
+ updateInUse: function ({deps=null}={}) {
+ if(deps === null) { return; }
+ this.collection.forEach((shape) => {
+ shape.inUse = deps.includes(shape.name);
+ });
+ }
+});
diff --git a/client/domain-view/views/transformation-view.js b/client/domain-view/views/transformation-view.js
new file mode 100644
index 0000000000..bff8ad6e91
--- /dev/null
+++ b/client/domain-view/views/transformation-view.js
@@ -0,0 +1,550 @@
+/*
+StochSS is a platform for simulating biochemical systems
+Copyright (C) 2019-2022 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 .
+*/
+
+let $ = require('jquery');
+let _ = require('underscore');
+//support files
+let app = require('../../app');
+let tests = require('../../views/tests');
+//views
+let View = require('ampersand-view');
+let InputView = require('../../views/input');
+let SelectView = require('ampersand-select-view');
+//templates
+let editTemplate = require('../templates/editTransformation.pug');
+let viewTemplate = require('../templates/viewTransformation.pug');
+
+module.exports = View.extend({
+ bindings: {
+ 'model.selected' : {
+ type: function (el, value, previousValue) {
+ el.checked = value;
+ },
+ hook: 'select-transformation'
+ },
+ 'model.inUse': {
+ hook: 'remove',
+ type: 'booleanAttribute',
+ name: 'disabled',
+ }
+ },
+ events: {
+ 'change [data-hook=input-name-container]' : 'updateDepsOptions',
+ 'change [data-hook=select-type-container]' : 'selectTransformationType',
+ 'change [data-hook=transformation-select-container]' : 'selectNestedTransformation',
+ 'change [data-target=vector-point1]' : 'setVectorPoint1',
+ 'change [data-target=vector-point2]' : 'setVectorPoint2',
+ 'change [data-target=reflect-normal]' : 'setReflectNormal',
+ 'change [data-target=reflect-point1]' : 'setReflectPoint1',
+ 'change [data-target=reflect-point2]' : 'setReflectPoint2',
+ 'change [data-target=reflect-point3]' : 'setReflectPoint3',
+ 'change [data-target=scale-center]' : 'setCenter',
+ 'click [data-hook=select-transformation]' : 'selectTransformation',
+ 'click [data-hook=remove]' : 'removeTransformation',
+ 'click [data-hook=collapsePointNormal]' : 'togglePointNormal',
+ 'click [data-hook=collapseThreePoint]' : 'toggleThreePoint'
+ },
+ initialize: function (attrs, options) {
+ View.prototype.initialize.apply(this, arguments);
+ this.viewMode = attrs.viewMode ? attrs.viewMode : false;
+ this.chevrons = {
+ hide: `
+
+ `,
+ show: `
+
+ `
+ }
+ },
+ render: function () {
+ this.template = this.viewMode ? viewTemplate : editTemplate;
+ View.prototype.render.apply(this, arguments);
+ this.details = {
+ 'Translate Transformation': [
+ $(this.queryByHook('vector-transformation-props'))
+ ],
+ 'Rotate Transformation': [
+ $(this.queryByHook('vector-transformation-props')),
+ $(this.queryByHook('rotation-angle-header')),
+ $(this.queryByHook('rotation-angle-prop'))
+ ],
+ 'Reflect Transformation': [
+ $(this.queryByHook('reflect-transformation-props'))
+ ],
+ 'Scale Transformation': [
+ $(this.queryByHook('scale-transformation-props'))
+ ]
+ }
+ app.documentSetup();
+ if(this.viewMode){
+ if(this.model.point2.inUse || this.model.point3.inUse) {
+ $(this.queryByHook('view-point2-header')).css('display', 'inline-block');
+ $(this.queryByHook('view-point3-header')).css('display', 'inline-block');
+ $(this.queryByHook('view-reflect-point2')).css('display', 'inline-block');
+ $(this.queryByHook('view-reflect-point3')).css('display', 'inline-block');
+ }else{
+ $(this.queryByHook('view-normal-header')).css('display', 'inline-block');
+ $(this.queryByHook('view-reflect-normal')).css('display', 'inline-block');
+ }
+ }else{
+ if(this.model.selected) {
+ setTimeout(_.bind(this.openDetails, this), 1);
+ }
+ this.model.on('change', _.bind(this.updateViewer, this));
+ this.renderTransformationSelectView();
+ }
+ this.displayDetails();
+ },
+ displayDetails: function () {
+ this.details[this.model.type].forEach((element) => {
+ element.css('display', 'block');
+ });
+ },
+ hideDetails: function () {
+ this.details[this.model.type].forEach((element) => {
+ element.css('display', 'none');
+ });
+ },
+ openDetails: function () {
+ $("#collapse-transformation-details" + this.model.cid).collapse("show");
+ },
+ removeTransformation: function () {
+ let name = this.model.name;
+ let collection = this.model.collection;
+ let actions = this.model.collection.parent.actions;
+ collection.removeTransformation(this.model);
+ collection.trigger('update-transformation-options', {currName: name});
+ actions.trigger('update-transformation-options', {currName: name});
+ },
+ renderTransformationSelectView: function () {
+ if(this.transformationSelectView) {
+ this.transformationSelectView.remove();
+ }
+ let options = [];
+ this.model.collection.parent.transformations.forEach((transformation) => {
+ if(transformation.name !== this.model.name) {
+ options.push(transformation.name);
+ }
+ });
+ this.transformationSelectView = new SelectView({
+ name: 'transformation',
+ required: false,
+ eagerValidate: false,
+ options: options,
+ value: this.model.transformation,
+ unselectedText: '-- Select a Transformation --'
+ });
+ let hook = "transformation-select-container";
+ app.registerRenderSubview(this, this.transformationSelectView, hook);
+ },
+ selectNestedTransformation: function (e) {
+ this.model.transformation = e.target.value;
+ this.model.collection.parent.trigger('update-transformation-deps');
+ },
+ selectTransformation: function () {
+ this.model.selected = !this.model.selected;
+ },
+ selectTransformationType: function (e) {
+ this.hideDetails();
+ this.model.type = e.target.value;
+ this.displayDetails();
+ },
+ setCenter: function (e) {
+ let key = e.target.parentElement.parentElement.dataset.name;
+ this.model.center[key] = Number(e.target.value);
+ this.model.trigger('change');
+ },
+ setReflectNormal: function (e) {
+ let key = e.target.parentElement.parentElement.dataset.name;
+ this.model.normal[key] = Number(e.target.value);
+ this.model.trigger('change');
+ },
+ setReflectPoint1: function (e) {
+ let key = e.target.parentElement.parentElement.dataset.name;
+ this.model.point1[key] = Number(e.target.value);
+ this.model.trigger('change');
+ },
+ setReflectPoint2: function (e) {
+ let key = e.target.parentElement.parentElement.dataset.name;
+ this.model.point2[key] = Number(e.target.value);
+ this.model.trigger('change');
+ },
+ setReflectPoint3: function (e) {
+ let key = e.target.parentElement.parentElement.dataset.name;
+ this.model.point3[key] = Number(e.target.value);
+ this.model.trigger('change');
+ },
+ setVectorPoint1: function (e) {
+ let key = e.target.parentElement.parentElement.dataset.name;
+ this.model.vector[0][key] = Number(e.target.value);
+ this.model.trigger('change');
+ },
+ setVectorPoint2: function (e) {
+ let key = e.target.parentElement.parentElement.dataset.name;
+ this.model.vector[1][key] = Number(e.target.value);
+ this.model.trigger('change');
+ },
+ togglePointNormal: function () {
+ let classes = $(this.queryByHook('collapsePointNormal')).attr("class").split(/\s+/);
+ $(this.queryByHook('three-point-chevron')).html(this.chevrons.hide);
+ if(classes.includes('collapsed')) {
+ $(this.queryByHook('point-normal-chevron')).html(this.chevrons.show);
+ }else{
+ $(this.queryByHook('point-normal-chevron')).html(this.chevrons.hide);
+ }
+ },
+ toggleThreePoint: function () {
+ let classes = $(this.queryByHook('collapseThreePoint')).attr("class").split(/\s+/);
+ $(this.queryByHook('point-normal-chevron')).html(this.chevrons.hide);
+ if(classes.includes('collapsed')) {
+ $(this.queryByHook('three-point-chevron')).html(this.chevrons.show);
+ }else{
+ $(this.queryByHook('three-point-chevron')).html(this.chevrons.hide);
+ }
+ },
+ update: function () {},
+ updateDepsOptions: function (e) {
+ let name = this.model.name;
+ this.model.name = e.target.value;
+ this.model.collection.parent.transformations.trigger(
+ 'update-transformation-options', {currName: name, newName: this.model.name}
+ );
+ this.model.collection.parent.actions.trigger(
+ 'update-transformation-options', {currName: name, newName: this.model.name}
+ );
+ },
+ updateValid: function () {},
+ updateViewer: function () {
+ this.parent.renderViewTransformationsView();
+ },
+ subviews: {
+ inputNameView: {
+ hook: 'input-name-container',
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ required: true,
+ name: 'name',
+ tests: tests.nameTests,
+ valueType: 'string',
+ value: this.model.name
+ });
+ }
+ },
+ selectTypeView: {
+ hook: 'select-type-container',
+ prepareView: function (el) {
+ let options = [
+ 'Translate Transformation',
+ 'Rotate Transformation',
+ 'Reflect Transformation',
+ 'Scale Transformation'
+ ];
+ return new SelectView({
+ name: 'type',
+ required: true,
+ idAttributes: 'cid',
+ options: options,
+ value: this.model.type
+ });
+ }
+ },
+ vectorPoint1XInputView: {
+ hook: 'vector-point1-x-container',
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ name: 'vector-point1-x',
+ tests: [tests.nanValue],
+ valueType: 'number',
+ value: this.model.vector[0].x
+ });
+ }
+ },
+ vectorPoint1YInputView: {
+ hook: 'vector-point1-y-container',
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ name: 'vector-point1-x',
+ tests: [tests.nanValue],
+ valueType: 'number',
+ value: this.model.vector[0].y
+ });
+ }
+ },
+ vectorPoint1ZInputView: {
+ hook: 'vector-point1-z-container',
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ name: 'vector-point1-z',
+ tests: [tests.nanValue],
+ valueType: 'number',
+ value: this.model.vector[0].z
+ });
+ }
+ },
+ vectorPoint2XInputView: {
+ hook: 'vector-point2-x-container',
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ name: 'vector-point2-x',
+ tests: [tests.nanValue],
+ valueType: 'number',
+ value: this.model.vector[1].x
+ });
+ }
+ },
+ vectorPoint2YInputView: {
+ hook: 'vector-point2-y-container',
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ name: 'vector-point2-y',
+ tests: [tests.nanValue],
+ valueType: 'number',
+ value: this.model.vector[1].y
+ });
+ }
+ },
+ vectorPoint2ZInputView: {
+ hook: 'vector-point2-z-container',
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ name: 'vector-point2-z',
+ tests: [tests.nanValue],
+ valueType: 'number',
+ value: this.model.vector[1].z
+ });
+ }
+ },
+ rotationAngleInputView: {
+ hook: 'rotation-angle-container',
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ name: 'angle',
+ modelKey: 'angle',
+ tests: [tests.nanValue],
+ valueType: 'number',
+ value: this.model.angle
+ });
+ }
+ },
+ scalingFactorInputView: {
+ hook: 'scale-factor-container',
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ name: 'factor',
+ modelKey: 'factor',
+ tests: [tests.nanValue],
+ valueType: 'number',
+ value: this.model.factor
+ });
+ }
+ },
+ centerXInputView: {
+ hook: 'center-x-container',
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ name: 'center-x',
+ tests: [tests.nanValue],
+ valueType: 'number',
+ value: this.model.center.x
+ });
+ }
+ },
+ centerYInputView: {
+ hook: 'center-y-container',
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ name: 'center-y',
+ tests: [tests.nanValue],
+ valueType: 'number',
+ value: this.model.center.y
+ });
+ }
+ },
+ centerZInputView: {
+ hook: 'center-z-container',
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ name: 'center-z',
+ tests: [tests.nanValue],
+ valueType: 'number',
+ value: this.model.center.z
+ });
+ }
+ },
+ normalXInputView: {
+ hook: 'normal-x-container',
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ name: 'normal-x',
+ tests: [tests.nanValue],
+ valueType: 'number',
+ value: this.model.normal.x
+ });
+ }
+ },
+ normalYInputView: {
+ hook: 'normal-y-container',
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ name: 'normal-y',
+ tests: [tests.nanValue],
+ valueType: 'number',
+ value: this.model.normal.y
+ });
+ }
+ },
+ normalZInputView: {
+ hook: 'normal-z-container',
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ name: 'normal-z',
+ tests: [tests.nanValue],
+ valueType: 'number',
+ value: this.model.normal.z
+ });
+ }
+ },
+ point1XInputView: {
+ hook: 'point1-x-container',
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ name: 'point1-x',
+ tests: [tests.nanValue],
+ valueType: 'number',
+ value: this.model.point1.x
+ });
+ }
+ },
+ point1YInputView: {
+ hook: 'point1-y-container',
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ name: 'point1-y',
+ tests: [tests.nanValue],
+ valueType: 'number',
+ value: this.model.point1.y
+ });
+ }
+ },
+ point1ZInputView: {
+ hook: 'point1-z-container',
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ name: 'point1-z',
+ tests: [tests.nanValue],
+ valueType: 'number',
+ value: this.model.point1.z
+ });
+ }
+ },
+ point2XInputView: {
+ hook: 'point2-x-container',
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ name: 'point2-x',
+ tests: [tests.nanValue],
+ valueType: 'number',
+ value: this.model.point2.x
+ });
+ }
+ },
+ point2YInputView: {
+ hook: 'point2-y-container',
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ name: 'point2-y',
+ tests: [tests.nanValue],
+ valueType: 'number',
+ value: this.model.point2.y
+ });
+ }
+ },
+ point2ZInputView: {
+ hook: 'point2-z-container',
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ name: 'point2-z',
+ tests: [tests.nanValue],
+ valueType: 'number',
+ value: this.model.point2.z
+ });
+ }
+ },
+ point3XInputView: {
+ hook: 'point3-x-container',
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ name: 'point3-x',
+ tests: [tests.nanValue],
+ valueType: 'number',
+ value: this.model.point3.x
+ });
+ }
+ },
+ point3YInputView: {
+ hook: 'point3-y-container',
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ name: 'point3-y',
+ tests: [tests.nanValue],
+ valueType: 'number',
+ value: this.model.point3.y
+ });
+ }
+ },
+ point3ZInputView: {
+ hook: 'point3-z-container',
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ name: 'point3-z',
+ tests: [tests.nanValue],
+ valueType: 'number',
+ value: this.model.point3.z
+ });
+ }
+ }
+ }
+});
\ No newline at end of file
diff --git a/client/domain-view/views/transformations-view.js b/client/domain-view/views/transformations-view.js
new file mode 100644
index 0000000000..09206e4575
--- /dev/null
+++ b/client/domain-view/views/transformations-view.js
@@ -0,0 +1,115 @@
+/*
+StochSS is a platform for simulating biochemical systems
+Copyright (C) 2019-2022 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 .
+*/
+
+let $ = require('jquery');
+//support files
+let app = require('../../app');
+let Tooltips = require('../../tooltips');
+//views
+let View = require('ampersand-view');
+let EditTransformationView = require('./transformation-view');
+//templates
+let template = require('../templates/transformationsView.pug');
+
+module.exports = View.extend({
+ template: template,
+ events: {
+ 'click [data-hook=collapse]' : 'changeCollapseButtonText',
+ 'click [data-hook=translate-transformation]' : 'addTransformation',
+ 'click [data-hook=rotate-transformation]' : 'addTransformation',
+ 'click [data-hook=reflect-transformation]' : 'addTransformation',
+ 'click [data-hook=scale-transformation]' : 'addTransformation'
+ },
+ initialize: function (attrs, options) {
+ View.prototype.initialize.apply(this, arguments);
+ this.readOnly = attrs.readOnly ? attrs.readOnly : false;
+ this.tooltips = Tooltips.domainTransformation;
+ },
+ render: function (attrs, options) {
+ View.prototype.render.apply(this, arguments);
+ if(this.readOnly) {
+ $(this.queryByHook('transformations-edit-tab')).addClass("disabled");
+ $(".nav .disabled>a").on("click", function(e) {
+ e.preventDefault();
+ return false;
+ });
+ $(this.queryByHook('transformations-view-tab')).tab('show');
+ $(this.queryByHook('edit-transformations')).removeClass('active');
+ $(this.queryByHook('view-transformations')).addClass('active');
+ }else{
+ this.renderEditTransformationsView();
+ this.collection.on('update-transformation-options', this.updateTransformationOptions, this);
+ this.collection.on('update-inuse', this.updateInUse, this);
+ this.collection.parent.trigger('update-transformation-deps');
+ }
+ this.renderViewTransformationsView();
+ },
+ changeCollapseButtonText: function (e) {
+ app.changeCollapseButtonText(this, e);
+ },
+ addTransformation: function (e) {
+ let type = e.target.dataset.name;
+ let name = this.collection.addTransformation(type);
+ this.collection.trigger('update-transformation-options', {currName: name});
+ this.collection.parent.actions.trigger('update-transformation-options', {currName: name});
+ },
+ renderEditTransformationsView: function ({key=null, attr=null}={}) {
+ if(this.editTransformationsView) {
+ this.editTransformationsView.remove();
+ }
+ let options = {filter: (model) => { return model.contains(attr, key); }}
+ this.editTransformationsView = this.renderCollection(
+ this.collection,
+ EditTransformationView,
+ this.queryByHook('edit-transformations-list'),
+ options
+ );
+ },
+ renderViewTransformationsView: function ({key=null, attr=null}={}) {
+ if(this.viewTransformationsView) {
+ this.viewTransformationsView.remove();
+ }
+ let options = {
+ viewOptions: {viewMode: true},
+ filter: (model) => { return model.contains(attr, key); }
+ }
+ this.viewTransformationsView = this.renderCollection(
+ this.collection,
+ EditTransformationView,
+ this.queryByHook('view-transformations-list'),
+ options
+ );
+ },
+ updateInUse: function ({deps=null}={}) {
+ if(deps === null) { return; }
+ this.collection.forEach((transformation) => {
+ transformation.inUse = deps.includes(transformation.name);
+ });
+ },
+ updateTransformationOptions: function ({currName=null, newName=null}={}) {
+ if(currName === null && newName === null) { return; }
+ this.editTransformationsView.views.forEach((view) => {
+ if(view.model.transformation === currName && newName !== null) {
+ view.model.transformation = newName
+ }
+ if((newName === null && view.model.name !== currName) || view.model.name !== newName) {
+ view.renderTransformationSelectView();
+ }
+ });
+ }
+});
\ No newline at end of file
diff --git a/client/domain-view/views/type-view.js b/client/domain-view/views/type-view.js
index ea2ac60f3d..789b16f060 100644
--- a/client/domain-view/views/type-view.js
+++ b/client/domain-view/views/type-view.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -37,18 +37,19 @@ module.exports = View.extend({
el.checked = value;
},
hook: 'select'
+ },
+ 'model.inUse': {
+ hook: 'remove',
+ type: 'booleanAttribute',
+ name: 'disabled',
}
},
events: {
- 'change [data-hook=type-name]' : 'handleRenameType',
- 'change [data-target=type-defaults]' : 'updateView',
+ 'change [data-hook=type-name]' : 'updateDepsOptions',
+ 'change [data-target=type-defaults]' : 'updateViewer',
'change [data-hook=td-fixed]' : 'setTDFixed',
- 'change [data-hook=type-geometry]' : 'updateView',
'click [data-hook=select]' : 'selectType',
- 'click [data-hook=unassign-all]' : 'handleUnassignParticles',
- 'click [data-hook=delete-type]' : 'handleDeleteType',
- 'click [data-hook=delete-all]' : 'handleDeleteTypeAndParticle',
- 'click [data-hook=apply-geometry]' : 'handleApplyGeometry'
+ 'click [data-hook=remove]' : 'handleDeleteType',
},
initialize: function (attrs, options) {
View.prototype.initialize.apply(this, arguments);
@@ -66,66 +67,13 @@ module.exports = View.extend({
$(this.queryByHook('view-td-fixed')).prop('checked', this.model.fixed)
app.documentSetup();
},
- completeAction: function () {
- $(this.queryByHook(`tg-in-progress-${this.model.typeID}`)).css("display", "none");
- $(this.queryByHook(`tg-complete-${this.model.typeID}`)).css("display", "inline-block");
- setTimeout(() => {
- $(this.queryByHook(`tg-complete-${this.model.typeID}`)).css("display", "none");
- }, 5000);
- },
- errorAction: function (action) {
- $(this.queryByHook(`tg-in-progress-${this.model.typeID}`)).css("display", "none");
- $(this.queryByHook(`tg-action-error-${this.model.typeID}`)).html(action);
- $(this.queryByHook(`tg-error-${this.model.typeID}`)).css("display", "block");
- },
- handleApplyGeometry: function (e) {
- this.startAction();
- let domain = this.model.collection.parent;
- let particles = domain.particles.toJSON();
- let center = [
- (domain.x_lim[1] + domain.x_lim[0]) / 2,
- (domain.y_lim[1] + domain.y_lim[0]) / 2,
- (domain.z_lim[1] + domain.z_lim[0]) / 2
- ];
- let data = {particles: particles, type: this.model.toJSON(), center: center}
- let endpoint = path.join(app.getApiPath(), 'spatial-model/apply-geometry');
- app.postXHR(endpoint, data, {
- success: (err, response, body) => {
- this.parent.parent.applyGeometry(body.particles, this.model);
- this.completeAction();
- },
- error: (err, response, body) => {
- if(body.Traceback.includes("SyntaxError")) {
- var tracePart = body.Traceback.split('\n').slice(6)
- tracePart.splice(2, 2)
- tracePart[1] = tracePart[1].replace(new RegExp(' ', 'g'), ' ')
- var errorBlock = `${body.Message}
${tracePart.join('
')}
`
- }else{
- var errorBlock = body.Message
- }
- this.errorAction(errorBlock);
- }
- });
- },
handleDeleteType: function (e) {
- let type = Number(e.target.dataset.type);
- this.model.collection.removeType(this.model);
- this.parent.parent.deleteType(this.model.typeID);
- },
- handleDeleteTypeAndParticle: function (e) {
- let type = Number(e.target.dataset.type);
+ let typeID = this.model.typeID;
+ let domain = this.model.collection.parent;
+ let actions = domain.actions;
this.model.collection.removeType(this.model);
- this.parent.parent.deleteType(this.model.typeID, {unassign: false});
- },
- handleRenameType: function (e) {
- this.updateView();
- let type = Number(e.target.parentElement.parentElement.dataset.target);
- let name = e.target.value;
- this.parent.parent.renameType(type, name);
- },
- handleUnassignParticles: function () {
- this.model.numParticles = 0;
- this.parent.parent.unassignAllParticles(this.model.typeID);
+ domain.trigger('update-particle-type-options', {currName: typeID});
+ actions.trigger('update-type-options', {currName: typeID});
},
openTypeDetails: function () {
$("#collapse-type-details" + this.model.typeID).collapse("show");
@@ -135,28 +83,32 @@ module.exports = View.extend({
},
setTDFixed: function (e) {
this.model.fixed = e.target.checked;
- this.updateView();
- },
- startAction: function () {
- $(this.queryByHook(`tg-complete-${this.model.typeID}`)).css("display", "none");
- $(this.queryByHook(`tg-error-${this.model.typeID}`)).css("display", "none");
- $(this.queryByHook(`tg-in-progress-${this.model.typeID}`)).css("display", "inline-block");
+ this.updateViewer();
},
update: function () {},
+ updateDepsOptions: function (e) {
+ let typeID = this.model.typeID;
+ this.model.name = e.target.value;
+ this.updateViewer();
+ this.model.collection.parent.trigger(
+ 'update-particle-type-options', {currName: typeID, newName: this.model.typeID}
+ );
+ this.model.collection.parent.actions.trigger(
+ 'update-type-options', {currName: typeID, newName: this.model.typeID}
+ );
+ },
updateValid: function () {},
- updateView: function () {
+ updateViewer: function () {
this.parent.renderViewTypeView();
- this.parent.parent.updateParticleViews({includeGeometry: true});
},
subviews: {
- inputTypeID: {
+ inputName: {
hook: "type-name",
prepareView: function (el) {
return new InputView({
parent: this,
required: true,
name: 'name',
- modelKey: 'name',
tests: [tests.invalidChar],
valueType: 'string',
value: this.model.name
@@ -232,20 +184,6 @@ module.exports = View.extend({
value: this.model.c
});
}
- },
- inputGeometry: {
- hook: 'type-geometry',
- prepareView: function (el) {
- return new InputView({
- parent: this,
- required: false,
- name: 'geometry',
- modelKey: 'geometry',
- valueType: 'string',
- value: this.model.geometry,
- placeholder: "--Expression in terms of 'x', 'y', 'z'--"
- });
- }
}
}
});
\ No newline at end of file
diff --git a/client/domain-view/views/types-description-view.js b/client/domain-view/views/types-description-view.js
deleted file mode 100644
index 2279ff68ee..0000000000
--- a/client/domain-view/views/types-description-view.js
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
-StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 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 .
-*/
-
-let $ = require('jquery');
-let path = require('path');
-//support files
-let app = require('../../app');
-//views
-let View = require('ampersand-view');
-let SelectView = require('ampersand-select-view');
-//templates
-let template = require('../templates/typesDescriptionView.pug');
-
-module.exports = View.extend({
- template: template,
- events: {
- 'change [data-hook=file-select]' : 'selectTypeFile',
- 'change [data-hook=file-location-select]' : 'selectFileLocation',
- 'click [data-hook=set-particle-types-btn]' : 'getTypesFromFile'
- },
- initialize: function (attrs, options) {
- View.prototype.initialize.apply(this, arguments);
- this.typeFile = null;
- },
- render: function(attrs, options) {
- View.prototype.render.apply(this, arguments);
- let endpoint = path.join(app.getApiPath(), 'spatial-model/types-list');
- app.getXHR(endpoint, {success: (err, response, body) => {
- this.typeFiles = body.files;
- this.fileLocations = body.paths;
- this.renderFileSelectView();
- }});
- },
- completeAction: function () {
- $(this.queryByHook('st-in-progress')).css('display', 'none');
- $(this.queryByHook('st-complete')).css('display', 'inline-block');
- },
- errorAction: function (action) {
- $(this.queryByHook('st-in-progress')).css('display', 'none');
- $(this.queryByHook('st-action-error')).text(action);
- $(this.queryByHook('st-error')).css('display', 'block');
- },
- getTypesFromFile: function () {
- this.startAction();
- let queryStr = `?path=${this.typeFile}`;
- let endpoint = path.join(app.getApiPath(), 'spatial-model/particle-types') + queryStr;
- app.getXHR(endpoint, {
- success: (err, response, body) => {
- this.parent.setParticleTypes(body.names, body.types);
- this.completeAction();
- },
- error: (err, response, body) => {
- this.errorAction(body.Message);
- }
- });
- },
- renderFileLocationSelectView: function (options) {
- if(this.fileLocationSelectView) {
- this.fileLocationSelectView.remove();
- }
- this.fileLocationSelectView = new SelectView({
- name: 'type-locations',
- required: false,
- idAttributes: 'cid',
- options: options,
- unselectedText: "-- Select Location --"
- });
- app.registerRenderSubview(this, this.fileLocationSelectView, "file-location-select");
- },
- renderFileSelectView: function () {
- if(this.fileSelectView) {
- this.fileSelectView.remove();
- }
- this.fileSelectView = new SelectView({
- name: 'type-files',
- required: false,
- idAttributes: 'cid',
- options: this.typeFiles,
- unselectedText: "-- Select Type File --",
- });
- app.registerRenderSubview(this, this.fileSelectView, "file-select");
- },
- selectFileLocation: function (e) {
- let value = e.target.value;
- this.typeFile = value ? value : null;
- $(this.queryByHook("set-particle-types-btn")).prop("disabled", this.typeFile === null);
- },
- selectTypeFile: function (e) {
- let value = e.target.value;
- if(value) {
- if(this.fileLocations[value].length > 1) {
- $(this.queryByHook("type-location-message")).css('display', "block");
- $(this.queryByHook("file-location-container")).css("display", "inline-block");
- this.renderFileLocationSelectView(this.fileLocations[value]);
- this.typeFile = null;
- }else{
- $(this.queryByHook("type-location-message")).css('display', "none");
- $(this.queryByHook("file-location-container")).css("display", "none");
- this.typeFile = this.fileLocations[value][0];
- }
- }else{
- $(this.queryByHook("type-location-message")).css('display', "none");
- $(this.queryByHook("file-location-container")).css("display", "none");
- this.typeFile = null;
- }
- $(this.queryByHook("set-particle-types-btn")).prop("disabled", this.typeFile === null);
- },
- startAction: function () {
- $(this.queryByHook("st-complete")).css('display', 'none');
- $(this.queryByHook("st-error")).css('display', 'none');
- $(this.queryByHook("st-in-progress")).css("display", "inline-block");
- },
-});
\ No newline at end of file
diff --git a/client/domain-view/views/types-view.js b/client/domain-view/views/types-view.js
index b5351ed9e9..fc60f115af 100644
--- a/client/domain-view/views/types-view.js
+++ b/client/domain-view/views/types-view.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -48,8 +48,16 @@ module.exports = View.extend({
$(this.queryByHook('view-domain-types')).addClass('active');
}else{
this.renderEditTypeView();
+ this.collection.on("update-inuse", this.updateInUse, this);
+ this.collection.parent.trigger('update-type-deps');
+ this.toggleTypesCollectionError("ev");
+ this.collection.on('add remove', () => {
+ this.toggleTypesCollectionError("ev");
+ this.toggleTypesCollectionError("vv");
+ }, this);
}
this.toggleDomainError();
+ this.toggleTypesCollectionError("vv");
this.renderViewTypeView();
},
changeCollapseButtonText: function (e) {
@@ -57,7 +65,15 @@ module.exports = View.extend({
},
handleAddDomainType: function () {
let name = this.collection.addType();
- this.parent.addType(name);
+ this.collection.parent.trigger('update-particle-type-options', {currName: name});
+ this.collection.parent.actions.trigger('update-type-options', {currName: name});
+ },
+ openSection: function () {
+ if(!$(this.queryByHook("domain-types-section")).hasClass("show")) {
+ let typesCollapseBtn = $(this.queryByHook("collapse-domain-types"));
+ typesCollapseBtn.click();
+ typesCollapseBtn.html('-');
+ }
},
renderEditTypeView: function () {
if(this.editTypeView) {
@@ -93,13 +109,47 @@ module.exports = View.extend({
);
},
toggleDomainError: function () {
- let errorMsg = $(this.queryByHook('domain-error'))
- if(!this.collection.parent.valid) {
+ let errorMsg = $(this.queryByHook('domain-error'));
+ if(this.collection.models[0].numParticles > 0) {
+ errorMsg.addClass('component-invalid');
+ errorMsg.removeClass('component-valid');
+ }else{
+ errorMsg.addClass('component-valid');
+ errorMsg.removeClass('component-invalid');
+ }
+ },
+ toggleTypesCollectionError: function (viewCode) {
+ let errorMsg = $(this.queryByHook(`${viewCode}-types-collection-error`));
+ if(this.collection.length == 1) {
errorMsg.addClass('component-invalid');
errorMsg.removeClass('component-valid');
}else{
errorMsg.addClass('component-valid');
errorMsg.removeClass('component-invalid');
}
+ },
+ updateInUse: function ({deps=null}={}) {
+ if(deps === null) { return; }
+ this.collection.forEach((type) => {
+ type.inUse = deps.includes(type.name);
+ });
+ },
+ updateParticleCounts: function (particles) {
+ let particleCounts = {};
+ particles.forEach((particle) => {
+ if(particleCounts[particle.type]) {
+ particleCounts[particle.type] += 1;
+ }else{
+ particleCounts[particle.type] = 1;
+ }
+ });
+ this.collection.forEach((type) => {
+ type.numParticles = particleCounts[type.typeID] ? particleCounts[type.typeID] : 0;
+ });
+ $(this.queryByHook('unassigned-type-count')).text(this.collection.models[0].numParticles);
+ this.renderEditTypeView();
+ this.renderViewTypeView();
+ this.collection.parent.updateValid();
+ this.toggleDomainError();
}
});
\ No newline at end of file
diff --git a/client/domain-view/views/view-particle.js b/client/domain-view/views/view-particle.js
index 68cd5c7d7a..6aa4a89830 100644
--- a/client/domain-view/views/view-particle.js
+++ b/client/domain-view/views/view-particle.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -24,6 +24,6 @@ let template = require('../templates/viewParticle.pug');
module.exports = View.extend({
template: template,
initialize: function (attrs, options) {
- this.type = this.model.collection.parent.types.get(this.model.type, "typeID").name;
+ this.type = this.parent.model.types.get(this.model.type, "typeID").name;
}
});
\ No newline at end of file
diff --git a/client/file-config.js b/client/file-config.js
index b1541e2e74..d1db755112 100644
--- a/client/file-config.js
+++ b/client/file-config.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/graphics.js b/client/graphics.js
index 6739f854c9..aa0be73137 100644
--- a/client/graphics.js
+++ b/client/graphics.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/job-view/job-view.js b/client/job-view/job-view.js
index 5d4fd9955e..439a4684f4 100644
--- a/client/job-view/job-view.js
+++ b/client/job-view/job-view.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/job-view/templates/spatialResultsView.pug b/client/job-view/templates/spatialResultsView.pug
index d2c4aee411..98ef61d0ae 100644
--- a/client/job-view/templates/spatialResultsView.pug
+++ b/client/job-view/templates/spatialResultsView.pug
@@ -20,21 +20,47 @@ div#workflow-results.card
div(id="title" data-hook="title")
- div.inline.horizontal-space
+ div.mx-1.row.head.align-items-baseline
- span.inline(for="target-of-interest") Target of Interest:
+ div.col-sm-2
- div.tooltip-icon(data-html="true" data-toggle="tooltip" title=this.tooltips.spatialTarget)
+ h6.inline Target of Interest
- div.inline(id="target-of-interest" data-hook="target-of-interest-list")
+ div.tooltip-icon(data-html="true" data-toggle="tooltip" title=this.tooltips.spatialTarget)
- div.inline.horizontal-space.hidden(data-hook="job-results-mode")
+ div.col-sm-4.hidden(data-hook="job-results-mode-header")
+
+ h6.inline Mode
- span.inline(for="target-of-interest") Mode:
+ div.tooltip-icon(data-html="true" data-toggle="tooltip" title=this.tooltips.mode)
- div.tooltip-icon(data-html="true" data-toggle="tooltip" title=this.tooltips.mode)
+ div.col-sm-6.hidden(data-hook="spatial-trajectory-header")
- div.inline(id="target-mode" data-hook="target-mode-list")
+ h6
+
+ div.inline.mr-2 Trajectory:
+
+ div.inline(data-hook="trajectory-index-value")=this.trajectoryIndex
+
+ div.mx-1.my-3.row
+
+ div.col-sm-2
+
+ div.inline(id="target-of-interest" data-hook="target-of-interest-list")
+
+ div.col-sm-4.hidden(data-hook="job-results-mode-container")
+
+ div.inline(id="target-mode" data-hook="target-mode-list")
+
+ div.col-sm-6.hidden(data-hook="spatial-trajectory-container")
+
+ input.custom-range(
+ type="range"
+ min="1"
+ max=`${this.model.settings.simulationSettings.realizations}`
+ value=this.trajectoryIndex
+ data-hook="trajectory-index-slider"
+ )
div.card
diff --git a/client/job-view/views/job-logs-view.js b/client/job-view/views/job-logs-view.js
index 7facc18b4a..bc71947c04 100644
--- a/client/job-view/views/job-logs-view.js
+++ b/client/job-view/views/job-logs-view.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/job-view/views/job-results-view.js b/client/job-view/views/job-results-view.js
index 67676a7c2b..55f6328ac6 100644
--- a/client/job-view/views/job-results-view.js
+++ b/client/job-view/views/job-results-view.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -42,6 +42,7 @@ module.exports = View.extend({
'change [data-hook=yaxis]' : 'setYAxis',
'change [data-hook=target-of-interest-list]' : 'getPlotForTarget',
'change [data-hook=target-mode-list]' : 'getPlotForTargetMode',
+ 'change [data-hook=trajectory-index-slider]' : 'getPlotForTrajectory',
'change [data-hook=specie-of-interest-list]' : 'getPlotForSpecies',
'change [data-hook=feature-extraction-list]' : 'getPlotForFeatureExtractor',
'change [data-hook=ensemble-aggragator-list]' : 'getPlotForEnsembleAggragator',
@@ -55,7 +56,8 @@ module.exports = View.extend({
'click [data-target=download-plot-csv]' : 'handlePlotCSVClick',
'click [data-hook=convert-to-notebook]' : 'handleConvertToNotebookClick',
'click [data-hook=download-results-csv]' : 'handleFullCSVClick',
- 'click [data-hook=job-presentation]' : 'handlePresentationClick'
+ 'click [data-hook=job-presentation]' : 'handlePresentationClick',
+ 'input [data-hook=trajectory-index-slider]' : 'viewTrajectoryIndex'
},
initialize: function (attrs, options) {
View.prototype.initialize.apply(this, arguments);
@@ -65,6 +67,7 @@ module.exports = View.extend({
this.tooltips = Tooltips.jobResults;
this.plots = {};
this.plotArgs = {};
+ this.trajectoryIndex = 1;
},
render: function (attrs, options) {
let isEnsemble = this.model.settings.simulationSettings.realizations > 1 &&
@@ -113,6 +116,10 @@ module.exports = View.extend({
this.targetIndex = null;
this.targetMode = "discrete";
this.renderTargetOfInterestView();
+ if(this.model.settings.simulationSettings.realizations > 1) {
+ $(this.queryByHook("spatial-trajectory-header")).css("display", "inline-block");
+ $(this.queryByHook("spatial-trajectory-container")).css("display", "inline-block");
+ }
$(this.queryByHook("spatial-plot-csv")).css('display', 'none');
}
this.getPlot(type);
@@ -218,7 +225,7 @@ module.exports = View.extend({
}else if(type === "spatial") {
data['sim_type'] = "SpatialPy";
data['data_keys'] = {
- target: this.spatialTarget, index: this.targetIndex, mode: this.targetMode
+ target: this.spatialTarget, index: this.targetIndex, mode: this.targetMode, trajectory: this.trajectoryIndex - 1
};
data['plt_key'] = type;
}else {
@@ -259,10 +266,12 @@ module.exports = View.extend({
this.targetIndex = null;
}
if(!["type", "v", "nu", "rho", "mass"].includes(this.spatialTarget)) {
- $(this.queryByHook('job-results-mode')).css('display', 'inline-block');
+ $(this.queryByHook('job-results-mode-header')).css('display', 'inline-block');
+ $(this.queryByHook('job-results-mode-container')).css('display', 'inline-block');
this.renderTargetModeView();
}else{
- $(this.queryByHook('job-results-mode')).css('display', 'none');
+ $(this.queryByHook('job-results-mode-header')).css('display', 'none');
+ $(this.queryByHook('job-results-mode-container')).css('display', 'none');
}
this.getPlot("spatial");
},
@@ -270,6 +279,10 @@ module.exports = View.extend({
this.targetMode = e.target.value;
this.getPlot("spatial");
},
+ getPlotForTrajectory: function (e) {
+ this.trajectoryIndex = Number(e.target.value);
+ this.getPlot('spatial');
+ },
getPlotKey: function (type) {
if(type === "psweep") {
let realizations = this.model.settings.simulationSettings.realizations;
@@ -579,6 +592,9 @@ module.exports = View.extend({
},
update: function () {},
updateValid: function () {},
+ viewTrajectoryIndex: function (e) {
+ $(this.queryByHook("trajectory-index-value")).html(e.target.value);
+ },
subviews: {
inputTitle: {
hook: 'title',
diff --git a/client/job-view/views/sweep-parameter-range-view.js b/client/job-view/views/sweep-parameter-range-view.js
index 05d50e04ff..e2c1f25ed9 100644
--- a/client/job-view/views/sweep-parameter-range-view.js
+++ b/client/job-view/views/sweep-parameter-range-view.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/modals.js b/client/modals.js
index 0f711d6436..11f76f7034 100644
--- a/client/modals.js
+++ b/client/modals.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/model-view/model-view.js b/client/model-view/model-view.js
index d259bc279f..48ea47eba4 100644
--- a/client/model-view/model-view.js
+++ b/client/model-view/model-view.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -43,6 +43,7 @@ let template = require('./modelView.pug');
module.exports = View.extend({
template: template,
events: {
+ 'change [data-hook=model-filter]' : 'filterModel',
'change [data-hook=all-continuous]' : 'setDefaultMode',
'change [data-hook=all-discrete]' : 'setDefaultMode',
'change [data-hook=advanced]' : 'setDefaultMode',
@@ -134,9 +135,25 @@ module.exports = View.extend({
this.setInitialDefaultMode(modal, "dynamic");
});
},
+ filterModel: function (e) {
+ var key = e.target.value === "" ? null : e.target.value;
+ var attr = null;
+ if(key && key.includes(':')) {
+ let attrKey = key.split(':');
+ attr = attrKey[0].toLowerCase().replace(/ /g, '');
+ key = attrKey[1];
+ }
+ this.renderSpeciesView({'key': key, 'attr': attr});
+ this.renderInitialConditionsView({'key': key, 'attr': attr});
+ this.renderParametersView({'key': key, 'attr': attr});
+ this.renderReactionsView({'key': key, 'attr': attr});
+ this.renderEventsView({'key': key, 'attr': attr});
+ this.renderRulesView({'key': key, 'attr': attr});
+ this.renderBoundaryConditionsView({'key': key, 'attr': attr});
+ this.renderSbmlComponentView({'key': key, 'attr': attr});
+ },
openAdvancedSection: function () {
if(!$(this.queryByHook("me-advanced-section")).hasClass("show")) {
- console.log("opening advanced section")
let advCollapseBtn = $(this.queryByHook("collapse-mv-advanced-section"));
advCollapseBtn.click();
advCollapseBtn.html('-');
@@ -157,7 +174,8 @@ module.exports = View.extend({
process: this.reactionsView,
event: this.eventsView,
rule: this.rulesView,
- domain: this.domainViewer
+ domain: this.domainViewer,
+ initialCondition: this.initialConditionsView
}
if(["reaction", "process", "event"].includes(error.type)) {
views[error.type].openSection(error);
@@ -166,17 +184,23 @@ module.exports = View.extend({
}
}
},
- renderBoundaryConditionsView: function () {
+ renderBoundaryConditionsView: function ({key=null, attr=null}={}) {
if(!this.model.is_spatial) { return };
+ let opened = $(this.queryByHook("boundary-conditions-container")).hasClass("show")
if(this.boundaryConditionsView) {
this.boundaryConditionsView.remove();
}
this.boundaryConditionsView = new BoundaryConditionsView({
collection: this.model.boundaryConditions,
- readOnly: this.readOnly
+ readOnly: this.readOnly,
+ attr: attr,
+ key: key
});
let hook = "boundary-conditions-view-container";
app.registerRenderSubview(this, this.boundaryConditionsView, hook);
+ if(opened) {
+ this.boundaryConditionsView.openSection();
+ }
},
renderDomainViewer: function (domainPath=null) {
if(!this.model.is_spatial) { return };
@@ -192,77 +216,113 @@ module.exports = View.extend({
});
app.registerRenderSubview(this, this.domainViewer, 'domain-viewer-container');
},
- renderEventsView: function () {
+ renderEventsView: function ({key=null, attr=null}={}) {
if(this.model.is_spatial) { return };
+ let opened = $(this.queryByHook("events")).hasClass("show");
if(this.eventsView) {
this.eventsView.remove();
}
this.eventsView = new EventsView({
collection: this.model.eventsCollection,
- readOnly: this.readOnly
+ readOnly: this.readOnly,
+ attr: attr,
+ key: key
});
let hook = "events-view-container";
app.registerRenderSubview(this, this.eventsView, hook);
+ if(opened) {
+ this.eventsView.openSection();
+ }
},
- renderInitialConditionsView: function () {
+ renderInitialConditionsView: function ({key=null, attr=null}={}) {
if(!this.model.is_spatial) { return };
+ let opened = $(this.queryByHook("initial-conditions")).hasClass("show")
if(this.initialConditionsView) {
this.initialConditionsView.remove();
}
this.initialConditionsView = new InitialConditionsView({
collection: this.model.initialConditions,
- readOnly: this.readOnly
+ readOnly: this.readOnly,
+ attr: attr,
+ key: key
});
let hook = "initial-conditions-view-container";
app.registerRenderSubview(this, this.initialConditionsView, hook);
+ if(opened) {
+ this.initialConditionsView.openSection();
+ }
},
- renderParametersView: function () {
+ renderParametersView: function ({key=null, attr=null}={}) {
+ let opened = $(this.queryByHook("parameters-list-container")).hasClass("show");
if(this.parametersView) {
this.parametersView.remove();
}
this.parametersView = new ParametersView({
collection: this.model.parameters,
- readOnly: this.readOnly
+ readOnly: this.readOnly,
+ attr: attr,
+ key: key
});
let hook = "parameters-view-container";
app.registerRenderSubview(this, this.parametersView, hook);
+ if(opened) {
+ this.parametersView.openSection();
+ }
},
- renderReactionsView: function () {
+ renderReactionsView: function ({key=null, attr=null}={}) {
+ let opened = $(this.queryByHook("reactions-list-container")).hasClass("show");
if(this.reactionsView) {
this.reactionsView.remove();
}
this.reactionsView = new ReactionsView({
collection: this.model.reactions,
- readOnly: this.readOnly
+ readOnly: this.readOnly,
+ attr: attr,
+ key: key
});
let hook = "reactions-view-container";
app.registerRenderSubview(this, this.reactionsView, hook);
+ if(opened) {
+ this.reactionsView.openSection();
+ }
},
- renderRulesView: function () {
+ renderRulesView: function ({key=null, attr=null}={}) {
if(this.model.is_spatial) { return };
+ let opened = $(this.queryByHook("rules-list-container")).hasClass("show");
if(this.rulesView) {
this.rulesView.remove();
}
this.rulesView = new RulesView({
collection: this.model.rules,
- readOnly: this.readOnly
+ readOnly: this.readOnly,
+ attr: attr,
+ key: key
});
let hook = "rules-view-container";
app.registerRenderSubview(this, this.rulesView, hook);
+ if(opened) {
+ this.rulesView.openSection();
+ }
},
- renderSbmlComponentView: function () {
+ renderSbmlComponentView: function ({key=null, attr=null}={}) {
if(this.model.is_spatial || !this.model.functionDefinitions.length) { return };
+ let opened = $(this.queryByHook("function-definitions-list-container")).hasClass("show")
if(this.sbmlComponentView) {
this.sbmlComponentView.remove();
}
this.sbmlComponentView = new SBMLComponentsView({
functionDefinitions: this.model.functionDefinitions,
- readOnly: this.readOnly
+ readOnly: this.readOnly,
+ attr: attr,
+ key: key
});
let hook = "sbml-components-view-container";
app.registerRenderSubview(this, this.sbmlComponentView, hook);
+ if(opened) {
+ this.sbmlComponentView.openSection();
+ }
},
- renderSpeciesView: function () {
+ renderSpeciesView: function ({key=null, attr=null}={}) {
if(this.speciesView) {
this.speciesView.remove();
}
@@ -270,7 +330,9 @@ module.exports = View.extend({
collection: this.model.species,
spatial: this.model.is_spatial,
defaultMode: this.model.defaultMode,
- readOnly: this.readOnly
+ readOnly: this.readOnly,
+ attr: attr,
+ key: key
});
let hook = "species-view-container";
app.registerRenderSubview(this, this.speciesView, hook);
@@ -410,5 +472,18 @@ module.exports = View.extend({
updateValid: function () {},
updateVolumeViewer: function (e) {
$(this.queryByHook("view-volume")).html("Volume: " + this.model.volume);
+ },
+ subviews: {
+ filter: {
+ hook: 'model-filter',
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ required: false,
+ name: 'filter',
+ valueType: 'string'
+ });
+ }
+ }
}
});
\ No newline at end of file
diff --git a/client/model-view/modelView.pug b/client/model-view/modelView.pug
index 8fea01cd20..d03bbc6b46 100644
--- a/client/model-view/modelView.pug
+++ b/client/model-view/modelView.pug
@@ -1,5 +1,11 @@
div#model-view
+ div
+
+ h5.inline.mr-2 Filter:
+
+ div.inline(data-hook="model-filter")
+
div.card
div.card-header.pb-0
diff --git a/client/model-view/templates/boundaryConditionsView.pug b/client/model-view/templates/boundaryConditionsView.pug
index bc2792c802..d0eb9e5eaf 100644
--- a/client/model-view/templates/boundaryConditionsView.pug
+++ b/client/model-view/templates/boundaryConditionsView.pug
@@ -25,7 +25,7 @@ div#boundary-conditions.card
p.mb-0
| Set spatial regions of the domain where a property of particles are held constant.
- div.collapse.tab-content(id="collapse-boundary-conditions")
+ div.collapse.tab-content(id="collapse-boundary-conditions" data-hook="boundary-conditions-container")
div.tab-pane.active(id="edit-boundary-conditions" data-hook="edit-boundary-conditions")
diff --git a/client/model-view/templates/domainViewer.pug b/client/model-view/templates/domainViewer.pug
index 26866a60f5..eef5c604cb 100644
--- a/client/model-view/templates/domainViewer.pug
+++ b/client/model-view/templates/domainViewer.pug
@@ -20,13 +20,17 @@ div#domain-viewer.card
div.collapse(id="collapse-domain" data-hook="domain-container")
- div.card-body.tab-content
+ div.card-body.tab-content.mx-4
div(data-hook="external-domains-container")
- button.btn.btn-outline-secondary.box-shadow.mb-3(data-hook="select-external-domain") View External Domain
+ button.btn.btn-outline-primary.box-shadow.mb-3(data-hook="edit-domain-btn") Edit Domain
- div(data-hook="external-domain-select" style="display: none")
+ button.btn.btn-outline-primary.box-shadow.mb-3.ml-2(data-hook="create-domain") Build New Domain
+
+ button.btn.btn-outline-primary.box-shadow.mb-3.ml-2(data-hook="select-external-domain") View External Domain
+
+ div.hidden(data-hook="domain-interact-section")
div.text-info(data-hook="select-location-message" style="display: none")
| There are multiple domain files with that name, please select a location
@@ -41,17 +45,13 @@ div#domain-viewer.card
div.inline(data-hook="select-location")
- div(data-hook="domain-view-container")
-
- div.tab-pane.active(id="edit-domain" data-hook="edit-domain")
-
- div.mt-3
+ div.mb-3.inline
- button.btn.btn-outline-primary.box-shadow(data-hook="edit-domain-btn") Edit Domain
+ button.btn.btn-outline-primary.box-shadow.ml-2(data-hook="save-to-model" disabled) Import into Model
- button.btn.btn-outline-primary.box-shadow.ml-2(data-hook="create-domain") Build New Domain
+ div(data-hook="domain-view-container")
- button.btn.btn-outline-primary.box-shadow.ml-2(data-hook="save-to-model" disabled) Import into Model
+ div.tab-pane.active(id="edit-domain" data-hook="edit-domain")
div.tab-pane(id="view-domain" data-hook="view-domain")
diff --git a/client/model-view/templates/editEvent.pug b/client/model-view/templates/editEvent.pug
new file mode 100644
index 0000000000..882eed7a37
--- /dev/null
+++ b/client/model-view/templates/editEvent.pug
@@ -0,0 +1,127 @@
+div.mx-1
+
+ if(this.model.collection.indexOf(this.model) !== 0)
+ hr
+
+ div.row.align-items-baseline
+
+ div.col-sm-1
+
+ div.pl-3: input(type="checkbox" data-hook="select" data-toggle="collapse" data-target="#collapse-event-details" + this.model.compID)
+
+ div.col-sm-7(data-hook="event-name-container")
+
+ div.col-sm-2
+
+ div.tooltip-icon-large(data-hook="annotation-tooltip" data-html="true" data-toggle="tooltip" title=this.model.annotation || "Click 'Add' to add an annotation")
+
+ button.btn.btn-outline-secondary.btn-sm.box-shadow(data-hook="edit-annotation-btn") Edit
+
+ div.col-sm-2
+
+ button.btn.btn-outline-secondary.box-shadow(data-hook="remove") X
+
+ div.mx-1.pl-2(data-hook="event-details")
+
+ div.collapse(id="collapse-event-details" + this.model.compID)
+
+ hr
+
+ div.mx-1.row.align-items-baseline
+
+ div.col-sm-6
+
+ div
+
+ h5.inline
+
+ span(for="trigger-input") Trigger Expression:
+
+ div.tooltip-icon(data-html="true" data-hook="rate-parameter-tooltip" data-toggle="tooltip" title=this.parent.tooltips.triggerExpression)
+
+ div
+
+ div(id="event-trigger-expression" data-hook="event-trigger-expression")
+
+ div.my-1
+
+ h6.inline Advanced
+ button.btn.btn-outline-collapse(data-toggle='collapse' data-target='#advanced-events' + this.model.compID data-hook='advanced-event-button') +
+
+ div.collapse(id="advanced-events" + this.model.compID data-hook="advanced-event-section")
+
+ div.mr-2.inline
+
+ h5.inline
+
+ span(for="event-delay") Delay:
+
+ div.tooltip-icon(data-html="true" data-toggle="tooltip" title=this.parent.tooltips.delay)
+
+ div.inline(style="width: 80%")
+
+ div(id="event-delay" data-hook="event-delay")
+
+ div.mr-2.inline
+
+ h5.inline
+
+ span(for="event-trigger-expression") Priority:
+
+ div.tooltip-icon(data-html="true" data-toggle="tooltip" title=this.parent.tooltips.priority)
+
+ div.inline(style="width: 80%")
+
+ div(id="event-priority" data-hook="event-priority")
+
+ div.row
+
+ div.col-lg-6.col-md-12.verticle-space
+
+ div.mr-2.inline
+
+ h5.inline
+
+ span.checkbox(for="initial-value") Initial Value:
+
+ div.tooltip-icon(data-html="true" data-toggle="tooltip" title=this.parent.tooltips.initialValue)
+
+ input(type="checkbox" id="initial-value" data-hook="event-trigger-init-value")
+
+ div.col-lg-6.col-md-12.verticle-space
+
+ div.mr-2.inline
+
+ h5.inline
+
+ span.checkbox(for="persistent") Persistent:
+
+ div.tooltip-icon(data-html="true" data-toggle="tooltip" title=this.parent.tooltips.persistent)
+
+ input(type="checkbox" id="persistent", data-hook="event-trigger-persistent")
+
+ div.mr-2.inline
+
+ h5.inline
+
+ span.checkbox(for="use-values-from-trigger-time") Use Values From:
+
+ div.tooltip-icon(data-html="true" data-toggle="tooltip" title=this.parent.tooltips.useValuesFromTriggerTime)
+
+ div.row
+
+ div.col-lg-6.col-md-12.verticle-space
+
+ div.horizontal-space.inline
+
+ input(type="radio" name="edit-use-values-from" + this.model.compID data-hook="edit-trigger-time" data-name="trigger")
+ | Trigger Time
+
+ div.col-lg-6.col-md-12.verticle-space
+
+ div.horizontal-space.inline
+
+ input(type="radio" name="edit-use-values-from" + this.model.compID data-hook="edit-assignment-time" data-name="assignment")
+ | Assignment Time
+
+ div.col-sm-6(data-hook="assignments-container")
diff --git a/client/model-view/templates/editEventAssignment.pug b/client/model-view/templates/editEventAssignment.pug
index ecc8b48dda..1b5276ece2 100644
--- a/client/model-view/templates/editEventAssignment.pug
+++ b/client/model-view/templates/editEventAssignment.pug
@@ -15,4 +15,4 @@ div.mx-1
div.col-sm-2
- button.btn.btn-outline-secondary(data-hook="remove") X
+ button.btn.btn-outline-secondary(data-hook="remove-assignment") X
diff --git a/client/model-view/templates/editInitialCondition.pug b/client/model-view/templates/editInitialCondition.pug
index 3b29326c07..6c24123f16 100644
--- a/client/model-view/templates/editInitialCondition.pug
+++ b/client/model-view/templates/editInitialCondition.pug
@@ -16,15 +16,27 @@ div.mx-1
div.col-sm-2
div(data-hook="place-details")
+
span Location:
+
div
+
div.inline(data-hook="x-container")
+
div.inline(data-hook="y-container")
+
div.inline(data-hook="z-container")
+
div(data-hook="scatter-details")
+
span Active in Types:
+
div(data-hook="initial-condition-types")
+ div(data-hook="restict-to-error")
+
+ p.text-danger Species must be active in at least one 'Type'.
+
div.col-sm-2
div.tooltip-icon-large(data-hook="annotation-tooltip" data-html="true" data-toggle="tooltip" title=this.model.annotation || "Click 'Add' to add an annotation")
diff --git a/client/model-view/templates/editSpatialSpecie.pug b/client/model-view/templates/editSpatialSpecie.pug
index ae78da2e75..f9f21cd282 100644
--- a/client/model-view/templates/editSpatialSpecie.pug
+++ b/client/model-view/templates/editSpatialSpecie.pug
@@ -17,6 +17,10 @@ div.mx-1
div.row(data-hook="species-types")
+ div(data-hook="restict-to-error")
+
+ p.text-danger Species must be active in at least one 'Type'.
+
div.col-sm-2
div.tooltip-icon-large(data-hook="annotation-tooltip" data-html="true" data-toggle="tooltip" title=this.model.annotation || "Click 'Add' to add an annotation")
diff --git a/client/model-view/templates/eventAssignmentsView.pug b/client/model-view/templates/eventAssignmentsView.pug
index f32e4cf6da..7cfca19d08 100644
--- a/client/model-view/templates/eventAssignmentsView.pug
+++ b/client/model-view/templates/eventAssignmentsView.pug
@@ -10,11 +10,11 @@ div#event-assignments-editor
div.tooltip-icon(data-html="true" data-toggle="tooltip" title=this.tooltips.assignments)
- button.btn.btn-outline-collapse(data-toggle="collapse" data-target="#collapse-event-assignments" data-hook="collapse-assignments") -
+ button.btn.btn-outline-collapse(data-toggle="collapse" data-target="#collapse-event-assignments" + this.collection.parent.compID data-hook="collapse-assignments") -
div
- div.collapse.show.tab-content(id="collapse-event-assignments" data-hook="event-assignments-list-container")
+ div.collapse.show.tab-content(id="collapse-event-assignments" + this.collection.parent.compID data-hook="event-assignments-list-container")
div.tab-pane.active(id="edit-event-assignments" data-hook="edit-event-assignments")
diff --git a/client/model-view/templates/eventDetails.pug b/client/model-view/templates/eventDetails.pug
deleted file mode 100644
index c473f51381..0000000000
--- a/client/model-view/templates/eventDetails.pug
+++ /dev/null
@@ -1,74 +0,0 @@
-div(data-hook="event-details")
-
- div.row
-
- div
-
- span(for="event-trigger-expression") Trigger Expression:
-
- div.tooltip-icon(data-html="true" data-toggle="tooltip" title=this.parent.tooltips.triggerExpression)
-
- div.col-md-8(id="event-trigger-expression" data-hook="event-trigger-expression")
-
- div(data-hook="event-assignments")
-
- div.my-1
-
- h6.inline Advanced
- button.btn.btn-outline-collapse(data-toggle='collapse', data-target='#advanced-events', data-hook='advanced-event-button') +
-
- div.collapse(id="advanced-events" data-hook="advanced-event-section")
-
- div.row
-
- div.col-md-6
-
- span(for="event-trigger-expression") Delay:
-
- div.tooltip-icon(data-html="true" data-toggle="tooltip" title=this.parent.tooltips.delay)
-
- div.col-md-9.inline(id="event-delay" data-hook="event-delay")
-
- div.col-md-6
-
- span(for="event-trigger-expression") Priority:
-
- div.tooltip-icon(data-html="true" data-toggle="tooltip" title=this.parent.tooltips.priority)
-
- div.col-md-8.inline(data-hook="event-priority")
-
- div.row
-
- div.col-md-6.verticle-space
-
- span.checkbox(for="initial-value") Initial Value:
-
- div.tooltip-icon(data-html="true" data-toggle="tooltip" title=this.parent.tooltips.initialValue)
-
- input(type="checkbox", id="initial-value", data-hook="event-trigger-init-value")
-
- div.col-md-6.verticle-space
-
- span.checkbox(for="persistent") Persistent:
-
- div.tooltip-icon(data-html="true" data-toggle="tooltip" title=this.parent.tooltips.persistent)
-
- input(type="checkbox", id="persistent", data-hook="event-trigger-persistent")
-
- div.row
-
- div.col-md-12.verticle-space
-
- span.checkbox(for="use-values-from-trigger-time") Use Values From:
-
- div.tooltip-icon(data-html="true" data-toggle="tooltip" title=this.parent.tooltips.useValuesFromTriggerTime)
-
- div.horizontal-space.inline
-
- input(type="radio", name="edit-use-values-from" data-hook="edit-trigger-time" data-name="trigger")
- | Trigger Time
-
- div.horizontal-space.inline
-
- input(type="radio", name="edit-use-values-from" data-hook="edit-assignment-time" data-name="assignment")
- | Assignment Time
diff --git a/client/model-view/templates/eventListing.pug b/client/model-view/templates/eventListing.pug
deleted file mode 100644
index f67a2ecc19..0000000000
--- a/client/model-view/templates/eventListing.pug
+++ /dev/null
@@ -1,22 +0,0 @@
-div.mx-1
-
- if(this.model.collection.indexOf(this.model) !== 0)
- hr
-
- div.row.align-items-baseline
-
- div.col-md-2
-
- div.pl-3: input(type="radio", data-hook="select", name="event-select")
-
- div.col-md-5(data-hook="event-name-container")
-
- div.col-md-3
-
- div.tooltip-icon-large(data-hook="annotation-tooltip" data-html="true" data-toggle="tooltip" title=this.model.annotation || "Click 'Add' to add an annotation")
-
- button.btn.btn-outline-secondary.btn-sm.box-shadow(data-hook="edit-annotation-btn") Edit
-
- div.col-md-2
-
- button.btn.btn-outline-secondary.box-shadow(data-hook="remove") X
diff --git a/client/model-view/templates/eventsView.pug b/client/model-view/templates/eventsView.pug
index 85fef5ae21..afccb32798 100644
--- a/client/model-view/templates/eventsView.pug
+++ b/client/model-view/templates/eventsView.pug
@@ -29,37 +29,31 @@ div#events.card
div.tab-pane.active(id="edit-events" data-hook="edit-events")
- div.row.mb-3.align-items-baseline
-
- div.col-md-6.container-part
-
- hr
-
- div.mx-1.row.head.align-items-baseline
+ hr
- div.col-md-2
+ div.mx-1.row.head.align-items-baseline
- h6 Edit
+ div.col-sm-1
- div.col-md-5
+ h6 Edit
- h6.inline Name
+ div.col-sm-7
- div.tooltip-icon(data-html="true" data-toggle="tooltip" title=this.tooltips.name)
+ h6.inline Name
- div.col-md-3
+ div.tooltip-icon(data-html="true" data-toggle="tooltip" title=this.tooltips.name)
- h6.inline Annotation
+ div.col-sm-2
- div.tooltip-icon(data-html="true" data-toggle="tooltip" title=this.tooltips.annotation)
+ h6.inline Annotation
- div.col-md-2
+ div.tooltip-icon(data-html="true" data-toggle="tooltip" title=this.tooltips.annotation)
- h6 Remove
+ div.col-sm-2
- div.mt-3(data-hook="edit-event-listing-container")
+ h6 Remove
- div.col-md-6.container-part(data-hook="event-details-container")
+ div.mt-3(data-hook="edit-event-container")
button.btn.btn-outline-primary.box-shadow(data-hook="add-event") Add Event
@@ -89,4 +83,4 @@ div#events.card
h6 Annotation
- div.mt-3(data-hook="view-event-listing-container")
+ div.mt-3(data-hook="view-event-container")
diff --git a/client/model-view/templates/reactionRestrictTo.pug b/client/model-view/templates/reactionRestrictTo.pug
index 6c810ba00a..3c9d70fa1d 100644
--- a/client/model-view/templates/reactionRestrictTo.pug
+++ b/client/model-view/templates/reactionRestrictTo.pug
@@ -2,4 +2,8 @@ div.mb-3
span Types the reaction can occur in:
- div.mt-1.row(data-hook="reaction-types-container")
\ No newline at end of file
+ div.mt-1.row(data-hook="reaction-types-container")
+
+ div(data-hook="restict-to-error")
+
+ p.text-danger Reactions must be active in at least one 'Type'.
\ No newline at end of file
diff --git a/client/model-view/templates/sbmlComponentsView.pug b/client/model-view/templates/sbmlComponentsView.pug
index 84ac3d3fb5..e3fe619412 100644
--- a/client/model-view/templates/sbmlComponentsView.pug
+++ b/client/model-view/templates/sbmlComponentsView.pug
@@ -6,7 +6,7 @@ div#sbml-components.card
button.btn.btn-outline-collapse(data-toggle="collapse", data-target="#collapse-sbml-component", data-hook="collapse") +
- div.collapse(id="collapse-sbml-component")
+ div.collapse(id="collapse-sbml-component" data-hook="sbml-component-container")
div.card-body
@@ -30,11 +30,13 @@ div#sbml-components.card
button.btn.btn-outline-collapse(data-toggle="collapse" data-target="#collapse-function-definitions" data-hook="collapse-function-definitions") -
+ div.inline(data-hook="function-definition-filter" style="float: right;")
+
div.card-body
p.mb-0
| Object representation defining an evaluable function to be used during simulation.
- div.collapse.tab-content(class="show" id="collapse-function-definitions")
+ div.collapse.tab-content(class="show" id="collapse-function-definitions" data-hook="function-definitions-list-container")
div.tab-pane.active(id="edit-function-definitions" data-hook="edit-function-definitions")
diff --git a/client/model-view/templates/viewEvent.pug b/client/model-view/templates/viewEvent.pug
index ef20893766..54a65d8abd 100644
--- a/client/model-view/templates/viewEvent.pug
+++ b/client/model-view/templates/viewEvent.pug
@@ -11,7 +11,7 @@ div.mx-1
div.col-sm-2=this.model.triggerExpression
- div.col-sm-4(data-hook="assignment-viewer")
+ div.col-sm-4(data-hook="assignments-container")
div.col-sm-2
@@ -35,13 +35,13 @@ div.mx-1
div
- input(type="radio", id="trigger-time", name="view-use-values-from" data-hook="view-trigger-time" disabled)
+ input(type="radio" id="trigger-time" name="view-use-values-from" + this.model.compID data-hook="view-trigger-time" disabled)
span.inline.horizontal-space(for="trigger-time") Trigger Time
div
- input(type="radio", id="assignment-time", name="view-use-values-from" data-hook="view-assignment-time" disabled)
+ input(type="radio" id="assignment-time" name="view-use-values-from" + this.model.compID data-hook="view-assignment-time" disabled)
span.inline.horizontal-space(for="assignment-time") Assignment Time
diff --git a/client/model-view/views/boundary-condition-view.js b/client/model-view/views/boundary-condition-view.js
index f1c5ab8116..c21620b63d 100644
--- a/client/model-view/views/boundary-condition-view.js
+++ b/client/model-view/views/boundary-condition-view.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/model-view/views/boundary-conditions-view.js b/client/model-view/views/boundary-conditions-view.js
index e2f4831a94..2c42dd65de 100644
--- a/client/model-view/views/boundary-conditions-view.js
+++ b/client/model-view/views/boundary-conditions-view.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -56,6 +56,8 @@ module.exports = View.extend({
View.prototype.initialize.apply(this, arguments);
this.readOnly = attrs.readOnly ? attrs.readOnly : false;
this.tooltips = Tooltips.boundaryConditionsEditor;
+ this.filterAttr = attrs.attr;
+ this.filterKey = attrs.key;
this.setDefaultBC();
},
render: function (attrs, options) {
@@ -70,10 +72,10 @@ module.exports = View.extend({
$(this.queryByHook('edit-boundary-conditions')).removeClass('active');
$(this.queryByHook('view-boundary-conditions')).addClass('active');
}else{
- this.renderEditBoundaryConditionView();
+ this.renderEditBoundaryConditionView({'key': this.filterKey, 'attr': this.filterAttr});
this.toggleAddNewBCButton();
}
- this.renderViewBoundaryConditionView();
+ this.renderViewBoundaryConditionView({'key': this.filterKey, 'attr': this.filterAttr});
},
changeCollapseButtonText: function (e) {
app.changeCollapseButtonText(this, e);
@@ -93,17 +95,16 @@ module.exports = View.extend({
},
handleAddBCClick: function (e) {
let endpoint = path.join(app.getApiPath(), "model/new-bc");
- let self = this;
- if(this.newBC.property === "v") {
+ if(this.newBC.target === "v") {
this.newBC.value = this.newBCVector;
}
let data = {model_path: this.collection.parent.directory,
kwargs: this.newBC};
app.postXHR(endpoint, data, {
- success: function (err, response, body) {
- self.collection.addNewBoundaryCondition(self.newBCName, body.expression);
- self.setDefaultBC();
- self.resetNewBCViews();
+ success: (err, response, body) => {
+ this.collection.addNewBoundaryCondition(this.newBCName, body.expression);
+ this.setDefaultBC();
+ this.resetNewBCViews();
}
});
},
@@ -121,15 +122,15 @@ module.exports = View.extend({
$(this.queryByHook("new-bc-velocity-value")).css("display", "none");
$(this.queryByHook("new-bc-other-value")).css("display", "block");
}
- this.newBC.property = target;
- this.newBC.species = null;
+ this.newBC.target = target;
$(this.queryByHook("new-bc-deterministic")).prop("disabled", true);
}else{
- let species = this.collection.parent.species.filter(function (specie) {
+ $(this.queryByHook("new-bc-velocity-value")).css("display", "none");
+ $(this.queryByHook("new-bc-other-value")).css("display", "block");
+ let species = this.collection.parent.species.filter((specie) => {
return specie.compID === Number(target);
})[0].name;
- this.newBC.property = null;
- this.newBC.species = species;
+ this.newBC.target = species;
$(this.queryByHook("new-bc-deterministic")).prop("disabled", false);
}
},
@@ -143,18 +144,25 @@ module.exports = View.extend({
this.newBC[key] = this.validateNewBCCondition(key, value);
}else if(key === "value") {
if(e.delegateTarget.dataset.hook.endsWith("x")) {
- this.newBCVector[0] = value === "" ? 0 : value;
+ this.newBCVector[0] = value === "" ? 0 : Number(value);
}else if(e.delegateTarget.dataset.hook.endsWith("y")) {
- this.newBCVector[1] = value === "" ? 0 : value;
+ this.newBCVector[1] = value === "" ? 0 : Number(value);
}else if(e.delegateTarget.dataset.hook.endsWith("z")) {
- this.newBCVector[2] = value === "" ? 0 : value;
+ this.newBCVector[2] = value === "" ? 0 : Number(value);
}else{
- this.newBC[key] = value === "" ? null : value;
+ this.newBC[key] = value === "" ? null : Number(value);
}
}
}
this.toggleAddNewBCButton();
},
+ openSection: function ({editMode=true}={}) {
+ if(!$(this.queryByHook("boundary-conditions-container")).hasClass("show")) {
+ let boundCondCollapseBtn = $(this.queryByHook("collapse-bc"));
+ boundCondCollapseBtn.click();
+ boundCondCollapseBtn.html('-');
+ }
+ },
renderEditBoundaryConditionView: function ({key=null, attr=null}={}) {
if(this.editBoundaryConditionView) {
this.editBoundaryConditionView.remove();
@@ -193,7 +201,7 @@ module.exports = View.extend({
resetNewBCViews: function () {
$(this.queryByHook("new-bc-deterministic")).prop("checked", this.newBC.deterministic);
$(this.queryByHook("new-bc-name")).find("input").val(this.newBCName);
- $(this.queryByHook("new-bc-target")).find("select").val(this.newBC.property);
+ $(this.queryByHook("new-bc-target")).find("select").val(this.newBC.target);
$(this.queryByHook("new-bc-type")).find("input").val(this.newBC.type_id);
$(this.queryByHook("new-bc-value")).find("input").val(this.newBC.value);
$(this.queryByHook("new-bc-x-min")).find("input").val(this.newBC.xmin);
@@ -207,14 +215,14 @@ module.exports = View.extend({
},
setDefaultBC: function () {
this.newBCName = "";
- this.newBC = {"species": null, "property": "v", "value": null, "deterministic": true, "type_id": null,
+ this.newBC = {"target": "v", "value": null, "deterministic": true, "type_id": null,
"xmin": null, "ymin": null, "zmin": null, "xmax": null, "ymax": null, "zmax": null};
this.newBCVector = [0, 0, 0];
this.setConditions = [];
},
toggleAddNewBCButton: function () {
let invalidName = this.newBCName === "";
- let invalidValue = this.newBC.property === "v" ? this.newBCVector === [0, 0, 0] : this.newBC.value === null;
+ let invalidValue = this.newBC.target === "v" ? this.newBCVector === [0, 0, 0] : this.newBC.value === null;
let disabled = invalidName || invalidValue || !this.setConditions.length;
$(this.queryByHook("add-new-bc")).prop("disabled", disabled);
},
@@ -229,7 +237,7 @@ module.exports = View.extend({
}else if(!this.setConditions.includes(key)){
this.setConditions.push(key);
}
- return value;
+ return key === "type_id" ? value : Number(value);
},
subviews: {
filter: {
@@ -240,6 +248,7 @@ module.exports = View.extend({
required: false,
name: 'filter',
valueType: 'string',
+ disabled: this.filterKey !== null,
placeholder: 'filter'
});
}
@@ -271,7 +280,7 @@ module.exports = View.extend({
name: 'target',
eagerValidate: true,
groupOptions: options,
- value: this.newBC.property
+ value: this.newBC.target
});
}
},
@@ -333,7 +342,7 @@ module.exports = View.extend({
parent: this,
required: false,
name: 'xmin',
- tests: tests.valueTests,
+ tests: tests.intTests,
valueType: 'number',
value: this.newBC.xmin
});
@@ -346,7 +355,7 @@ module.exports = View.extend({
parent: this,
required: false,
name: 'ymin',
- tests: tests.valueTests,
+ tests: tests.intTests,
valueType: 'number',
value: this.newBC.ymin
});
@@ -359,7 +368,7 @@ module.exports = View.extend({
parent: this,
required: false,
name: 'zmin',
- tests: tests.valueTests,
+ tests: tests.intTests,
valueType: 'number',
value: this.newBC.zmin
});
@@ -372,7 +381,7 @@ module.exports = View.extend({
parent: this,
required: false,
name: 'xmax',
- tests: tests.valueTests,
+ tests: tests.intTests,
valueType: 'number',
value: this.newBC.xmax
});
@@ -385,7 +394,7 @@ module.exports = View.extend({
parent: this,
required: false,
name: 'ymax',
- tests: tests.valueTests,
+ tests: tests.intTests,
valueType: 'number',
value: this.newBC.ymax
});
@@ -398,7 +407,7 @@ module.exports = View.extend({
parent: this,
required: false,
name: 'zmax',
- tests: tests.valueTests,
+ tests: tests.intTests,
valueType: 'number',
value: this.newBC.zmax
});
@@ -411,8 +420,8 @@ module.exports = View.extend({
parent: this,
required: false,
name: 'type',
- tests: tests.valueTests,
- valueType: 'number',
+ tests: tests.optionalNameTests,
+ valueType: 'string',
value: this.newBC.type_id
});
}
diff --git a/client/model-view/views/component-types.js b/client/model-view/views/component-types.js
index 57ca64b996..9fbf89431f 100644
--- a/client/model-view/views/component-types.js
+++ b/client/model-view/views/component-types.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -47,5 +47,6 @@ module.exports = View.extend({
this.parent.model.types.splice(index, 1);
}
this.parent.model.trigger('change');
+ this.parent.model.trigger('types-changed');
}
});
\ No newline at end of file
diff --git a/client/model-view/views/domain-viewer.js b/client/model-view/views/domain-viewer.js
index 720501ba4c..fffed1e405 100644
--- a/client/model-view/views/domain-viewer.js
+++ b/client/model-view/views/domain-viewer.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -128,7 +128,7 @@ module.exports = View.extend({
this.domainPath = null;
$(this.queryByHook('select-location-message')).css('display', 'none');
$(this.queryByHook('select-domain-location')).css('display', 'none');
- $(this.queryByHook('external-domain-select')).css('display', 'none');
+ $(this.queryByHook('domain-interact-section')).css('display', 'none');
$(this.queryByHook('save-to-model')).prop('disabled', true);
this.renderDomainView();
}
@@ -162,6 +162,7 @@ module.exports = View.extend({
domainCollapseBtn.html('-');
}
app.switchToEditTab(this, "domain");
+ this.domainView.displayError();
},
renderDomainFileSelectView: function () {
if(this.domainFileSelectView) {
@@ -175,7 +176,7 @@ module.exports = View.extend({
unselectedText: '-- Select Domain --'
});
app.registerRenderSubview(this, this.domainFileSelectView, 'select-domain');
- $(this.queryByHook('external-domain-select')).css('display', 'block');
+ $(this.queryByHook('domain-interact-section')).css('display', 'block');
},
renderDomainLocationSelectView: function (options) {
if(this.domainLocationSelectView) {
diff --git a/client/model-view/views/event-assignment-view.js b/client/model-view/views/event-assignment-view.js
index 2e00163ef0..1871f79911 100644
--- a/client/model-view/views/event-assignment-view.js
+++ b/client/model-view/views/event-assignment-view.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -29,7 +29,7 @@ let viewTemplate = require('../templates/viewEventAssignment.pug');
module.exports = View.extend({
events: {
- 'click [data-hook=remove]' : 'removeAssignment',
+ 'click [data-hook=remove-assignment]' : 'removeAssignment',
'change [data-hook=event-assignment-variable]' : 'selectAssignmentVariable',
'change [data-hook=event-assignment-expression]' : 'updateViewer'
},
diff --git a/client/model-view/views/event-assignments-view.js b/client/model-view/views/event-assignments-view.js
index 483df74ee3..d473af8828 100644
--- a/client/model-view/views/event-assignments-view.js
+++ b/client/model-view/views/event-assignments-view.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/model-view/views/event-details.js b/client/model-view/views/event-details.js
deleted file mode 100644
index 5bd52cf5ee..0000000000
--- a/client/model-view/views/event-details.js
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
-StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 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 .
-*/
-
-let $ = require('jquery');
-//support files
-let app = require('../../app');
-let tests = require('../../views/tests');
-//views
-let InputView = require('../../views/input');
-let View = require('ampersand-view');
-let EventAssignment = require('./event-assignments-view');
-//templates
-let template = require('../templates/eventDetails.pug');
-
-module.exports = View.extend({
- template: template,
- bindings: {
- 'model.initialValue': {
- hook: 'event-trigger-init-value',
- type: 'booleanAttribute',
- name: 'checked'
- },
- 'model.persistent': {
- hook: 'event-trigger-persistent',
- type: 'booleanAttribute',
- name: 'checked'
- },
- },
- events: {
- 'change [data-hook=event-trigger-init-value]' : 'setTriggerInitialValue',
- 'change [data-hook=event-trigger-persistent]' : 'setTriggerPersistent',
- 'change [data-hook=edit-trigger-time]' : 'setUseValuesFromTriggerTime',
- 'change [data-hook=edit-assignment-time]' : 'setUseValuesFromTriggerTime',
- 'click [data-hook=advanced-event-button]' : 'changeCollapseButtonText'
- },
- initialize: function (attrs, options) {
- View.prototype.initialize.apply(this, arguments);
- },
- render: function () {
- View.prototype.render.apply(this, arguments);
- this.renderEventAssignments();
- if(this.model.useValuesFromTriggerTime){
- $(this.queryByHook('edit-trigger-time')).prop('checked', true);
- }else{
- $(this.queryByHook('edit-assignment-time')).prop('checked', true);
- }
- app.tooltipSetup();
- },
- changeCollapseButtonText: function (e) {
- app.changeCollapseButtonText(this, e);
- },
- openAdvancedSection: function () {
- if(this.model.advanced_error && !$(this.queryByHook("advanced-event-section")).hasClass('show')) {
- let advCollapseBtn = $(this.queryByHook("advanced-event-button"));
- advCollapseBtn.click();
- advCollapseBtn.html('-');
- }
- },
- renderEventAssignments: function () {
- if(this.eventAssignmentsView){
- this.eventAssignmentsView.remove();
- }
- this.eventAssignmentsView = new EventAssignment({
- collection: this.model.eventAssignments,
- tooltips: this.parent.tooltips
- });
- app.registerRenderSubview(this, this.eventAssignmentsView, 'event-assignments');
- },
- setTriggerInitialValue: function (e) {
- this.model.initialValue = e.target.checked;
- },
- setTriggerPersistent: function (e) {
- this.model.persistent = e.target.checked;
- },
- setUseValuesFromTriggerTime: function (e) {
- this.model.useValuesFromTriggerTime = e.target.dataset.name === "trigger";
- },
- update: function () {},
- updateValid: function () {},
- subviews: {
- inputDelay: {
- hook: 'event-delay',
- prepareView: function (el) {
- return new InputView({
- parent: this,
- required: false,
- name: 'delay',
- placeholder: '---No Expression Entered---',
- modelKey: 'delay',
- valueType: 'string',
- value: this.model.delay
- });
- }
- },
- inputPriority: {
- hook: 'event-priority',
- prepareView: function (el) {
- return new InputView({
- parent: this,
- required: true,
- name: 'priority',
- modelKey: 'priority',
- valueType: 'string',
- value: this.model.priority
- });
- }
- },
- inputTriggerExpression: {
- hook: 'event-trigger-expression',
- prepareView: function (el) {
- return new InputView({
- parent: this,
- required: true,
- name: 'trigger-expression',
- placeholder: '---No Expression Entered---',
- modelKey: 'triggerExpression',
- valueType: 'string',
- value: this.model.triggerExpression
- });
- }
- }
- }
-});
\ No newline at end of file
diff --git a/client/model-view/views/event-listing.js b/client/model-view/views/event-listing.js
deleted file mode 100644
index 6ab4cea796..0000000000
--- a/client/model-view/views/event-listing.js
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
-StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 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 .
-*/
-
-let $ = require('jquery');
-let _ = require('underscore');
-//support files
-let app = require('../../app');
-let tests = require('../../views/tests');
-let modals = require('../../modals');
-//views
-let InputView = require('../../views/input');
-let View = require('ampersand-view');
-let EventAssignment = require('./event-assignments-view');
-//templates
-let viewTemplate = require('../templates/viewEvent.pug');
-let editTemplate = require('../templates/eventListing.pug');
-
-module.exports = View.extend({
- bindings: {
- 'model.selected' : {
- type: function (el, value, previousValue) {
- el.checked = value;
- },
- hook: 'select'
- },
- 'model.initialValue': {
- hook: 'event-trigger-init-value',
- type: 'booleanAttribute',
- name: 'checked'
- },
- 'model.persistent': {
- hook: 'event-trigger-persistent',
- type: 'booleanAttribute',
- name: 'checked'
- }
- },
- events: {
- 'click [data-hook=select]' : 'selectEvent',
- 'click [data-hook=edit-annotation-btn]' : 'editAnnotation',
- 'click [data-hook=remove]' : 'removeEvent'
- },
- initialize: function (attrs, options) {
- View.prototype.initialize.apply(this, arguments);
- this.viewMode = attrs.viewMode ? attrs.viewMode : false;
- if(this.viewMode) {
- this.delay = this.model.delay === "" ? "None" : this.model.delay;
- }else{
- this.model.on("change", _.bind(this.updateViewer, this));
- }
- },
- render: function () {
- this.template = this.viewMode ? viewTemplate : editTemplate;
- View.prototype.render.apply(this, arguments);
- app.documentSetup();
- if(!this.model.annotation){
- $(this.queryByHook('edit-annotation-btn')).text('Add');
- }
- if(this.viewMode) {
- if(this.model.useValuesFromTriggerTime) {
- $(this.queryByHook("view-trigger-time")).prop('checked', true);
- }else{
- $(this.queryByHook("view-assignment-time")).prop('checked', true);
- }
- }
- },
- editAnnotation: function () {
- if(document.querySelector('#eventAnnotationModal')) {
- document.querySelector('#eventAnnotationModal').remove();
- }
- let self = this;
- let name = this.model.name;
- let annotation = this.model.annotation;
- let modal = $(modals.annotationModalHtml("event", name, annotation)).modal();
- let okBtn = document.querySelector('#eventAnnotationModal .ok-model-btn');
- let input = document.querySelector('#eventAnnotationModal #eventAnnotationInput');
- input.addEventListener("keyup", function (event) {
- if(event.keyCode === 13){
- event.preventDefault();
- okBtn.click();
- }
- });
- okBtn.addEventListener('click', function (e) {
- self.model.annotation = input.value.trim();
- self.parent.renderEditEventListingsView();
- modal.modal('hide');
- });
- },
- removeEvent: function () {
- this.remove();
- this.collection.removeEvent(this.model);
- },
- selectEvent: function (e) {
- this.model.collection.trigger("select", this.model);
- },
- update: function () {},
- updateValid: function () {},
- updateViewer: function () {
- this.parent.renderViewEventListingView();
- },
- subviews: {
- inputName: {
- hook: 'event-name-container',
- prepareView: function (el) {
- return new InputView({
- parent: this,
- required: true,
- name: 'name',
- tests: tests.nameTests,
- modelKey: 'name',
- valueType: 'string',
- value: this.model.name
- });
- }
- },
- viewEventAssignmentsView: {
- hook: "assignment-viewer",
- prepareView: function (el) {
- return new EventAssignment({
- collection: this.model.eventAssignments,
- tooltips: this.parent.tooltips,
- readOnly: true
- });
- }
- }
- }
-});
\ No newline at end of file
diff --git a/client/model-view/views/event-view.js b/client/model-view/views/event-view.js
new file mode 100644
index 0000000000..b7179de850
--- /dev/null
+++ b/client/model-view/views/event-view.js
@@ -0,0 +1,221 @@
+/*
+StochSS is a platform for simulating biochemical systems
+Copyright (C) 2019-2023 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 .
+*/
+
+let $ = require('jquery');
+let _ = require('underscore');
+//support files
+let app = require('../../app');
+let modals = require('../../modals');
+let tests = require('../../views/tests');
+//views
+let View = require('ampersand-view');
+let InputView = require('../../views/input');
+let EventAssignmentsView = require('./event-assignments-view');
+//templates
+let editTemplate = require('../templates/editEvent.pug');
+let viewTemplate = require('../templates/viewEvent.pug');
+
+module.exports = View.extend({
+ bindings: {
+ 'model.selected' : {
+ type: function (el, value, previousValue) {
+ el.checked = value;
+ },
+ hook: 'select'
+ },
+ 'model.initialValue': {
+ hook: 'event-trigger-init-value',
+ type: 'booleanAttribute',
+ name: 'checked'
+ },
+ 'model.persistent': {
+ hook: 'event-trigger-persistent',
+ type: 'booleanAttribute',
+ name: 'checked'
+ }
+ },
+ events: {
+ 'change [data-hook=event-trigger-init-value]' : 'setTriggerInitialValue',
+ 'change [data-hook=event-trigger-persistent]' : 'setTriggerPersistent',
+ 'change [data-hook=edit-trigger-time]' : 'setUseValuesFromTriggerTime',
+ 'change [data-hook=edit-assignment-time]' : 'setUseValuesFromTriggerTime',
+ 'click [data-hook=select]' : 'selectEvent',
+ 'click [data-hook=edit-annotation-btn]' : 'editAnnotation',
+ 'click [data-hook=remove]' : 'removeEvent',
+ 'click [data-hook=advanced-event-button]' : 'changeCollapseButtonText'
+ },
+ initialize: function (attrs, options) {
+ View.prototype.initialize.apply(this, arguments);
+ this.viewMode = attrs.viewMode ? attrs.viewMode : false;
+ if(this.viewMode) {
+ this.delay = !this.model.delay ? "None" : this.model.delay;
+ }else{
+ this.model.on("change", _.bind(this.updateViewer, this));
+ this.model.on("change-event", () => {
+ this.renderEventAssignmentsView({override: true});
+ });
+ }
+ },
+ render: function () {
+ this.template = this.viewMode ? viewTemplate : editTemplate;
+ View.prototype.render.apply(this, arguments);
+ app.documentSetup();
+ if(this.viewMode) {
+ var uvfView = "view"
+ this.renderEventAssignmentsView();
+ }else{
+ var uvfView = "edit"
+ if(this.model.selected) {
+ setTimeout(_.bind(this.openEventDetails, this), 1);
+ }
+ if(!this.model.annotation){
+ $(this.queryByHook('edit-annotation-btn')).text('Add');
+ }
+ }
+ if(this.model.useValuesFromTriggerTime){
+ $(this.queryByHook(`${uvfView}-trigger-time`)).prop('checked', true);
+ }else{
+ $(this.queryByHook(`${uvfView}-assignment-time`)).prop('checked', true);
+ }
+ },
+ changeCollapseButtonText: function (e) {
+ app.changeCollapseButtonText(this, e);
+ },
+ editAnnotation: function () {
+ if(document.querySelector('#eventAnnotationModal')) {
+ document.querySelector('#eventAnnotationModal').remove();
+ }
+ let name = this.model.name;
+ let annotation = this.model.annotation;
+ let modal = $(modals.annotationModalHtml("event", name, annotation)).modal();
+ let okBtn = document.querySelector('#eventAnnotationModal .ok-model-btn');
+ let input = document.querySelector('#eventAnnotationModal #eventAnnotationInput');
+ input.addEventListener("keyup", (event) => {
+ if(event.keyCode === 13){
+ event.preventDefault();
+ okBtn.click();
+ }
+ });
+ okBtn.addEventListener('click', (e) => {
+ modal.modal('hide');
+ this.model.annotation = input.value.trim();
+ this.parent.renderEditEventView();
+ });
+ },
+ openEventDetails: function () {
+ $("#collapse-event-details" + this.model.compID).collapse("show");
+ this.renderDetailsSection();
+ },
+ removeEvent: function () {
+ this.collection.removeEvent(this.model);
+ },
+ renderDetailsSection: function () {
+ this.renderEventAssignmentsView();
+ },
+ renderEventAssignmentsView: function ({override=false}={}) {
+ if(override && this.eventAssignmentsView) {
+ this.eventAssignmentsView.remove();
+ }
+
+ if(!this.eventAssignmentsView || override) {
+ this.eventAssignmentsView = new EventAssignmentsView({
+ collection: this.model.eventAssignments,
+ tooltips: this.parent.tooltips,
+ readOnly: this.viewMode
+ });
+ app.registerRenderSubview(this, this.eventAssignmentsView, "assignments-container");
+ }
+ },
+ selectEvent: function (e) {
+ this.model.selected = !this.model.selected;
+ this.renderDetailsSection();
+ },
+ setTriggerInitialValue: function (e) {
+ this.model.initialValue = e.target.checked;
+ },
+ setTriggerPersistent: function (e) {
+ this.model.persistent = e.target.checked;
+ },
+ setUseValuesFromTriggerTime: function (e) {
+ this.model.useValuesFromTriggerTime = e.target.dataset.name === "trigger";
+ },
+ update: function () {},
+ updateValid: function () {},
+ updateViewer: function (event) {
+ if(!event || !("selected" in event._changed)){
+ this.parent.renderViewEventView();
+ }
+ },
+ subviews: {
+ inputName: {
+ hook: 'event-name-container',
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ required: true,
+ name: 'name',
+ tests: tests.nameTests,
+ modelKey: 'name',
+ valueType: 'string',
+ value: this.model.name
+ });
+ }
+ },
+ inputTriggerExpression: {
+ hook: 'event-trigger-expression',
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ required: true,
+ name: 'trigger-expression',
+ placeholder: '---No Expression Entered---',
+ modelKey: 'triggerExpression',
+ valueType: 'string',
+ value: this.model.triggerExpression
+ });
+ }
+ },
+ inputDelay: {
+ hook: 'event-delay',
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ required: false,
+ name: 'delay',
+ placeholder: '---No Expression Entered---',
+ modelKey: 'delay',
+ valueType: 'string',
+ value: this.model.delay
+ });
+ }
+ },
+ inputPriority: {
+ hook: 'event-priority',
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ required: true,
+ name: 'priority',
+ modelKey: 'priority',
+ valueType: 'string',
+ value: this.model.priority
+ });
+ }
+ }
+ }
+});
\ No newline at end of file
diff --git a/client/model-view/views/events-view.js b/client/model-view/views/events-view.js
index 7b07de89db..d8fc9bbc59 100644
--- a/client/model-view/views/events-view.js
+++ b/client/model-view/views/events-view.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -22,17 +22,15 @@ let app = require('../../app');
let Tooltips = require("../../tooltips");
//views
let View = require('ampersand-view');
+let EventView = require('./event-view');
let InputView = require('../../views/input');
-let EventDetails = require('./event-details');
-let EventListings = require('./event-listing');
-let ViewSwitcher = require('ampersand-view-switcher');
//templates
let template = require('../templates/eventsView.pug');
module.exports = View.extend({
template: template,
events: {
- 'click [data-hook=add-event]' : 'addEvent',
+ 'click [data-hook=add-event]' : 'handleAddEvent',
'click [data-hook=collapse]' : 'changeCollapseButtonText',
'change [data-hook=event-filter]' : 'filterEvents'
},
@@ -40,26 +38,8 @@ module.exports = View.extend({
View.prototype.initialize.apply(this, arguments);
this.readOnly = attrs.readOnly ? attrs.readOnly : false;
this.tooltips = Tooltips.eventsEditor;
- if(!this.readOnly) {
- this.collection.on("select", function (event) {
- this.setSelectedEvent(event);
- this.setDetailsView(event);
- }, this);
- this.collection.on("remove", function (event) {
- // Select the last event by default
- // But only if there are other events other than the one we're removing
- if (event.detailsView){
- event.detailsView.remove();
- }
- this.collection.removeEvent(event);
- if (this.collection.length) {
- let selected = this.collection.at(this.collection.length-1);
- this.collection.trigger("select", selected);
- }
- }, this);
- this.collection.parent.species.on('add remove', this.toggleAddEventButton, this);
- this.collection.parent.parameters.on('add remove', this.toggleAddEventButton, this);
- }
+ this.filterAttr = attrs.attr;
+ this.filterKey = attrs.key;
},
render: function () {
View.prototype.render.apply(this, arguments);
@@ -73,24 +53,10 @@ module.exports = View.extend({
$(this.queryByHook('edit-events')).removeClass('active');
$(this.queryByHook('view-events')).addClass('active');
}else {
- this.renderEditEventListingsView();
- this.detailsContainer = this.queryByHook('event-details-container');
- this.detailsViewSwitcher = new ViewSwitcher({
- el: this.detailsContainer
- });
- if(this.collection.length) {
- this.setSelectedEvent(this.collection.at(0));
- this.collection.trigger("select", this.selectedEvent);
- }
+ this.renderEditEventView({'key': this.filterKey, 'attr': this.filterAttr});
this.toggleAddEventButton();
}
- this.renderViewEventListingView();
- },
- addEvent: function () {
- let event = this.collection.addEvent();
- event.detailsView = this.newDetailsView(event);
- this.collection.trigger("select", event);
- app.tooltipSetup();
+ this.renderViewEventView({'key': this.filterKey, 'attr': this.filterAttr});
},
changeCollapseButtonText: function (e) {
app.changeCollapseButtonText(this, e);
@@ -104,14 +70,20 @@ module.exports = View.extend({
key = attrKey[1];
}
if(!this.readOnly) {
- this.renderEditEventListingsView({'key': key, 'attr': attr});
+ this.renderEditEventView({'key': key, 'attr': attr});
}
- this.renderViewEventListingView({'key': key, 'attr': attr});
+ this.renderViewEventView({'key': key, 'attr': attr});
},
- newDetailsView: function (event) {
- let detailsView = new EventDetails({ model: event });
- detailsView.parent = this;
- return detailsView;
+ handleAddEvent: function () {
+ let event = this.collection.addEvent();
+ app.tooltipSetup();
+ },
+ openAdvancedSection: function () {
+ if(this.model.advanced_error && !$(this.queryByHook("advanced-event-section")).hasClass('show')) {
+ let advCollapseBtn = $(this.queryByHook("advanced-event-button"));
+ advCollapseBtn.click();
+ advCollapseBtn.html('-');
+ }
},
openSection: function (error) {
if(!$(this.queryByHook("events")).hasClass("show")) {
@@ -119,29 +91,34 @@ module.exports = View.extend({
evtCollapseBtn.click();
evtCollapseBtn.html('-');
}
- app.switchToEditTab(this, "events");
- var event = this.collection.filter((e) => {
- return e.compID === error.id;
- })[0];
- this.collection.trigger("select", event);
- event.detailsView.openAdvancedSection();
+ if(!this.readOnly) {
+ app.switchToEditTab(this, "events");
+ }
+ if(error) {
+ let eventView = this.editEventView.views.filter((eventView) => {
+ return eventView.model.compID === error.id;
+ })[0];
+ eventView.model.selected = true;
+ eventView.openEventDetails();
+ eventView.openAdvancedSection();
+ }
},
- renderEditEventListingsView: function ({key=null, attr=null}={}) {
- if(this.editEventListingsView){
- this.editEventListingsView.remove();
+ renderEditEventView: function ({key=null, attr=null}={}) {
+ if(this.editEventsView){
+ this.editEventsView.remove();
}
let options = {filter: (model) => { return model.contains(attr, key); }}
- this.editEventListingsView = this.renderCollection(
+ this.editEventsView = this.renderCollection(
this.collection,
- EventListings,
- this.queryByHook('edit-event-listing-container'),
+ EventView,
+ this.queryByHook('edit-event-container'),
options
);
app.tooltipSetup();
},
- renderViewEventListingView: function ({key=null, attr=null}={}) {
- if(this.viewEventListingsView) {
- this.viewEventListingsView.remove();
+ renderViewEventView: function ({key=null, attr=null}={}) {
+ if(this.viewEventsView) {
+ this.viewEventsView.remove();
}
this.containsMdlWithAnn = this.collection.filter(function (model) {return model.annotation}).length > 0;
if(!this.containsMdlWithAnn) {
@@ -153,29 +130,15 @@ module.exports = View.extend({
viewOptions: {parent: this, viewMode: true},
filter: (model) => { return model.contains(attr, key); }
};
- this.viewEventListingsView = this.renderCollection(
+ this.viewEventsView = this.renderCollection(
this.collection,
- EventListings,
- this.queryByHook('view-event-listing-container'),
+ EventView,
+ this.queryByHook('view-event-container'),
options
);
app.tooltipSetup();
},
- setDetailsView: function (event) {
- event.detailsView = event.detailsView || this.newDetailsView(event);
- this.detailsViewSwitcher.set(event.detailsView);
- },
- setSelectedEvent: function (event) {
- this.collection.each(function (m) { m.selected = false; });
- event.selected = true;
- this.selectedEvent = event;
- },
toggleAddEventButton: function () {
- this.collection.map(function (event) {
- if(event.detailsView && event.selected){
- event.detailsView.renderEventAssignments();
- }
- });
let numSpecies = this.collection.parent.species.length;
let numParameters = this.collection.parent.parameters.length;
let disabled = numSpecies <= 0 && numParameters <= 0;
@@ -192,6 +155,7 @@ module.exports = View.extend({
required: false,
name: 'filter',
valueType: 'string',
+ disabled: this.filterKey !== null,
placeholder: 'filter'
});
}
diff --git a/client/model-view/views/function-definition-view.js b/client/model-view/views/function-definition-view.js
index 4eccb14246..185dda0125 100644
--- a/client/model-view/views/function-definition-view.js
+++ b/client/model-view/views/function-definition-view.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/model-view/views/initial-condition-view.js b/client/model-view/views/initial-condition-view.js
index 1e9c013193..b76bd00e60 100644
--- a/client/model-view/views/initial-condition-view.js
+++ b/client/model-view/views/initial-condition-view.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -57,6 +57,8 @@ module.exports = View.extend({
app.documentSetup();
if(!this.viewMode) {
this.model.on('change', _.bind(this.updateViewer, this));
+ this.model.on('types-changed', this.toggleRestrictToError, this);
+ this.toggleRestrictToError();
}
if(!this.model.annotation){
$(this.queryByHook('edit-annotation-btn')).text('Add');
@@ -106,6 +108,7 @@ module.exports = View.extend({
this.model.icType = newType;
if(currentType === "Place" || newType === "Place"){
this.toggleDetailsView();
+ this.toggleRestrictToError();
}
},
toggleDetailsView: function () {
@@ -117,6 +120,16 @@ module.exports = View.extend({
$(this.queryByHook("scatter-details")).css("display", "block");
}
},
+ toggleRestrictToError: function () {
+ let errorMsg = $(this.queryByHook("restict-to-error"));
+ if(!this.model.validateComponent()) {
+ errorMsg.addClass('component-invalid');
+ errorMsg.removeClass('component-valid');
+ }else{
+ errorMsg.addClass('component-valid');
+ errorMsg.removeClass('component-invalid');
+ }
+ },
update: function () {},
updateValid: function () {},
updateViewer: function () {
diff --git a/client/model-view/views/initial-conditions-view.js b/client/model-view/views/initial-conditions-view.js
index 3a43c7fcff..aa2ecb2616 100644
--- a/client/model-view/views/initial-conditions-view.js
+++ b/client/model-view/views/initial-conditions-view.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -41,6 +41,8 @@ module.exports = View.extend({
View.prototype.initialize.apply(this, arguments);
this.tooltips = Tooltips.initialConditionsEditor;
this.readOnly = attrs.readOnly ? attrs.readOnly : false;
+ this.filterAttr = attrs.attr;
+ this.filterKey = attrs.key;
},
render: function () {
View.prototype.render.apply(this, arguments);
@@ -54,9 +56,9 @@ module.exports = View.extend({
$(this.queryByHook('edit-initial-conditions')).removeClass('active');
$(this.queryByHook('view-initial-conditions')).addClass('active');
}else{
- this.renderEditInitialConditionsView();
+ this.renderEditInitialConditionsView({'key': this.filterKey, 'attr': this.filterAttr});
}
- this.renderViewInitialConditionsView();
+ this.renderViewInitialConditionsView({'key': this.filterKey, 'attr': this.filterAttr});
},
addInitialCondition: function (e) {
var initialConditionType = e.target.textContent;
@@ -86,6 +88,13 @@ module.exports = View.extend({
}
this.renderViewInitialConditionsView({'key': key, 'attr': attr});
},
+ openSection: function ({editMode=true}={}) {
+ if(!$(this.queryByHook("initial-conditions")).hasClass("show")) {
+ let initCondCollapseBtn = $(this.queryByHook("initial-condition-button"));
+ initCondCollapseBtn.click();
+ initCondCollapseBtn.html('-');
+ }
+ },
renderEditInitialConditionsView: function ({key=null, attr=null}={}) {
if(this.editInitialConditionView) {
this.editInitialConditionView.remove();
@@ -132,6 +141,7 @@ module.exports = View.extend({
required: false,
name: 'filter',
valueType: 'string',
+ disabled: this.filterKey !== null,
placeholder: 'filter'
});
}
diff --git a/client/model-view/views/parameter-view.js b/client/model-view/views/parameter-view.js
index 075d250095..9db707c3b1 100644
--- a/client/model-view/views/parameter-view.js
+++ b/client/model-view/views/parameter-view.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/model-view/views/parameters-view.js b/client/model-view/views/parameters-view.js
index 68e122eb1d..2a8eceb8fd 100644
--- a/client/model-view/views/parameters-view.js
+++ b/client/model-view/views/parameters-view.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -38,9 +38,10 @@ module.exports = View.extend({
View.prototype.initialize.apply(this, arguments);
this.readOnly = attrs.readOnly ? attrs.readOnly : false;
this.tooltips = Tooltips.parametersEditor;
- let self = this;
- this.collection.on('update-parameters', function (compID, parameter) {
- self.collection.parent.reactions.map(function (reaction) {
+ this.filterAttr = attrs.attr;
+ this.filterKey = attrs.key;
+ this.collection.on('update-parameters', (compID, parameter) => {
+ this.collection.parent.reactions.map((reaction) => {
if(reaction.rate && reaction.rate.compID === compID){
reaction.rate = parameter;
if(reaction.reactionType !== 'custom-propensity') {
@@ -48,22 +49,23 @@ module.exports = View.extend({
}
}
});
- self.collection.parent.eventsCollection.map(function (event) {
- event.eventAssignments.map(function (assignment) {
+ this.collection.parent.eventsCollection.map((event) => {
+ event.eventAssignments.map((assignment) => {
if(assignment.variable.compID === compID) {
assignment.variable = parameter;
}
})
- if(event.selected)
- event.detailsView.renderEventAssignments();
+ if(event.selected) {
+ event.trigger('change-event');
+ }
});
- self.collection.parent.rules.map(function (rule) {
+ this.collection.parent.rules.map((rule) => {
if(rule.variable.compID === compID) {
rule.variable = parameter;
}
});
- if(self.parent.rulesView) {
- self.parent.rulesView.renderEditRules();
+ if(this.parent.rulesView) {
+ this.parent.rulesView.renderEditRules();
}
});
},
@@ -79,9 +81,9 @@ module.exports = View.extend({
$(this.queryByHook('edit-parameters')).removeClass('active');
$(this.queryByHook('view-parameters')).addClass('active');
}else{
- this.renderEditParameter();
+ this.renderEditParameter({'key': this.filterKey, 'attr': this.filterAttr});
}
- this.renderViewParameter();
+ this.renderViewParameter({'key': this.filterKey, 'attr': this.filterAttr});
},
addParameter: function () {
this.collection.addParameter();
@@ -109,7 +111,9 @@ module.exports = View.extend({
paramCollapseBtn.click();
paramCollapseBtn.html('-');
}
- app.switchToEditTab(this, "parameters");
+ if(!this.readOnly) {
+ app.switchToEditTab(this, "parameters");
+ }
},
renderEditParameter: function ({key=null, attr=null}={}) {
if(this.editParameterView){
@@ -155,6 +159,7 @@ module.exports = View.extend({
required: false,
name: 'filter',
valueType: 'string',
+ disabled: this.filterKey !== null,
placeholder: 'filter'
});
}
diff --git a/client/model-view/views/reactant-product.js b/client/model-view/views/reactant-product.js
index f3b82d54b9..17205590ea 100644
--- a/client/model-view/views/reactant-product.js
+++ b/client/model-view/views/reactant-product.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/model-view/views/reaction-restrict-to.js b/client/model-view/views/reaction-restrict-to.js
index 7f48966fc8..bc070c9bf6 100644
--- a/client/model-view/views/reaction-restrict-to.js
+++ b/client/model-view/views/reaction-restrict-to.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -15,7 +15,7 @@ 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 .
*/
-
+let $ = require('jquery');
//views
let View = require('ampersand-view');
let DomainTypesView = require('./component-types');
@@ -32,6 +32,8 @@ module.exports = View.extend({
render: function () {
View.prototype.render.apply(this, arguments);
this.renderDomainTypes();
+ this.toggleRestrictToError();
+ this.model.on('types-changed', this.toggleRestrictToError, this);
},
renderDomainTypes: function () {
if(this.domainTypesView) {
@@ -45,5 +47,15 @@ module.exports = View.extend({
return model.typeID != 0;
}}
);
+ },
+ toggleRestrictToError: function () {
+ let errorMsg = $(this.queryByHook("restict-to-error"));
+ if(this.model.types.length <= 0) {
+ errorMsg.addClass('component-invalid');
+ errorMsg.removeClass('component-valid');
+ }else{
+ errorMsg.addClass('component-valid');
+ errorMsg.removeClass('component-invalid');
+ }
}
});
\ No newline at end of file
diff --git a/client/model-view/views/reaction-view.js b/client/model-view/views/reaction-view.js
index 30f8042e32..9c4b81c066 100644
--- a/client/model-view/views/reaction-view.js
+++ b/client/model-view/views/reaction-view.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/model-view/views/reactions-view.js b/client/model-view/views/reactions-view.js
index 8efb1b97c7..6ed6424e31 100644
--- a/client/model-view/views/reactions-view.js
+++ b/client/model-view/views/reactions-view.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -48,6 +48,8 @@ module.exports = View.extend({
View.prototype.initialize.apply(this, arguments);
this.tooltips = Tooltips.reactionsEditor;
this.readOnly = attrs.readOnly ? attrs.readOnly : false;
+ this.filterAttr = attrs.attr;
+ this.filterKey = attrs.key;
if(!this.readOnly) {
this.collection.parent.species.on('add remove', this.toggleAddReactionButton, this);
this.collection.parent.parameters.on('add remove', this.updateMAState, this);
@@ -66,7 +68,7 @@ module.exports = View.extend({
$(this.queryByHook('edit-reactions')).removeClass('active');
$(this.queryByHook('view-reactions')).addClass('active');
}else{
- this.renderEditReactionView();
+ this.renderEditReactionView({'key': this.filterKey, 'attr': this.filterAttr});
this.toggleAddReactionButton();
this.updateMAState();
this.renderReactionTypes();
@@ -76,7 +78,7 @@ module.exports = View.extend({
displayMode: false,
output: 'html',
});
- this.renderViewReactionView();
+ this.renderViewReactionView({'key': this.filterKey, 'attr': this.filterAttr});
},
changeCollapseButtonText: function (e) {
app.changeCollapseButtonText(this, e);
@@ -126,8 +128,10 @@ module.exports = View.extend({
reacCollapseBtn.click();
reacCollapseBtn.html('-');
}
- app.switchToEditTab(this, "reactions");
- if(error.type !== "process") {
+ if(!this.readOnly) {
+ app.switchToEditTab(this, "reactions");
+ }
+ if(error && error.type !== "process") {
let reactionView = this.editReactionView.views.filter((reactView) => {
return reactView.model.compID === error.id;
})[0];
@@ -226,6 +230,7 @@ module.exports = View.extend({
required: false,
name: 'filter',
valueType: 'string',
+ disabled: this.filterKey !== null,
placeholder: 'filter'
});
}
diff --git a/client/model-view/views/rule-view.js b/client/model-view/views/rule-view.js
index 38a2d3c27e..0723cc05f8 100644
--- a/client/model-view/views/rule-view.js
+++ b/client/model-view/views/rule-view.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/model-view/views/rules-view.js b/client/model-view/views/rules-view.js
index 00870f4377..c8f9c89858 100644
--- a/client/model-view/views/rules-view.js
+++ b/client/model-view/views/rules-view.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -42,6 +42,8 @@ module.exports = View.extend({
this.collection.parent.species.on('add remove', this.toggleAddRuleButton, this);
this.collection.parent.parameters.on('add remove', this.toggleAddRuleButton, this);
this.tooltips = Tooltips.rulesEditor;
+ this.filterAttr = attrs.attr;
+ this.filterKey = attrs.key;
},
render: function () {
View.prototype.render.apply(this, arguments);
@@ -56,10 +58,10 @@ module.exports = View.extend({
$(this.queryByHook('edit-rules')).removeClass('active');
$(this.queryByHook('view-rules')).addClass('active');
}else {
- this.renderEditRules();
+ this.renderEditRules({'key': this.filterKey, 'attr': this.filterAttr});
this.toggleAddRuleButton();
}
- this.renderViewRules();
+ this.renderViewRules({'key': this.filterKey, 'attr': this.filterAttr});
},
addRule: function (e) {
let type = e.target.dataset.name;
@@ -88,7 +90,9 @@ module.exports = View.extend({
ruleCollapseBtn.click();
ruleCollapseBtn.html('-');
}
- app.switchToEditTab(this, "rules");
+ if(!this.readOnly) {
+ app.switchToEditTab(this, "rules");
+ }
},
renderDocs: function () {
let options = {
@@ -135,7 +139,7 @@ module.exports = View.extend({
app.tooltipSetup();
},
toggleAddRuleButton: function () {
- this.renderEditRules();
+ this.renderEditRules({'key': this.filterKey, 'attr': this.filterAttr});
let numSpecies = this.collection.parent.species.length;
let numParameters = this.collection.parent.parameters.length;
let disabled = numSpecies <= 0 && numParameters <= 0;
@@ -152,6 +156,7 @@ module.exports = View.extend({
required: false,
name: 'filter',
valueType: 'string',
+ disabled: this.filterKey !== null,
placeholder: 'filter'
});
}
diff --git a/client/model-view/views/sbml-components-view.js b/client/model-view/views/sbml-components-view.js
index 1fa52cde0c..79ee6ca7cc 100644
--- a/client/model-view/views/sbml-components-view.js
+++ b/client/model-view/views/sbml-components-view.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -22,6 +22,7 @@ let app = require('../../app');
let Tooltips = require('../../tooltips');
//views
let View = require('ampersand-view');
+let InputView = require('../../views/input');
let EditFunctionDefinition = require('./function-definition-view');
//templates
let template = require('../templates/sbmlComponentsView.pug');
@@ -30,13 +31,16 @@ module.exports = View.extend({
template: template,
events: {
'click [data-hook=collapse]' : 'changeCollapseButtonText',
- 'click [data-hook=collapse-function-definitions]' : 'changeCollapseButtonText'
+ 'click [data-hook=collapse-function-definitions]' : 'changeCollapseButtonText',
+ 'change [data-hook=function-definition-filter]' : 'functionDefinitionRules'
},
initialize: function (attrs, options) {
View.prototype.initialize.apply(this, arguments);
this.tooltips = Tooltips.sbmlComponentsEditor
this.functionDefinitions = attrs.functionDefinitions;
this.readOnly = attrs.readOnly ? attrs.readOnly : false;
+ this.filterAttr = attrs.attr;
+ this.filterKey = attrs.key;
},
render: function () {
View.prototype.render.apply(this, arguments);
@@ -50,35 +54,70 @@ module.exports = View.extend({
$(this.queryByHook('edit-function-definitions')).removeClass('active');
$(this.queryByHook('view-function-definitions')).addClass('active');
}else {
- this.renderEditFunctionDefinitionView();
+ this.renderEditFunctionDefinitionView({'key': this.filterKey, 'attr': this.filterAttr});
}
- this.renderViewFunctionDefinitionView();
+ this.renderViewFunctionDefinitionView({'key': this.filterKey, 'attr': this.filterAttr});
},
changeCollapseButtonText: function (e) {
app.changeCollapseButtonText(this, e);
},
- renderEditFunctionDefinitionView: function () {
+ openFuncDefSection: function () {
+ if(!$(this.queryByHook("function-definitions-list-container")).hasClass("show")) {
+ let funcDefCollapseBtn = $(this.queryByHook("collapse-function-definitions"));
+ funcDefCollapseBtn.click();
+ funcDefCollapseBtn.html('-');
+ }
+ if(!this.readOnly) {
+ app.switchToEditTab(this, "function-definitions");
+ }
+ },
+ openSection: function () {
+ if(!$(this.queryByHook("sbml-comonent-container")).hasClass("show")) {
+ let funcDefCollapseBtn = $(this.queryByHook("collapse"));
+ funcDefCollapseBtn.click();
+ funcDefCollapseBtn.html('-');
+ }
+ },
+ functionDefinitionRules: function (e) {
+ var key = e.target.value === "" ? null : e.target.value;
+ var attr = null;
+ if(key && key.includes(':')) {
+ let attrKey = key.split(':');
+ attr = attrKey[0].toLowerCase().replace(/ /g, '');
+ key = attrKey[1];
+ }
+ if(!this.readOnly) {
+ this.renderEditFunctionDefinitionView({'key': key, 'attr': attr});
+ }
+ this.renderViewFunctionDefinitionView({'key': key, 'attr': attr});
+ },
+ renderEditFunctionDefinitionView: function ({key=null, attr=null}={}) {
if(this.editFunctionDefinitionView){
this.editFunctionDefinitionView.remove();
}
+ let options = {filter: (model) => { return model.contains(attr, key); }}
this.editFunctionDefinitionView = this.renderCollection(
this.functionDefinitions,
EditFunctionDefinition,
- this.queryByHook('edit-function-definition-list')
+ this.queryByHook('edit-function-definition-list'),
+ options
);
app.tooltipSetup();
},
- renderViewFunctionDefinitionView: function () {
+ renderViewFunctionDefinitionView: function ({key=null, attr=null}={}) {
if(this.viewFunctionDefinitionView) {
this.viewFunctionDefinitionView.remove();
}
- let options = {viewOptions: {viewMode: true}};
this.containsMdlWithAnn = this.functionDefinitions.filter(function (model) {return model.annotation}).length > 0;
if(!this.containsMdlWithAnn) {
$(this.queryByHook("function-definition-annotation-header")).css("display", "none");
}else{
$(this.queryByHook("function-definition-annotation-header")).css("display", "block");
}
+ let options = {
+ viewOptions: {viewMode: true, hasAnnotations: this.containsMdlWithAnn},
+ filter: (model) => { return model.contains(attr, key); }
+ }
this.viewFunctionDefinitionView = this.renderCollection(
this.functionDefinitions,
EditFunctionDefinition,
@@ -86,5 +125,22 @@ module.exports = View.extend({
options
);
app.tooltipSetup();
+ },
+ update: function () {},
+ updateValid: function () {},
+ subviews: {
+ filter: {
+ hook: 'function-definition-filter',
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ required: false,
+ name: 'filter',
+ valueType: 'string',
+ disabled: this.filterKey !== null,
+ placeholder: 'filter'
+ });
+ }
+ }
}
});
\ No newline at end of file
diff --git a/client/model-view/views/specie-view.js b/client/model-view/views/specie-view.js
index ae1949d887..3fc2cafb69 100644
--- a/client/model-view/views/specie-view.js
+++ b/client/model-view/views/specie-view.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -102,8 +102,10 @@ module.exports = View.extend({
this.updateInputValidation();
if(!this.viewMode){
this.model.on('change', _.bind(this.updateViewer, this));
+ this.model.on('types-changed', this.toggleRestrictToError, this);
if(this.parent.spatial) {
this.renderTypes();
+ this.toggleRestrictToError();
}
}
},
@@ -170,6 +172,16 @@ module.exports = View.extend({
this.updateInputValidation();
this.toggleSwitchingSettingsInput();
},
+ toggleRestrictToError: function () {
+ let errorMsg = $(this.queryByHook("restict-to-error"));
+ if(this.model.types.length <= 0) {
+ errorMsg.addClass('component-invalid');
+ errorMsg.removeClass('component-valid');
+ }else{
+ errorMsg.addClass('component-valid');
+ errorMsg.removeClass('component-invalid');
+ }
+ },
toggleSwitchingSettings: function () {
if(this.model.mode === "dynamic"){
$(this.queryByHook('switching-tol')).prop('disabled', false);
diff --git a/client/model-view/views/species-view.js b/client/model-view/views/species-view.js
index b55220fd05..f0bb0eb25a 100644
--- a/client/model-view/views/species-view.js
+++ b/client/model-view/views/species-view.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -41,17 +41,18 @@ module.exports = View.extend({
this.template = this.spatial ? spatialSpeciesTemplate : speciesTemplate;
this.tooltips = Tooltips.speciesEditor;
this.defaultMode = attrs.defaultMode;
- let self = this;
- this.collection.on('update-species', function (compID, specie, isNameUpdate, isDefaultMode) {
- self.collection.parent.reactions.forEach(function (reaction) {
+ this.filterAttr = attrs.attr;
+ this.filterKey = attrs.key;
+ this.collection.on('update-species', (compID, specie, isNameUpdate, isDefaultMode) => {
+ this.collection.parent.reactions.forEach((reaction) => {
var changedReaction = false;
- reaction.reactants.forEach(function (reactant) {
+ reaction.reactants.forEach((reactant) => {
if(reactant.specie.compID === compID) {
reactant.specie = specie;
changedReaction = true;
}
});
- reaction.products.forEach(function (product) {
+ reaction.products.forEach((product) => {
if(product.specie.compID === compID) {
product.specie = specie;
changedReaction = true;
@@ -61,23 +62,23 @@ module.exports = View.extend({
reaction.trigger('change-reaction');
}
});
- self.collection.parent.eventsCollection.forEach(function (event) {
- event.eventAssignments.forEach(function (assignment) {
+ this.collection.parent.eventsCollection.forEach((event) => {
+ event.eventAssignments.forEach((assignment) => {
if(assignment.variable.compID === compID) {
assignment.variable = specie;
}
})
if(isNameUpdate && event.selected) {
- event.detailsView.renderEventAssignments();
+ event.trigger('change-event');
}
});
- self.collection.parent.rules.forEach(function (rule) {
+ this.collection.parent.rules.forEach((rule) => {
if(rule.variable.compID === compID) {
rule.variable = specie;
}
});
if(isNameUpdate) {
- self.parent.rulesView.renderEditRules();
+ this.parent.rulesView.renderEditRules();
}
});
},
@@ -94,9 +95,9 @@ module.exports = View.extend({
$(this.queryByHook('view-species')).addClass('active');
}else{
this.toggleSpeciesCollectionError();
- this.renderEditSpeciesView();
+ this.renderEditSpeciesView({'key': this.filterKey, 'attr': this.filterAttr});
}
- this.renderViewSpeciesView();
+ this.renderViewSpeciesView({'key': this.filterKey, 'attr': this.filterAttr});
},
addSpecies: function () {
if(this.parent.model.domain.types) {
@@ -206,6 +207,7 @@ module.exports = View.extend({
required: false,
name: 'filter',
valueType: 'string',
+ disabled: this.filterKey !== null,
placeholder: 'filter'
});
}
diff --git a/client/model-view/views/stoich-species-view.js b/client/model-view/views/stoich-species-view.js
index 01df300036..b0a19a1e3b 100644
--- a/client/model-view/views/stoich-species-view.js
+++ b/client/model-view/views/stoich-species-view.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/models/action.js b/client/models/action.js
new file mode 100644
index 0000000000..1e7a862e76
--- /dev/null
+++ b/client/models/action.js
@@ -0,0 +1,94 @@
+/*
+StochSS is a platform for simulating biochemical systems
+Copyright (C) 2019-2022 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
+let Point = require('./point');
+let State = require('ampersand-state');
+
+module.exports = State.extend({
+ props: {
+ c: 'number',
+ enable: 'boolean',
+ filename: 'string',
+ fixed: 'boolean',
+ mass: 'number',
+ nu: 'number',
+ priority: 'number',
+ rho: 'number',
+ scope: 'string',
+ shape: 'string',
+ subdomainFile: 'string',
+ transformation: 'string',
+ type: 'string',
+ typeID: 'number',
+ vol: 'number'
+ },
+ children: {
+ point: Point,
+ newPoint: Point
+ },
+ session: {
+ selected: {
+ type: 'boolean',
+ default: false
+ },
+ },
+ initialize: function (attrs, options) {
+ State.prototype.initialize.apply(this, arguments);
+ },
+ contains: function (attr, key) {
+ if(key === null) { return true; }
+
+ let checks = {
+ 'c': this.c === key,
+ 'disabled': !this.enable,
+ 'enable': this.enable,
+ 'filename': this.filename.includes(key),
+ 'fixed': this.fixed,
+ 'mass': this.mass === key,
+ 'nu': this.nu === key,
+ 'priority': this.priority === key,
+ 'rho': this.rho === key,
+ 'scope': this.scope === key,
+ 'shape': this.shape.includes(key),
+ 'subdomainfile': this.subdomainFile.includes(key),
+ 'transformation': this.transformation.includes(key),
+ 'type': this.type === key,
+ 'typeid': this.collection.parent.types.get(this.typeID, 'typeID').name === key,
+ 'vol': this.vol === key
+ }
+
+ if(attr !== null) {
+ let otherAttrs = {
+ 'speedofsound': 'c', 'static': 'fixed', 'viscosity': 'nu',
+ 'density': 'rho', 'type_id': 'typeid', 'volume': 'vol'
+ }
+ if(Object.keys(otherAttrs).includes(attr)) {
+ attr = otherAttrs[attr];
+ }
+ return checks[attr];
+ }
+ for(let attribute in checks) {
+ if(checks[attribute]) { return true; }
+ }
+ return false;
+ },
+ validate: function () {
+ return true;
+ }
+});
\ No newline at end of file
diff --git a/client/models/actions.js b/client/models/actions.js
new file mode 100644
index 0000000000..7f0d788633
--- /dev/null
+++ b/client/models/actions.js
@@ -0,0 +1,67 @@
+/*
+StochSS is a platform for simulating biochemical systems
+Copyright (C) 2019-2022 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 .
+*/
+
+let _ = require('underscore');
+//models
+let Point = require('./point');
+let Action = require('./action');
+//collections
+let Collection = require('ampersand-collection');
+
+module.exports = Collection.extend({
+ model: Action,
+ addAction: function (type, {action=null}={}) {
+ if(action) {
+ action.type = type;
+ }else{
+ action = new Action({
+ c: 10,
+ enable: false,
+ filename: '',
+ fixed: false,
+ mass: 1.0,
+ nu: 0.0,
+ priority: 1,
+ rho: 1.0,
+ scope: 'Multi Particle',
+ shape: '',
+ subdomainFile: '',
+ transformation: '',
+ type: type,
+ typeID: 0,
+ vol: 1.0
+ });
+ action.selected = true;
+ action.point = this.getNewPoint();
+ action.newPoint = this.getNewPoint();
+ }
+ this.add(action);
+ },
+ getNewPoint: function (action) {
+ return new Point({x: 0, y: 0, z: 0});
+ },
+ removeAction: function (action) {
+ this.remove(action);
+ },
+ validateCollection: function () {
+ if(this.length <= 0) { return false; }
+ if(this.parent.particles === undefined) { return false; }
+ if(this.parent.particles.length <= 0) { return false; }
+ return true;
+ }
+});
diff --git a/client/models/boundary-condition.js b/client/models/boundary-condition.js
index 0fc3db918b..c95f3d0ab4 100644
--- a/client/models/boundary-condition.js
+++ b/client/models/boundary-condition.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/models/boundary-conditions.js b/client/models/boundary-conditions.js
index f6e5110d32..5f1c4d17ab 100644
--- a/client/models/boundary-conditions.js
+++ b/client/models/boundary-conditions.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/models/creator.js b/client/models/creator.js
index e92b96392d..f38e6c3363 100644
--- a/client/models/creator.js
+++ b/client/models/creator.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/models/creators.js b/client/models/creators.js
index ca80dec981..e10b8184af 100644
--- a/client/models/creators.js
+++ b/client/models/creators.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/models/domain-type.js b/client/models/domain-type.js
index 16584666e4..1e34db83e1 100644
--- a/client/models/domain-type.js
+++ b/client/models/domain-type.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -23,7 +23,6 @@ module.exports = State.extend({
props: {
c: 'number',
fixed: 'boolean',
- geometry: 'string',
mass: 'number',
name: 'string',
nu: 'number',
@@ -32,6 +31,10 @@ module.exports = State.extend({
volume: 'number'
},
session: {
+ inUse: {
+ type: 'boolean',
+ default: false,
+ },
numParticles: 'number',
selected: {
type: 'boolean',
diff --git a/client/models/domain-types.js b/client/models/domain-types.js
index 9ec4e5ac59..0d786c271a 100644
--- a/client/models/domain-types.js
+++ b/client/models/domain-types.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -30,7 +30,6 @@ module.exports = Collection.extend({
let type = new Type({
c: 10,
fixed: false,
- geometry: "",
mass: 1.0,
name: name,
nu: 0.0,
@@ -46,6 +45,8 @@ module.exports = Collection.extend({
this.remove(type);
},
validateCollection: function () {
+ if(this.models[0].numParticles > 0) { return false; }
+ if(this.length <= 1) { return false; }
return true;
}
});
\ No newline at end of file
diff --git a/client/models/domain.js b/client/models/domain.js
index 137e9c38f4..00c06d3d9b 100644
--- a/client/models/domain.js
+++ b/client/models/domain.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -17,10 +17,12 @@ along with this program. If not, see .
*/
//models
-var State = require('ampersand-state');
+let State = require('ampersand-state');
//collections
+let Shapes = require('./shapes');
+let Actions = require('./actions');
let Types = require('./domain-types');
-let Particles = require('./particles');
+let Transformations = require('./transformations');
module.exports = State.extend({
props: {
@@ -37,37 +39,28 @@ module.exports = State.extend({
template_version: 'number'
},
collections: {
- types: Types,
- particles: Particles
+ actions: Actions,
+ shapes: Shapes,
+ transformations: Transformations,
+ types: Types
},
session: {
def_particle_id: 'number',
def_type_id: 'number',
directory: 'string',
- dirname: 'string'
+ dirname: 'string',
+ particles: 'object',
+ error: 'object'
},
initialize: function (attrs, options) {
State.prototype.initialize.apply(this, arguments)
- this.particles.on('add change remove', this.updateValid, this);
- this.def_particle_id = this.particles.length;
this.def_type_id = this.types.length;
- let self = this;
- this.particles.forEach(function (particle) {
- if(particle.particle_id >= self.def_particle_id) {
- self.def_particle_id = particle.particle_id + 1;
- }
- });
- this.types.forEach(function (type) {
- if(type.typeID >= self.def_type_id) {
- self.def_type_id = type.typeID + 1;
+ this.types.forEach((type) => {
+ if(type.typeID >= this.def_type_id) {
+ this.def_type_id = type.typeID + 1;
}
});
},
- getDefaultID: function () {
- let id = this.def_particle_id;
- this.def_particle_id += 1;
- return id;
- },
getDefaultTypeID: function () {
let id = this.def_type_id;
this.def_type_id += 1;
@@ -84,14 +77,16 @@ module.exports = State.extend({
type.typeID = id;
}
});
- this.particles.forEach((particle) => {
- if(particle.type > oldType) {
- particle.type -= 1;
- }
- });
},
validateModel: function () {
- if(!this.particles.validateCollection()) return false;
+ if(!this.types.validateCollection()) {
+ this.error = {"type":"types"};
+ return false;
+ }
+ if(!this.actions.validateCollection()) {
+ this.error = {"type":"actions"};
+ return false;
+ }
return true;
},
updateValid: function () {
diff --git a/client/models/event-assignment.js b/client/models/event-assignment.js
index fe7f72f5e8..ae5db1463c 100644
--- a/client/models/event-assignment.js
+++ b/client/models/event-assignment.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/models/event-assignments.js b/client/models/event-assignments.js
index c26e83c17a..e769035304 100644
--- a/client/models/event-assignments.js
+++ b/client/models/event-assignments.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/models/event.js b/client/models/event.js
index 78a2126b55..b3994ae12b 100644
--- a/client/models/event.js
+++ b/client/models/event.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -39,7 +39,7 @@ module.exports = State.extend({
session: {
selected: {
type: 'boolean',
- default: true,
+ default: false,
},
advanced_error: 'boolean'
},
diff --git a/client/models/events.js b/client/models/events.js
index a22303eb39..113458faae 100644
--- a/client/models/events.js
+++ b/client/models/events.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -37,6 +37,7 @@ module.exports = Collection.extend({
initialValue: false,
persistent: false,
useValuesFromTriggerTime: false,
+ selected: true
});
event.eventAssignments.addEventAssignment()
this.parent.updateValid()
diff --git a/client/models/function-definition.js b/client/models/function-definition.js
index 9fb082b46f..5e9adf21b2 100644
--- a/client/models/function-definition.js
+++ b/client/models/function-definition.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -28,5 +28,22 @@ module.exports = State.extend({
variables: 'string',
signature: 'string',
annotation: 'string'
+ },
+ contains: function (attr, key) {
+ if(key === null) { return true; }
+
+ let checks = {
+ 'name': this.name.includes(key),
+ 'signature': this.name.includes(key),
+ 'expression': this.expression.includes(key)
+ }
+
+ if(attr !== null) {
+ return checks[attr];
+ }
+ for(let attribute in checks) {
+ if(checks[attribute]) { return true; }
+ }
+ return false;
}
});
\ No newline at end of file
diff --git a/client/models/function-definitions.js b/client/models/function-definitions.js
index 143b2086a3..cecb4297b2 100644
--- a/client/models/function-definitions.js
+++ b/client/models/function-definitions.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/models/initial-condition.js b/client/models/initial-condition.js
index 7d8cfd9659..ed09f7393f 100644
--- a/client/models/initial-condition.js
+++ b/client/models/initial-condition.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -22,6 +22,7 @@ var Specie = require('./specie');
module.exports = State.extend({
props: {
+ compID: 'number',
icType: 'string',
annotation: 'string',
count: 'any',
@@ -71,5 +72,9 @@ module.exports = State.extend({
if(checks[attribute]) { return true; }
}
return false;
+ },
+ validateComponent: function () {
+ if(this.icType !== "Place" && this.types.length <= 0) { return false; }
+ return true;
}
});
\ No newline at end of file
diff --git a/client/models/initial-conditions.js b/client/models/initial-conditions.js
index f1f037498d..b7194e34cb 100644
--- a/client/models/initial-conditions.js
+++ b/client/models/initial-conditions.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -23,8 +23,11 @@ var InitialCondition = require('./initial-condition');
module.exports = Collection.extend({
model: InitialCondition,
+ indexes: ['compID'],
addInitialCondition: function (initialConditionType, types) {
+ var id = this.parent.getDefaultID();
var initialCondition = new InitialCondition({
+ compID: id,
icType: initialConditionType,
annotation: "",
types: types,
@@ -43,4 +46,13 @@ module.exports = Collection.extend({
removeInitialCondition: function (initialCondition) {
this.remove(initialCondition);
},
+ validateCollection: function () {
+ for(var i = 0; i < this.length; i++) {
+ if(!this.models[i].validateComponent()) {
+ this.parent.error = {'id':this.models[i].compID,'type':'initialCondition'};
+ return false;
+ }
+ }
+ return true;
+ }
});
\ No newline at end of file
diff --git a/client/models/job.js b/client/models/job.js
index 40cb8db071..97d7b8317e 100644
--- a/client/models/job.js
+++ b/client/models/job.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/models/jobs.js b/client/models/jobs.js
index eb1fcc99a3..c42797fb52 100644
--- a/client/models/jobs.js
+++ b/client/models/jobs.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/models/me.js b/client/models/me.js
index c88cc9ed35..54b239e7e1 100644
--- a/client/models/me.js
+++ b/client/models/me.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/models/metadata.js b/client/models/metadata.js
index 8d7318f8de..bca5325c0b 100644
--- a/client/models/metadata.js
+++ b/client/models/metadata.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/models/model.js b/client/models/model.js
index c993e86c91..7d126dae57 100644
--- a/client/models/model.js
+++ b/client/models/model.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -96,32 +96,33 @@ module.exports = Model.extend({
this.rules.on('add change remove', this.updateValid, this);
},
validateModel: function () {
- if(!this.species.validateCollection(this.is_spatial)) return false;
- if(!this.parameters.validateCollection()) return false;
- if(!this.reactions.validateCollection()) return false;
- if(!this.eventsCollection.validateCollection()) return false;
- if(!this.rules.validateCollection()) return false;
+ if(this.is_spatial && this.domain.validateModel() === false) {
+ this.error = {"type":"domain"};
+ return false;
+ };
+ if(!this.species.validateCollection(this.is_spatial)) { return false; }
+ if(this.is_spatial && !this.initialConditions.validateCollection()) { return false; }
+ if(!this.parameters.validateCollection()) { return false; }
+ if(!this.reactions.validateCollection(this.is_spatial)) { return false; }
+ if(!this.eventsCollection.validateCollection()) { return false; }
+ if(!this.rules.validateCollection()) { return false; }
if(!this.is_spatial && this.reactions.length <= 0 && this.eventsCollection.length <= 0 && this.rules.length <= 0) {
- this.error = {"type":"process"}
+ this.error = {"type":"process"};
return false;
}
if(!this.volume === "" || isNaN(this.volume)) {
- this.error = {"type":"volume"}
- return false
- };
- if(this.domain.validateModel() === false) {
- this.error = {"type":"domain"}
- return false
+ this.error = {"type":"volume"};
+ return false;
};
- if(this.modelSettings.validate() === false) {
- this.error = {"type":"timespan"}
- return false
+ if(this.modelSettings.validate(this.is_spatial) === false) {
+ this.error = {"type":"timespan"};
+ return false;
};
return true;
},
updateValid: function () {
- this.error = {}
- this.valid = this.validateModel()
+ this.error = {};
+ this.valid = this.validateModel();
},
getDefaultID: function () {
var id = this.defaultID;
diff --git a/client/models/parameter-sweep-settings.js b/client/models/parameter-sweep-settings.js
index 073bc1f1bf..b8e78b9b66 100644
--- a/client/models/parameter-sweep-settings.js
+++ b/client/models/parameter-sweep-settings.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/models/parameter.js b/client/models/parameter.js
index a617b43bb7..33ff770af1 100644
--- a/client/models/parameter.js
+++ b/client/models/parameter.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/models/parameters.js b/client/models/parameters.js
index 2279bc59ee..904bb01353 100644
--- a/client/models/parameters.js
+++ b/client/models/parameters.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/models/particle.js b/client/models/particle.js
index 0a685631bf..82f1ad2b11 100644
--- a/client/models/particle.js
+++ b/client/models/particle.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/models/particles.js b/client/models/particles.js
index 968f1f6ff5..ccb13f5a51 100644
--- a/client/models/particles.js
+++ b/client/models/particles.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -24,40 +24,4 @@ var Collection = require('ampersand-collection');
module.exports = Collection.extend({
model: Particle,
indexes: ['particle_id'],
- addParticle: function ({particle=null, point=[0,0,0], vol=1, mass=1, type=0, nu=0, fixed=false, c=10, rho=1}={}) {
- let id = this.parent.getDefaultID();
- if(particle) {
- particle.particle_id = id;
- }else{
- particle = new Particle({
- c: c,
- fixed: fixed,
- mass: mass,
- nu: nu,
- particle_id: id,
- point: point,
- rho: rho,
- type: type,
- volume: vol
- });
- }
- this.add(particle);
- },
- removeParticle: function (particle) {
- this.remove(particle);
- },
- removeParticles: function (particles) {
- let self = this;
- particles.forEach(function (particle) {
- self.removeParticle(particle)
- });
- },
- validateCollection: function () {
- for(var i = 0; i < this.length; i++) {
- if(this.models[i].type < 1) {
- return false;
- }
- }
- return true;
- }
});
\ No newline at end of file
diff --git a/client/models/point.js b/client/models/point.js
new file mode 100644
index 0000000000..cc4cf57193
--- /dev/null
+++ b/client/models/point.js
@@ -0,0 +1,75 @@
+/*
+StochSS is a platform for simulating biochemical systems
+Copyright (C) 2019-2022 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
+let State = require('ampersand-state');
+
+module.exports = State.extend({
+ props: {
+ x: 'number',
+ y: 'number',
+ z: 'number',
+ },
+ initialize: function (attrs, options) {
+ State.prototype.initialize.apply(this, arguments);
+ },
+ contains: function (attr, key) {
+ if(key === null) { return true; }
+
+ let centers = [`[${this.x}, ${this.y}, ${this.z}]`, `[${this.x},${this.y},${this.z}]`];
+
+ let checks = {
+ 'center': centers.includes(key),
+ 'x': this.x === key,
+ 'y': this.y === key,
+ 'z': this.z === key,
+ }
+
+ if(attr !== null) {
+ let otherAttrs = {
+ 'point': 'center', 'normal': 'center',
+ 'point1': 'center','point2': 'center','point3': 'center'
+ }
+ if(Object.keys(otherAttrs).includes(attr)) {
+ attr = otherAttrs[attr];
+ }
+ return checks[attr];
+ }
+ for(let attribute in checks) {
+ if(checks[attribute]) { return true; }
+ }
+ return false
+ },
+ validate: function () {
+ return true;
+ },
+ derived: {
+ inUse: {
+ deps: ['x', 'y', 'z'],
+ fn: function () {
+ if(this.x !== 0) {
+ return true;
+ }
+ if(this.y !== 0) {
+ return true;
+ }
+ return this.z !== 0;
+ }
+ }
+ }
+});
\ No newline at end of file
diff --git a/client/models/presentation.js b/client/models/presentation.js
index a3799b596a..8d9347afd4 100644
--- a/client/models/presentation.js
+++ b/client/models/presentation.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/models/project.js b/client/models/project.js
index a8db193934..358457ad75 100644
--- a/client/models/project.js
+++ b/client/models/project.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/models/reaction.js b/client/models/reaction.js
index 67685f921d..9709d2963a 100644
--- a/client/models/reaction.js
+++ b/client/models/reaction.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -73,11 +73,19 @@ module.exports = State.extend({
if(this.reactionType === "custom-propensity") { return }
var odePropensity = this.rate.name;
var propensity = this.rate.name;
-
+
+ let reactants = {}
this.reactants.forEach((stoichSpecies) => {
- let name = stoichSpecies.specie.name;
- if(stoichSpecies.ratio == 2) {
- odePropensity += ` * ${name} * ${name}`;
+ if(Object.keys(reactants).includes(stoichSpecies.specie.name)) {
+ reactants[stoichSpecies.specie.name] += stoichSpecies.ratio;
+ }else{
+ reactants[stoichSpecies.specie.name] = stoichSpecies.ratio;
+ }
+ });
+
+ Object.keys(reactants).forEach((name) => {
+ if(reactants[name] == 2) {
+ odePropensity = `${propensity} * ${name} * ${name}`;
propensity = `${propensity} * ${name} * (${name} - 1) / vol`;
}else{
odePropensity += ` * ${name}`;
@@ -85,7 +93,7 @@ module.exports = State.extend({
}
})
- let order = this.reactants.length;
+ let order = reactants.length;
if(order == 2) {
propensity += " / vol";
}else if(order == 0) {
@@ -237,13 +245,14 @@ module.exports = State.extend({
}
return "custom-massaction"
},
- validateComponent: function () {
- if(!this.name.trim() || this.name.match(/^\d/)) return false;
- if((!/^[a-zA-Z0-9_]+$/.test(this.name))) return false;
- if(!this.propensity.trim() && this.reactionType === "custom-propensity") return false;
+ validateComponent: function (isSpatial) {
+ if(!this.name.trim() || this.name.match(/^\d/)) { return false; }
+ if((!/^[a-zA-Z0-9_]+$/.test(this.name))) { return false; }
+ if(!this.propensity.trim() && this.reactionType === "custom-propensity") { return false; }
if(this.reactionType.startsWith('custom')) {
- if(this.reactants.length <= 0 && this.products.length <= 0) return false;
+ if(this.reactants.length <= 0 && this.products.length <= 0) { return false; }
}
+ if(isSpatial && this.types.length <= 0) { return false; }
return true;
}
});
\ No newline at end of file
diff --git a/client/models/reactions.js b/client/models/reactions.js
index f609f339c5..9fdc867292 100644
--- a/client/models/reactions.js
+++ b/client/models/reactions.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -85,14 +85,14 @@ Reactions = Collection.extend({
this.remove(reaction);
this.parent.updateValid()
},
- validateCollection: function () {
+ validateCollection: function (isSpatial) {
for(var i = 0; i < this.length; i++) {
- if(!this.models[i].validateComponent()) {
- this.parent.error = {'id':this.models[i].compID,'type':'reaction'}
- return false
+ if(!this.models[i].validateComponent(isSpatial)) {
+ this.parent.error = {'id':this.models[i].compID,'type':'reaction'};
+ return false;
}
}
- return true
+ return true;
}
});
diff --git a/client/models/results-settings.js b/client/models/results-settings.js
index e41e7e37a4..a9799161f1 100644
--- a/client/models/results-settings.js
+++ b/client/models/results-settings.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/models/rule.js b/client/models/rule.js
index aeebe6bea0..0bfc19b1f3 100644
--- a/client/models/rule.js
+++ b/client/models/rule.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/models/rules.js b/client/models/rules.js
index 09d6b9c846..215ba88710 100644
--- a/client/models/rules.js
+++ b/client/models/rules.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/models/settings.js b/client/models/settings.js
index 85f9cfd0ee..5dba94e6ed 100644
--- a/client/models/settings.js
+++ b/client/models/settings.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/models/shape.js b/client/models/shape.js
new file mode 100644
index 0000000000..ad6c2dce2b
--- /dev/null
+++ b/client/models/shape.js
@@ -0,0 +1,95 @@
+/*
+StochSS is a platform for simulating biochemical systems
+Copyright (C) 2019-2022 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
+let State = require('ampersand-state');
+
+module.exports = State.extend({
+ props: {
+ deltar: 'number',
+ deltas: 'number',
+ deltax: 'number',
+ deltay: 'number',
+ deltaz: 'number',
+ depth: 'number',
+ fillable: 'boolean',
+ formula: 'string',
+ height: 'number',
+ lattice: 'string',
+ length: 'number',
+ name: 'string',
+ radius: 'number',
+ type: 'string'
+ },
+ session: {
+ selected: {
+ type: 'boolean',
+ default: false
+ },
+ inUse: {
+ type: 'boolean',
+ default: false
+ }
+ },
+ derived: {
+ notEditable: {
+ deps: ['fillable'],
+ fn: function () {
+ return this.fillable;
+ }
+ }
+ },
+ initialize: function (attrs, options) {
+ State.prototype.initialize.apply(this, arguments);
+ },
+ contains: function (attr, key) {
+ if(key === null) { return true; }
+
+ let checks = {
+ 'deltar': ["Spherical Lattice", "Cylindrical Lattice"].includes(this.lattice) &&
+ this.deltar === key,
+ 'deltas': ["Spherical Lattice", "Cylindrical Lattice"].includes(this.lattice) &&
+ this.deltas === key,
+ 'deltax': this.lattice === "Cartesian Lattice" && this.deltax === key,
+ 'deltay': this.lattice === "Cartesian Lattice" && this.deltay === key,
+ 'deltaz': this.lattice === "Cartesian Lattice" && this.deltaz === key,
+ 'depth': this.lattice === "Cartesian Lattice" && this.depth === key,
+ 'formula': this.formula.includes(key),
+ 'height': this.lattice === "Cartesian Lattice" && this.height === key,
+ 'lattice': this.lattice === key,
+ 'length': ["Cartesian Lattice", "Cylindrical Lattice"].includes(this.lattice) &&
+ this.length === key,
+ 'name': this.name.includes(key),
+ 'radius': ["Spherical Lattice", "Cylindrical Lattice"].includes(this.lattice) &&
+ this.radius === key,
+ 'type': this.type === key,
+ }
+
+ if(attr !== null) {
+ return checks[attr];
+ }
+ for(let attribute in checks) {
+ if(checks[attribute]) { return true; }
+ }
+ return false
+ },
+ validate: function () {
+ if((!/^[a-zA-Z0-9_]+$/.test(this.name))) return false;
+ return true;
+ }
+});
diff --git a/client/models/shapes.js b/client/models/shapes.js
new file mode 100644
index 0000000000..173f78b87f
--- /dev/null
+++ b/client/models/shapes.js
@@ -0,0 +1,65 @@
+/*
+StochSS is a platform for simulating biochemical systems
+Copyright (C) 2019-2022 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 .
+*/
+
+let _ = require('underscore');
+//models
+let Shape = require('./shape');
+//collections
+let Collection = require('ampersand-collection');
+
+module.exports = Collection.extend({
+ model: Shape,
+ indexes: ['name'],
+ addShape: function (type) {
+ let name = this.getDefaultName();
+ let shape = new Shape({
+ deltar: 0,
+ deltas: 0,
+ deltax: 0,
+ deltay: 0,
+ deltaz: 0,
+ depth: 0,
+ fillable: false,
+ formula: '',
+ height: 0,
+ lattice: type,
+ length: 0,
+ name: name,
+ radius: 0,
+ type: 'Standard'
+ });
+ this.add(shape);
+ return name;
+ },
+ getDefaultName: function () {
+ var i = this.length + 1;
+ var name = `shape${i}`;
+ var names = this.map((shape) => { return shape.name; });
+ while(_.contains(names, name)) {
+ i += 1;
+ name = `shape${i}`;
+ }
+ return name;
+ },
+ removeShape: function (shape) {
+ this.remove(shape);
+ },
+ validateCollection: function () {
+ return true;
+ }
+});
diff --git a/client/models/simulation-settings.js b/client/models/simulation-settings.js
index b49870d447..bd091c4fc0 100644
--- a/client/models/simulation-settings.js
+++ b/client/models/simulation-settings.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/models/specie.js b/client/models/specie.js
index 698a32ebe6..a8b2a9d674 100644
--- a/client/models/specie.js
+++ b/client/models/specie.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -78,14 +78,15 @@ module.exports = State.extend({
}
return false;
},
- validateComponent: function () {
- if(!this.name.trim() || this.name.match(/^\d/)) return false;
- if((!/^[a-zA-Z0-9_]+$/.test(this.name))) return false;
- if(this.value === "" || isNaN(this.value)) return false;
+ validateComponent: function (isSpatial) {
+ if(!this.name.trim() || this.name.match(/^\d/)) { return false; }
+ if((!/^[a-zA-Z0-9_]+$/.test(this.name))) { return false; }
+ if(this.value === "" || isNaN(this.value)) { return false; }
if(this.mode === "dynamic") {
- if(this.isSwitchTol && (this.switchTol === "" || isNaN(this.switchTol))) return false;
- if(!this.isSwitchTol && (this.switchMin === "" || isNaN(this.switchMin))) return false;
+ if(this.isSwitchTol && (this.switchTol === "" || isNaN(this.switchTol))) { return false; }
+ if(!this.isSwitchTol && (this.switchMin === "" || isNaN(this.switchMin))) { return false; }
}
+ if(isSpatial && this.types.length <= 0) { return false; }
return true;
}
});
\ No newline at end of file
diff --git a/client/models/species.js b/client/models/species.js
index dcba8eadc2..c35b3bd2b5 100644
--- a/client/models/species.js
+++ b/client/models/species.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -58,13 +58,13 @@ module.exports = Collection.extend({
},
validateCollection: function (isSpatial) {
if(this.length <= 0 && !isSpatial) {
- this.parent.error = {'type':'species'}
+ this.parent.error = {'type':'species'};
return false;
}
for(var i = 0; i < this.length; i++) {
- if(!this.models[i].validateComponent()) {
- this.parent.error = {'id':this.models[i].compID,'type':'species'}
- return false
+ if(!this.models[i].validateComponent(isSpatial)) {
+ this.parent.error = {'id':this.models[i].compID,'type':'species'};
+ return false;
}
}
return true;
diff --git a/client/models/stoich-specie.js b/client/models/stoich-specie.js
index 293062fa3f..ff9abe9be3 100644
--- a/client/models/stoich-specie.js
+++ b/client/models/stoich-specie.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/models/stoich-species.js b/client/models/stoich-species.js
index 4fc9006f68..ff9fc034c0 100644
--- a/client/models/stoich-species.js
+++ b/client/models/stoich-species.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/models/sweep-parameter.js b/client/models/sweep-parameter.js
index 13b45fbad1..2506e45e19 100644
--- a/client/models/sweep-parameter.js
+++ b/client/models/sweep-parameter.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/models/sweep-parameters.js b/client/models/sweep-parameters.js
index 68c58af6d2..65e51e57a8 100644
--- a/client/models/sweep-parameters.js
+++ b/client/models/sweep-parameters.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/models/timespan-settings.js b/client/models/timespan-settings.js
index 78618766e3..cf8dc643a0 100644
--- a/client/models/timespan-settings.js
+++ b/client/models/timespan-settings.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -39,9 +39,10 @@ module.exports = State.extend({
initialize: function (attrs, options) {
State.prototype.initialize.apply(this, arguments)
},
- validate: function () {
- if(this.endSim === "" || isNaN(this.endSim)) return false;
- if(this.timeStep === "" || isNaN(this.timeStep)) return false;
+ validate: function (isSpatial) {
+ if(this.endSim === "" || isNaN(this.endSim)) { return false; }
+ if(this.timeStep === "" || isNaN(this.timeStep)) { return false; }
+ if(isSpatial && this.timestepSize > this.timeStep) { return false; }
return true;
}
});
\ No newline at end of file
diff --git a/client/models/transformation.js b/client/models/transformation.js
new file mode 100644
index 0000000000..5d4be22b8a
--- /dev/null
+++ b/client/models/transformation.js
@@ -0,0 +1,109 @@
+/*
+StochSS is a platform for simulating biochemical systems
+Copyright (C) 2019-2022 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
+let Point = require('./point');
+let State = require('ampersand-state');
+
+module.exports = State.extend({
+ props: {
+ angle: 'number',
+ factor: 'number',
+ name: 'string',
+ transformation: 'string',
+ type: 'string',
+ vector: 'object'
+ },
+ children: {
+ center: Point,
+ normal: Point,
+ point1: Point,
+ point2: Point,
+ point3: Point
+ },
+ session: {
+ selected: {
+ type: 'boolean',
+ default: false
+ },
+ inUse: {
+ type: 'boolean',
+ default: false
+ }
+ },
+ initialize: function (attrs, options) {
+ State.prototype.initialize.apply(this, arguments);
+ },
+ contains: function (attr, key) {
+ if(key === null) { return true; }
+
+ let checks = {
+ 'angle': this.angle === Number(key),
+ 'factor': this.factor === Number(key),
+ 'name': this.name.includes(key),
+ 'transformation': this.transformation.includes(key),
+ 'type': this.type === key
+ }
+
+ if(attr !== null) {
+ let otherAttrs = {
+ 'centerx': 'x', 'centery': 'y', 'centerz': 'z',
+ 'normalx': 'x', 'normaly': 'y', 'normalz': 'z',
+ 'point1x': 'x', 'point1y': 'y', 'point1z': 'z',
+ 'point2x': 'x', 'point2y': 'y', 'point2z': 'z',
+ 'point3x': 'x', 'point3y': 'y', 'point3z': 'z'
+ }
+ let pointCheck = this.setPointCheck(attr)
+ if(Object.keys(otherAttrs).includes(attr)) {
+ attr = otherAttrs[attr];
+ }
+ checks['center'] = this.center.contains(attr, key);
+ checks['normal'] = this.normal.contains(attr, key);
+ checks['point1'] = this.point1.contains(attr, key);
+ checks['point2'] = this.point2.contains(attr, key);
+ checks['point3'] = this.point3.contains(attr, key);
+ if(['x', 'y', 'z'].includes(attr)) {
+ if(pointCheck !== null) {
+ return checks[pointCheck]
+ }
+ let points = ['center', 'normal', 'point1', 'point2', 'point3'];
+ for(let attribute in points) {
+ if(checks[attribute]) { return true; }
+ }
+ return false;
+ }
+ return checks[attr];
+ }
+ for(let attribute in checks) {
+ if(checks[attribute]) { return true; }
+ }
+ return false;
+ },
+ setPointCheck: function (attr) {
+ if(attr.includes('center')) { return "center"; }
+ if(attr.includes('normal')) { return "normal"; }
+ if(attr.includes('point1')) { return "point1"; }
+ if(attr.includes('point2')) { return "point2"; }
+ if(attr.includes('point3')) { return "point3"; }
+ return null;
+ },
+ validate: function () {
+ if((!/^[a-zA-Z0-9_]+$/.test(this.name))) return false;
+ return true;
+ }
+});
\ No newline at end of file
diff --git a/client/models/transformations.js b/client/models/transformations.js
new file mode 100644
index 0000000000..3bf5ca2afe
--- /dev/null
+++ b/client/models/transformations.js
@@ -0,0 +1,70 @@
+/*
+StochSS is a platform for simulating biochemical systems
+Copyright (C) 2019-2022 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 .
+*/
+
+let _ = require('underscore');
+//models
+let Point = require('./point');
+let Transformation = require('./transformation');
+//collections
+let Collection = require('ampersand-collection');
+
+module.exports = Collection.extend({
+ model: Transformation,
+ indexes: ['name'],
+ addTransformation: function (type) {
+ let name = this.getDefaultName();
+ let transformation = new Transformation({
+ angle: 0,
+ factor: 1,
+ name: name,
+ transformation: "",
+ type: type,
+ });
+ transformation.selected = true;
+ transformation.vector = this.getNewVector();
+ transformation.center = this.getNewPoint();
+ transformation.normal = this.getNewPoint();
+ transformation.point1 = this.getNewPoint();
+ transformation.point2 = this.getNewPoint();
+ transformation.point3 = this.getNewPoint();
+ this.add(transformation);
+ return name;
+ },
+ getDefaultName: function () {
+ var i = this.length + 1;
+ var name = 'transformation' + i;
+ var names = this.map(function (transformation) {return transformation.name; });
+ while(_.contains(names, name)) {
+ i += 1;
+ name = 'transformation' + i;
+ }
+ return name;
+ },
+ getNewPoint: function () {
+ return new Point({x: 0, y: 0, z: 0});
+ },
+ getNewVector: function () {
+ return [this.getNewPoint(), this.getNewPoint()];
+ },
+ removeTransformation: function (transformation) {
+ this.remove(transformation);
+ },
+ validateCollection: function () {
+ return true;
+ }
+});
diff --git a/client/models/user-settings.js b/client/models/user-settings.js
new file mode 100644
index 0000000000..44cc65bc3d
--- /dev/null
+++ b/client/models/user-settings.js
@@ -0,0 +1,47 @@
+/*
+StochSS is a platform for simulating biochemical systems
+Copyright (C) 2019-2022 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 .
+*/
+
+let path = require('path');
+//support files
+let app = require('../app');
+//models
+let Model = require('ampersand-model');
+
+module.exports = Model.extend({
+ url: function () {
+ return path.join(app.getApiPath(), "user-settings");
+ },
+ props: {
+ awsAccessKeyID: 'string',
+ awsRegion: 'string',
+ headNode: 'string',
+ userLogs: 'boolean'
+ },
+ session: {
+ awsHeadNodeStatus: 'string',
+ awsSecretKey: 'string',
+ modelLoaded: 'boolean'
+ },
+ applySettings: function (cb, {secretKey=null}={}) {
+ let data = {
+ settings: this.toJSON(),
+ secret_key: secretKey
+ }
+ app.postXHR(this.url(), data, { success: cb });
+ }
+});
diff --git a/client/models/workflow-group.js b/client/models/workflow-group.js
index b1e86e8e92..73acf20ddb 100644
--- a/client/models/workflow-group.js
+++ b/client/models/workflow-group.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/models/workflow-groups.js b/client/models/workflow-groups.js
index c64bb2dbec..1edd573b48 100644
--- a/client/models/workflow-groups.js
+++ b/client/models/workflow-groups.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/models/workflow.js b/client/models/workflow.js
index b82d02340f..e84f875fe2 100644
--- a/client/models/workflow.js
+++ b/client/models/workflow.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/models/workflows.js b/client/models/workflows.js
index d783c5e3c0..8be760cffb 100644
--- a/client/models/workflows.js
+++ b/client/models/workflows.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/page-help.js b/client/page-help.js
index 5fdd5407dc..8f48813aee 100644
--- a/client/page-help.js
+++ b/client/page-help.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/pages/base.js b/client/pages/base.js
index c588e1ef5f..9377244414 100644
--- a/client/pages/base.js
+++ b/client/pages/base.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/pages/browser.js b/client/pages/browser.js
index 463af56413..9f22e6ee74 100644
--- a/client/pages/browser.js
+++ b/client/pages/browser.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/pages/domain-editor.js b/client/pages/domain-editor.js
index 9a489a61e9..f8a6e64af8 100644
--- a/client/pages/domain-editor.js
+++ b/client/pages/domain-editor.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -29,6 +29,7 @@ let DomainView = require('../domain-view/domain-view');
let PageView = require('../pages/base');
//templates
let template = require('../templates/pages/domainEditor.pug');
+let errorTemplate = require('../templates/pages/errorTemplate.pug');
import initPage from './page.js';
@@ -64,6 +65,12 @@ let DomainEditor = PageView.extend({
this.domain.dirname = newDomain && !modelPath ? domainPath : null
this.model = this.buildModel(body.model, modelPath);
this.renderSubviews();
+ },
+ error: (err, response, body) => {
+ this.title = `${response.statusCode} ${body.Reason.toUpperCase()}`;
+ this.errMsg = body.Message;
+ this.logoPath = "/static/stochss-logo.png";
+ this.renderErrorView();
}
});
},
@@ -172,6 +179,10 @@ let DomainEditor = PageView.extend({
});
app.registerRenderSubview(this, this.domainView, "domain-view-container");
},
+ renderErrorView: function () {
+ this.template = errorTemplate;
+ PageView.prototype.render.apply(this, arguments);
+ },
renderSubviews: function () {
let breadData = this.getBreadcrumbData();
if(breadData.model && breadData.project) {
diff --git a/client/pages/example-library.js b/client/pages/example-library.js
index 6f0b74b4ff..be644fb069 100644
--- a/client/pages/example-library.js
+++ b/client/pages/example-library.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/pages/home.js b/client/pages/home.js
index 0dc762a273..d0c9d62aee 100644
--- a/client/pages/home.js
+++ b/client/pages/home.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/pages/job-presentation.js b/client/pages/job-presentation.js
index b59e979bc7..ad0c95aa82 100644
--- a/client/pages/job-presentation.js
+++ b/client/pages/job-presentation.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -57,6 +57,9 @@ let JobPresentationPage = PageView.extend({
this.renderSubviews(false);
},
error: (err, response, body) => {
+ this.logoPath = "/hub/static/stochss-logo.png";
+ this.title = "404 PAGE NOT FOUND";
+ this.errMsg = `This ${this.fileType} presentation was removed by the author and is no longer available.`;
this.renderSubviews(true);
}
});
diff --git a/client/pages/loading-page.js b/client/pages/loading-page.js
index 70eb4b8891..17ba1bddae 100644
--- a/client/pages/loading-page.js
+++ b/client/pages/loading-page.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -22,9 +22,9 @@ let path = require('path');
let app = require('../app');
let modals = require('../modals');
// views
-var PageView = require('./base');
+let PageView = require('./base');
// templates
-var template = require('../templates/pages/loadingPage.pug');
+let template = require('../templates/pages/loadingPage.pug');
import initPage from './page.js';
@@ -39,7 +39,6 @@ let LoadingPage = PageView.extend({
},
render: function (attrs, options) {
PageView.prototype.render.apply(this, arguments);
- let self = this;
$(document.querySelector("div[data-hook=side-navbar]")).css("display", "none");
$(document.querySelector("main[data-hook=page-main]")).removeClass().addClass("col-md-12 body");
$(this.queryByHook("loading-spinner")).css("display", "block");
@@ -56,75 +55,73 @@ let LoadingPage = PageView.extend({
$(this.queryByHook("loading-target")).css("display", "none")
let message = `If the file is a Project, Workflow, Model, Domain, or Notebook it will be opened when the upload has completed.`;
$(this.queryByHook("loading-message")).html(message);
- let self = this;
- let queryStr = "?path=" + filePath + "&cmd=validate";
+ let queryStr = `?path=${filePath}&cmd=validate`;
let endpoint = path.join(app.getApiPath(), 'file/upload-from-link') + queryStr;
app.getXHR(endpoint, {
- success: function (err, response, body) {
+ success: (err, response, body) => {
if(!body.exists) {
- self.uploadFileFromLink(filePath, false);
+ this.uploadFileFromLink(filePath, false);
}else{
let title = "File Already Exists";
let message = "A file with that name already exists, do you wish to overwrite this file?";
let modal = $(modals.uploadFileExistsHtml(title, message)).modal();
let yesBtn = document.querySelector("#uploadFileExistsModal .yes-modal-btn");
let noBtn = document.querySelector("#uploadFileExistsModal .btn-secondary")
- yesBtn.addEventListener('click', function (e) {
+ yesBtn.addEventListener('click', (e) => {
modal.modal('hide');
- self.uploadFileFromLink(filePath, true);
+ this.uploadFileFromLink(filePath, true);
});
- noBtn.addEventListener('click', function (e) {
- window.location.href = self.homeLink;
+ noBtn.addEventListener('click', (e) => {
+ window.location.href = this.homeLink;
});
}
},
- error: function(err, response, body) {
+ error: (err, response, body) => {
if(document.querySelector("#errorModal")) {
document.querySelector("#errorModal").remove();
}
- $(self.queryByHook("loading-spinner")).css("display", "none");
+ $(this.queryByHook("loading-spinner")).css("display", "none");
let modal = $(modals.errorHtml(body.Reason, body.Message)).modal();
- modal.on('hidden.bs.modal', function (e) {
- window.location.href = self.homeLink;
+ modal.on('hidden.bs.modal', (e) => {
+ window.location.href = this.homeLink;
});
}
});
},
getUploadResponse: function () {
- let self = this;
- setTimeout(function () {
- let queryStr = "?path=" + self.responsePath + "&cmd=read";
+ setTimeout(() => {
+ let queryStr = `?path=${this.responsePath}&cmd=read`;
let endpoint = path.join(app.getApiPath(), 'file/upload-from-link') + queryStr;
- let errorCB = function (err, response, body) {
+ let errorCB = (err, response, body) => {
if(document.querySelector("#errorModal")) {
document.querySelector("#errorModal").remove();
}
- $(self.queryByHook("loading-spinner")).css("display", "none");
+ $(this.queryByHook("loading-spinner")).css("display", "none");
let modal = $(modals.errorHtml(body.reason, body.message)).modal();
- modal.on('hidden.bs.modal', function (e) {
- window.location.href = self.homeLink;
+ modal.on('hidden.bs.modal', (e) => {
+ window.location.href = this.homeLink;
});
}
app.getXHR(endpoint, {
- success: function (err, response, body) {
+ success: (err, response, body) => {
if(Object.keys(body).includes("reason")) {
errorCB(err, response, body);
}else if(body.done) {
if(body.file_path.endsWith(".proj")){
- self.openStochSSPage("stochss/project/manager", body.file_path);
+ this.openStochSSPage("stochss/project/manager", body.file_path);
}else if(body.file_path.endsWith(".wkfl")){
- self.openStochSSPage("stochss/workflow/edit", body.file_path);
+ this.openStochSSPage("stochss/workflow/edit", body.file_path);
}else if(body.file_path.endsWith(".mdl")){
- self.openStochSSPage("stochss/models/edit", body.file_path);
+ this.openStochSSPage("stochss/models/edit", body.file_path);
}else if(body.file_path.endsWith(".domn")){
- self.openStochSSPage("stochss/domain/edit", body.file_path);
+ this.openStochSSPage("stochss/domain/edit", body.file_path);
}else if(body.file_path.endsWith(".ipynb")){
- self.openNotebookFile(body.file_path);
+ this.openNotebookFile(body.file_path);
}else{
- self.openStochSSPage('stochss/files');
+ this.openStochSSPage('stochss/files');
}
}else{
- self.getUploadResponse();
+ this.getUploadResponse();
}
},
error: errorCB
@@ -147,13 +144,12 @@ let LoadingPage = PageView.extend({
$(this.queryByHook("loading-header")).html("Updating Format");
$(this.queryByHook("loading-target")).html(filePath.split('/').pop());
$(this.queryByHook("loading-message")).html(message);
- let self = this;
- let queryStr = "?path=" + filePath;
+ let queryStr = `?path=${filePath}`;
let endpoint = path.join(app.getApiPath(), target, "update-format") + queryStr;
app.getXHR(endpoint, {
- success: function (err, response, body) {
+ success: (err, response, body) => {
let dst = target === "workflow" ? body : filePath;
- self.openStochSSPage(identifier, dst);
+ this.openStochSSPage(identifier, dst);
}
});
},
@@ -168,19 +164,18 @@ let LoadingPage = PageView.extend({
this.updateFormat(filePath, message, "workflow", identifier);
},
uploadFileFromLink: function (filePath, overwrite) {
- setTimeout(function () {
- $(self.queryByHook("loading-problem").css("display", "block"));
+ setTimeout(() => {
+ $(this.queryByHook("loading-problem")).css("display", "block");
}, 30000);
- let self = this;
- var queryStr = "?path=" + filePath;
+ var queryStr = `?path=${filePath}`;
if(overwrite) {
- queryStr += "&overwrite=" + overwrite;
+ queryStr += `&overwrite=${overwrite}`;
}
let endpoint = path.join(app.getApiPath(), 'file/upload-from-link') + queryStr;
app.getXHR(endpoint, {
- success: function (err, response, body) {
- self.responsePath = body.responsePath;
- self.getUploadResponse();
+ success: (err, response, body) => {
+ this.responsePath = body.responsePath;
+ this.getUploadResponse();
}
});
}
diff --git a/client/pages/model-editor.js b/client/pages/model-editor.js
index ab48e71a30..8fd76438b9 100644
--- a/client/pages/model-editor.js
+++ b/client/pages/model-editor.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -138,7 +138,10 @@ let ModelEditor = PageView.extend({
}, 5000);
},
focusOnError: function (e) {
- let mdlSections = ["species", "parameter", "reaction", "process", "event", "rule", "volume", "domain"];
+ let mdlSections = [
+ "species", "parameter", "reaction", "process", "event",
+ "rule", "volume", "domain", "initialCondition"
+ ];
if(this.model.error) {
if(this.model.error.type === "timespan") {
this.openTimespanSection();
@@ -148,6 +151,7 @@ let ModelEditor = PageView.extend({
setTimeout(() => {
let inputErrors = this.queryAll(".input-invalid");
let componentErrors = this.queryAll(".component-invalid");
+ console.log(inputErrors, componentErrors)
if(componentErrors.length > 0) {
componentErrors[0].scrollIntoView({'block':"center"});
}else if(inputErrors.length > 0) {
@@ -251,6 +255,7 @@ let ModelEditor = PageView.extend({
},
handleSimulateClick: function (e) {
let errorMsg = $(this.queryByHook("error-detected-msg"));
+ this.model.updateValid();
if(!this.model.valid) {
this.displayError(errorMsg, e);
}else{
@@ -303,7 +308,7 @@ let ModelEditor = PageView.extend({
tspnCollapseBtn.click();
tspnCollapseBtn.html('-');
}
- this.switchToEditTab(this.modelSettings, "timespan");
+ app.switchToEditTab(this.modelSettings, "timespan");
},
plotResults: function (data) {
this.ran(true);
diff --git a/client/pages/model-presentation.js b/client/pages/model-presentation.js
index fe3d43eac3..2c8fb413dd 100644
--- a/client/pages/model-presentation.js
+++ b/client/pages/model-presentation.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -24,6 +24,7 @@ let bootstrap = require('bootstrap');
let app = require('../app');
//models
let Model = require('../models/model');
+let Particles = require('../models/particles');
//views
let PageView = require('./base');
let ModelView = require('../model-view/model-view');
@@ -52,11 +53,17 @@ let ModelPresentationPage = PageView.extend({
app.getXHR(endpoint, {
success: (err, response, body) => {
this.model.set(body.model);
+ if(Object.keys(body.model.domain).includes("particles")) {
+ let particles = new Particles(body.model.domain.particles);
+ this.model.domain.particles = particles;
+ }
let domainPlot = Boolean(body.domainPlot) ? body.domainPlot : null;
this.renderSubviews(false, domainPlot);
},
error: (err, response, body) => {
- this.notFound = true;
+ this.logoPath = "/hub/static/stochss-logo.png";
+ this.title = "404 PAGE NOT FOUND";
+ this.errMsg = `This ${this.fileType} presentation was removed by the author and is no longer available.`;
this.renderSubviews(true);
}
});
diff --git a/client/pages/multiple-plots.js b/client/pages/multiple-plots.js
index 908e86c0cb..48e1508cde 100644
--- a/client/pages/multiple-plots.js
+++ b/client/pages/multiple-plots.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/pages/notebook-presentation.js b/client/pages/notebook-presentation.js
index 7553762842..35714cc268 100644
--- a/client/pages/notebook-presentation.js
+++ b/client/pages/notebook-presentation.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -41,16 +41,18 @@ let NotebookPresentationPage = PageView.extend({
let owner = urlParams.get("owner");
let file = urlParams.get("file");
this.fileType = "Notebook";
- let self = this;
let queryStr = "?owner=" + owner + "&file=" + file;
let endpoint = "api/notebook/load" + queryStr;
app.getXHR(endpoint, {
- success: function (err, response, body) {
- self.name = body.file.split('/').pop().split('.ipynb')[0];
- self.renderSubviews(false, body.html);
+ success: (err, response, body) => {
+ this.name = body.file.split('/').pop().split('.ipynb')[0];
+ this.renderSubviews(false, body.html);
},
- error: function (err, response, body) {
- self.renderSubviews(true, null);
+ error: (err, response, body) => {
+ this.logoPath = "/hub/static/stochss-logo.png";
+ this.title = "404 PAGE NOT FOUND";
+ this.errMsg = `This ${this.fileType} presentation was removed by the author and is no longer available.`;
+ this.renderSubviews(true, null);
}
});
let downloadStart = "/stochss/notebook/download_presentation";
diff --git a/client/pages/page.js b/client/pages/page.js
index a91d6a2bcf..e8855e89a5 100644
--- a/client/pages/page.js
+++ b/client/pages/page.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/pages/project-manager.js b/client/pages/project-manager.js
index e2122d1060..2346697474 100644
--- a/client/pages/project-manager.js
+++ b/client/pages/project-manager.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/pages/quickstart.js b/client/pages/quickstart.js
index fea0c53566..2ca96e3b8e 100644
--- a/client/pages/quickstart.js
+++ b/client/pages/quickstart.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/pages/user-settings.js b/client/pages/user-settings.js
new file mode 100644
index 0000000000..40be934556
--- /dev/null
+++ b/client/pages/user-settings.js
@@ -0,0 +1,297 @@
+/*
+StochSS is a platform for simulating biochemical systems
+Copyright (C) 2019-2022 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 .
+*/
+
+let $ = require('jquery');
+let path = require('path');
+// support files
+let app = require('../app');
+let tests = require('../views/tests');
+// models
+let Settings = require('../models/user-settings');
+// views
+let PageView = require('./base');
+let InputView = require('../views/input');
+let SelectView = require('ampersand-select-view');
+// templates
+let template = require('../templates/pages/userSettings.pug');
+
+import initPage from './page.js';
+
+let userSettings = PageView.extend({
+ template: template,
+ events: {
+ 'change [data-hook=user-logs]' : 'toggleUserLogs',
+ 'change [data-target=aws-credentials]' : 'toggleAWSComputeNodeSection',
+ 'change [data-hook=aws-secretaccesskey-container]' : 'handleSetSecretKey',
+ 'change [data-hook=aws-instancetype-container]' : 'handleSelectInstanceType',
+ 'change [data-hook=aws-instancesize-container]' : 'handleSelectInstanceSize',
+ 'click [data-hook=refresh-aws-status]' : 'handleRefreshAWSStatus',
+ 'click [data-hook=launch-aws-cluster]' : 'handleLaunchAWSCluster',
+ 'click [data-hook=terminate-aws-cluster]' : 'handleTerminateAWSCluster',
+ 'click [data-hook=apply-user-settings]' : 'handleApplyUserSettings'
+ },
+ initialize: function (attrs, options) {
+ PageView.prototype.initialize.apply(this, arguments);
+ let urlParams = new URLSearchParams(window.location.search);
+ this.path = urlParams.has('continue') ? urlParams.get('continue') : null;
+ this.model = new Settings();
+ this.secretKey = null;
+ app.getXHR(this.model.url(), {
+ success: (err, response, body) => {
+ this.model.set(body.settings);
+ this.model.modelLoaded = true;
+ this.instances = body.instances;
+ if(this.model.headNode === "") {
+ this.awsType = "";
+ this.awsSize = "";
+ }else{
+ let data = this.model.headNode.split('.')
+ this.awsType = data[0];
+ this.awsSize = data[1];
+ this.renderAWSInstanceSizesView();
+ }
+ $(this.queryByHook('user-logs')).prop('checked', this.model.userLogs);
+ this.renderAWSInstanceTypesView();
+ this.toggleAWSComputeNodeSection();
+ this.refreshAWSStatus();
+ }
+ });
+ },
+ render: function (attrs, options) {
+ PageView.prototype.render.apply(this, arguments);
+ if(this.path !== null) {
+ $(this.queryByHook('aws-config-msg')).css('display', 'block');
+ }
+ },
+ completeAction: function () {
+ $(this.queryByHook("usa-in-progress")).css("display", "none");
+ $(this.queryByHook("usa-complete")).css("display", "inline-block");
+ setTimeout(() => {
+ $(this.queryByHook("usa-complete")).css("display", "none");
+ }, 5000);
+ },
+ disables: function (btnType, status) {
+ let disables = {
+ instance: !['not configured', 'not launched', 'terminated'].includes(status),
+ launch: !['not launched', 'terminated'].includes(status),
+ refresh: status === "not configured",
+ terminate: ['not configured', 'not launched', 'terminated'].includes(status)
+ }
+ return disables[btnType];
+ },
+ errorAction: function (action) {
+ $(this.queryByHook("usa-in-progress")).css("display", "none");
+ $(this.queryByHook("usa-action-error")).text(action);
+ $(this.queryByHook("usa-error")).css("display", "block");
+ },
+ handleApplyUserSettings: function ({cb=null}={}) {
+ this.startAction();
+ if(cb === null) {
+ if(this.path === null) {
+ cb = () => {
+ this.completeAction();
+ this.refreshAWSStatus();
+ }
+ }else{
+ cb = () => {
+ window.location.href = this.path;
+ }
+ }
+ }
+ let options = this.secretKey !== null ? {secretKey: this.secretKey} : {};
+ this.model.applySettings(cb, options);
+ },
+ handleLaunchAWSCluster: function () {
+ this.handleApplyUserSettings({cb: () => {
+ this.completeAction();
+ this.launchAWSCluster();
+ }});
+ },
+ handleRefreshAWSStatus: function () {
+ this.handleApplyUserSettings({cb: () => {
+ this.completeAction();
+ this.refreshAWSStatus();
+ }});
+ },
+ handleSelectInstanceSize: function (e) {
+ this.awsSize = e.target.value;
+ this.model.headNode = this.awsSize === "" ? "" : `${this.awsType}.${this.awsSize}`;
+ this.updateAWSStatus();
+ },
+ handleSelectInstanceType: function (e) {
+ this.awsType = e.target.value;
+ this.awsSize = "";
+ this.model.headNode = "";
+ this.renderAWSInstanceSizesView();
+ this.updateAWSStatus();
+ },
+ handleSetSecretKey: function (e) {
+ this.secretKey = e.target.value;
+ this.model.awsSecretKey = this.secretKey ? "set" : null;
+ this.toggleAWSComputeNodeSection();
+ },
+ handleTerminateAWSCluster: function () {
+ this.handleApplyUserSettings({cb: () => {
+ this.completeAction();
+ this.terminateAWSCluster();
+ }});
+ },
+ launchAWSCluster: function () {
+ let endpoint = path.join(app.getApiPath(), 'aws/launch-cluster');
+ app.getXHR(endpoint, {
+ success: (err, response, body) => {
+ this.model.awsHeadNodeStatus = body.settings.awsHeadNodeStatus;
+ this.updateAWSStatus();
+ }
+ });
+ },
+ refreshAWSStatus: function () {
+ if(this.model.headNode === "") { return }
+ let endpoint = path.join(app.getApiPath(), 'aws/cluster-status');
+ app.getXHR(endpoint, {
+ success: (err, response, body) => {
+ this.model.awsHeadNodeStatus = body.settings.awsHeadNodeStatus;
+ this.updateAWSStatus();
+ }
+ });
+ },
+ renderAWSInstanceSizesView: function () {
+ if(this.awsInstanceSizesView) {
+ this.awsInstanceSizesView.remove();
+ }
+ if(this.awsType === "") { return }
+ let options = this.instances[this.awsType];
+ this.awsInstanceSizesView = new SelectView({
+ name: 'aws-instance-size',
+ required: false,
+ idAttributes: 'cid',
+ options: options,
+ value: this.awsSize,
+ unselectedText: "-- Select Instance Size --"
+ });
+ let hook = "aws-instancesize-container";
+ app.registerRenderSubview(this, this.awsInstanceSizesView, hook);
+ },
+ renderAWSInstanceTypesView: function () {
+ if(this.awsInstanceTypesView) {
+ this.awsInstanceTypesView.remove();
+ }
+ let options = Object.keys(this.instances);
+ this.awsInstanceTypesView = new SelectView({
+ name: 'aws-instance-type',
+ required: false,
+ idAttributes: 'cid',
+ options: options,
+ value: this.awsType,
+ unselectedText: "-- Select Instance Type --"
+ });
+ let hook = "aws-instancetype-container";
+ app.registerRenderSubview(this, this.awsInstanceTypesView, hook);
+ },
+ startAction: function () {
+ $(this.queryByHook("usa-complete")).css("display", "none");
+ $(this.queryByHook("usa-error")).css("display", "none");
+ $(this.queryByHook("usa-in-progress")).css("display", "inline-block");
+ },
+ terminateAWSCluster: function () {
+ let endpoint = path.join(app.getApiPath(), 'aws/terminate-cluster');
+ app.getXHR(endpoint, {
+ success: (err, response, body) => {
+ this.model.awsHeadNodeStatus = body.settings.awsHeadNodeStatus;
+ this.updateAWSStatus();
+ }
+ });
+ },
+ toggleAWSComputeNodeSection: function () {
+ let regionSet = this.model.awsRegion !== "";
+ let accessKeySet = this.model.awsAccessKeyID !== "";
+ let secretKeySet = this.model.awsSecretKey !== null;
+ let display = regionSet && accessKeySet && secretKeySet ? "block" : "none";
+ $(this.queryByHook('aws-compute-node-section')).css('display', display);
+ if(display === "block") {
+ this.updateAWSStatus();
+ }
+ },
+ toggleUserLogs: function (e) {
+ this.model.userLogs = e.target.checked;
+ },
+ update: function () {},
+ updateAWSStatus: function () {
+ let awsStatus = this.model.headNode === "" ? "not configured" : this.model.awsHeadNodeStatus;
+ $(this.queryByHook('aws-instancetype-container').firstChild.children[1]).prop(
+ 'disabled', this.disables('instance', awsStatus)
+ );
+ if(this.awsType !== "") {
+ $(this.queryByHook('aws-instancesize-container').firstChild.children[1]).prop(
+ 'disabled', this.disables('instance', awsStatus)
+ );
+ }
+ $(this.queryByHook('refresh-user-settings')).prop('disabled', this.disables('refresh', awsStatus));
+ $(this.queryByHook('aws-headnode-status')).text(awsStatus);
+ $(this.queryByHook('launch-aws-cluster')).prop('disabled', this.disables('launch', awsStatus));
+ $(this.queryByHook('terminate-aws-cluster')).prop('disabled', this.disables('terminate', awsStatus));
+ },
+ updateValid: function () {},
+ subviews: {
+ awsRegionInputView: {
+ hook: 'aws-region-container',
+ waitFor: 'model.modelLoaded',
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ required: false,
+ name: 'aws-region',
+ modelKey: 'awsRegion',
+ valueType: 'string',
+ value: this.model.awsRegion,
+ placeholder: "-- i.e. us-east-2 --"
+ });
+ }
+ },
+ awsAccessKeyID: {
+ hook: 'aws-accesskeyid-container',
+ waitFor: 'model.modelLoaded',
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ required: false,
+ name: 'aws-access-key-id',
+ modelKey: 'awsAccessKeyID',
+ valueType: 'string',
+ value: this.model.awsAccessKeyID
+ });
+ }
+ },
+ awsSecretAccessKey: {
+ hook: 'aws-secretaccesskey-container',
+ waitFor: 'model.modelLoaded',
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ type: 'password',
+ required: false,
+ name: 'aws-secret-access-key',
+ valueType: 'string',
+ value: this.model.awsSecretKey
+ });
+ }
+ }
+ }
+});
+
+initPage(userSettings);
diff --git a/client/pages/users-home.js b/client/pages/users-home.js
index b099ad41e5..acda68093c 100644
--- a/client/pages/users-home.js
+++ b/client/pages/users-home.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/pages/workflow-manager.js b/client/pages/workflow-manager.js
index 07acc47fd8..63b6d16b60 100644
--- a/client/pages/workflow-manager.js
+++ b/client/pages/workflow-manager.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -46,7 +46,7 @@ let WorkflowManager = PageView.extend({
'click [data-hook=save-model]' : 'handleSaveWorkflow',
'click [data-hook=collapse-settings]' : 'changeCollapseButtonText',
'click [data-hook=save]' : 'clickSaveHandler',
- 'click [data-hook=start-job]' : 'clickStartJobHandler',
+ 'click [data-target=start-job]' : 'clickStartJobHandler',
'click [data-hook=edit-model]' : 'clickEditModelHandler',
'click [data-hook=collapse-jobs]' : 'changeCollapseButtonText',
'click [data-hook=return-to-project-btn]' : 'handleReturnToProject'
@@ -91,7 +91,7 @@ let WorkflowManager = PageView.extend({
app.changeCollapseButtonText(this, e);
},
clickEditModelHandler: function (e) {
- this.handleSaveWorkflow(e, _.bind(function () {
+ this.handleSaveWorkflow(_.bind(function () {
let queryStr = "?path=" + this.model.model;
let endpoint = path.join(app.getBasePath(), "stochss/models/edit") + queryStr;
window.location.href = endpoint;
@@ -99,33 +99,28 @@ let WorkflowManager = PageView.extend({
},
clickSaveHandler: function (e) {
this.saving();
- this.handleSaveWorkflow(e, _.bind(this.saved, this));
+ this.handleSaveWorkflow(_.bind(this.saved, this));
},
clickStartJobHandler: function (e) {
this.saving();
- let types = {
- "Ensemble Simulation": "gillespy",
- "Parameter Sweep": "parameterSweep",
- "Spatial Ensemble Simulation": "spatial"
- };
- let data = {
- "settings": this.model.settings.toJSON(), "mdl_path": this.model.model,
- "type": types[this.model.type], "time_stamp": this.getTimeStamp()
- };
- let queryStr = `?path=${this.model.directory}&data=${JSON.stringify(data)}`;
- let initEndpoint = `${path.join(app.getApiPath(), "workflow/init-job")}${queryStr}`;
- app.getXHR(initEndpoint, {
- success: (err, response, body) => {
- this.saved();
- let runQuery = `?path=${body}&type=${data.type}`;
- let runEndpoint = `${path.join(app.getApiPath(), "workflow/run-job")}${runQuery}`;
- app.getXHR(runEndpoint, {
- success: (err, response, body) => {
- this.updateWorkflow(true);
- }
- });
- }
- });
+ if(e.target.dataset['computeenv'] === "local") {
+ this.startJob()
+ }else if(e.target.dataset['computeenv'] === "aws") {
+ let endpoint = path.join(app.getApiPath(), 'aws/job-config-check');
+ app.getXHR(endpoint, {
+ success: (err, response, body) => {
+ this.startJob({compute: "AWS"});
+ },
+ error: (err, response, body) => {
+ this.handleSaveWorkflow(() => {
+ this.saved();
+ let contEndpoint = `${path.join(app.getBasePath(), 'stochss/workflow/edit')}${window.location.search}`;
+ let endpoint = `${path.join(app.getBasePath(), 'stochss/settings')}?continue=${contEndpoint}`;
+ window.location.href = endpoint;
+ });
+ }
+ });
+ }
},
getTimeStamp: function () {
if(!this.model.newFormat) {
@@ -178,7 +173,7 @@ let WorkflowManager = PageView.extend({
},
handleReturnToProject: function (e) {
if(this.model.activeJob.model.directory){
- this.handleSaveWorkflow(e, _.bind(function () {
+ this.handleSaveWorkflow(_.bind(function () {
let queryStr = "?path=" + this.projectPath;
let endpoint = path.join(app.getBasePath(), "stochss/project/manager") + queryStr;
window.location.href = endpoint;
@@ -189,18 +184,16 @@ let WorkflowManager = PageView.extend({
window.location.href = endpoint;
}
},
- handleSaveWorkflow: function (e, cb) {
- let self = this;
- let endpoint = this.model.url();
- app.postXHR(endpoint, this.model.toJSON(), {
- success: function (err, response, body) {
+ handleSaveWorkflow: function (cb) {
+ app.postXHR(this.model.url(), this.model.toJSON(), {
+ success: (err, response, body) => {
if(cb) {
cb();
}else{
- $(self.queryByHook("src-model")).css("display", "none");
- let oldFormRdyState = !self.model.newFormat && self.model.activeJob.status === "ready";
- if(oldFormRdyState || self.model.newFormat) {
- self.setupSettingsView();
+ $(this.queryByHook("src-model")).css("display", "none");
+ let oldFormRdyState = !this.model.newFormat && this.model.activeJob.status === "ready";
+ if(oldFormRdyState || this.model.newFormat) {
+ this.setupSettingsView();
}
}
}
@@ -313,10 +306,12 @@ let WorkflowManager = PageView.extend({
this.renderActiveJob();
},
setupSettingsView: function () {
- if(this.model.newFormat) {
- $(this.queryByHook("start-job")).text("Start New Job");
+ if(!this.model.newFormat) {
+ $(this.queryByHook("of-start-job")).css('display', 'inline-block');
+ $(this.queryByHook("nf-start-job")).css('display', 'none');
}
if(this.model.type === "Parameter Sweep"){
+ $(this.queryByHook("aws-start-job")).addClass("disabled")
if(this.model.settings.parameterSweepSettings.parameters.length < 1) {
$(this.queryByHook("start-job")).prop("disabled", true);
}
@@ -345,6 +340,32 @@ let WorkflowManager = PageView.extend({
this.renderSettingsView(options);
}
},
+ startJob: function ({compute="Local"}={}) {
+ let types = {
+ "Ensemble Simulation": "gillespy",
+ "Parameter Sweep": "parameterSweep",
+ "Spatial Ensemble Simulation": "spatial"
+ };
+ let data = {
+ "settings": this.model.settings.toJSON(), "mdl_path": this.model.model,
+ "type": types[this.model.type], "time_stamp": this.getTimeStamp(),
+ "compute": compute
+ };
+ let queryStr = `?path=${this.model.directory}&data=${JSON.stringify(data)}`;
+ let initEndpoint = `${path.join(app.getApiPath(), "workflow/init-job")}${queryStr}`;
+ app.getXHR(initEndpoint, {
+ success: (err, response, body) => {
+ this.saved();
+ let runQuery = `?path=${body}&type=${data.type}`;
+ let runEndpoint = `${path.join(app.getApiPath(), "workflow/run-job")}${runQuery}`;
+ app.getXHR(runEndpoint, {
+ success: (err, response, body) => {
+ this.updateWorkflow(true);
+ }
+ });
+ }
+ });
+ },
updateWorkflow: function (newJob) {
let self = this;
if(this.model.newFormat) {
diff --git a/client/pages/workflow-selection.js b/client/pages/workflow-selection.js
index c334af64f4..6752d15993 100644
--- a/client/pages/workflow-selection.js
+++ b/client/pages/workflow-selection.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -24,6 +24,7 @@ var app = require('../app');
let Model = require('../models/model');
//views
let PageView = require('./base');
+let SelectView = require('ampersand-select-view');
//templates
let template = require('../templates/pages/workflowSelection.pug');
@@ -32,6 +33,7 @@ import initPage from './page.js';
let workflowSelection = PageView.extend({
template: template,
events: {
+ "change [data-hook=compute-env-select]" : "handleSelectComputeEnv",
"click [data-hook=ensemble-simulation]" : "notebookWorkflow",
"click [data-hook=spatial-simulation]" : "notebookWorkflow",
"click [data-hook=oned-parameter-sweep]" : "notebookWorkflow",
@@ -41,7 +43,6 @@ let workflowSelection = PageView.extend({
},
initialize: function (attrs, options) {
PageView.prototype.initialize.apply(this, arguments);
- let self = this;
let urlParams = new URLSearchParams(window.location.search);
this.modelDir = urlParams.get('path');
if(urlParams.has('parentPath')){
@@ -57,33 +58,37 @@ let workflowSelection = PageView.extend({
this.projectName = this.projectPath.split('/').pop().split('.proj')[0];
this.workflowGroupName = this.parentPath.split('/').pop().split('.wkgp')[0];
}
+ this.computeEnv = "StochSS";
this.model = new Model({
directory: this.modelDir,
isPreview: false,
for: "wkfl",
});
app.getXHR(this.model.url(), {
- success: function (err, response, body) {
- self.model.set(body)
- $(self.queryByHook("wkfl-selection-header")).text("Workflow Selection for " + self.model.name);
- if(self.modelDir.includes(".proj")) {
- self.queryByHook("workflow-selection-breadcrumb-links").style.display = "block";
+ success: (err, response, body) => {
+ this.model.set(body)
+ $(this.queryByHook("wkfl-selection-header")).text(`Workflow Selection for ${this.model.name}`);
+ if(this.modelDir.includes(".proj")) {
+ this.queryByHook("workflow-selection-breadcrumb-links").style.display = "block";
}
- self.validateWorkflows()
+ this.validateWorkflows()
}
});
},
+ handleSelectComputeEnv: function (e) {
+ this.computeEnv = e.target.value;
+ },
notebookWorkflow: function (e) {
let type = e.target.dataset.type;
- let queryString = "?type=" + type + "&path=" + this.modelDir + "&parentPath=" + this.parentPath
- let endpoint = path.join(app.getApiPath(), "workflow/notebook") + queryString
+ let queryString = `?type=${type}&path=${this.modelDir}&parentPath=${this.parentPath}&compute=${this.computeEnv}`;
+ let endpoint = `${path.join(app.getApiPath(), "workflow/notebook")}${queryString}`;
app.getXHR(endpoint, {
- success: function (err, response, body) {
+ success: (err, response, body) => {
let notebookPath = path.join(app.getBasePath(), "notebooks", body.FilePath)
window.open(notebookPath, "_blank")
window.history.back();
}
- })
+ });
},
validateWorkflows: function () {
this.model.updateValid();
@@ -98,7 +103,7 @@ let workflowSelection = PageView.extend({
if(dimensions < 2) {
$(this.queryByHook('psweep-workflow-message')).css('display', 'block');
if(dimensions === 1) {
- $(this.queryByHook('psweep-workflow-message')).text('2D Parameter Sweep workflows require at least two parameters');
+ $(this.queryByHook('psweep-workflow-message')).html('2D Parameter Sweep workflows require at least two parameters');
}
}
$(this.queryByHook('ensemble-simulation')).prop('disabled', invalid || this.model.is_spatial);
@@ -107,6 +112,22 @@ let workflowSelection = PageView.extend({
$(this.queryByHook('oned-parameter-sweep')).prop('disabled', invalid || this.model.is_spatial || dimensions < 1);
$(this.queryByHook('twod-parameter-sweep')).prop('disabled', invalid || this.model.is_spatial || dimensions < 2);
$(this.queryByHook('sciope-model-exploration')).prop('disabled', invalid || this.model.is_spatial);
+ $(this.queryByHook('compute-env-select').firstChild.children[1]).prop('disabled', this.model.is_spatial);
+ },
+ subviews: {
+ computeEnvSelect: {
+ hook: "compute-env-select",
+ prepareView: function (el) {
+ let options = ["StochSS", "AWS Cloud"];
+ return new SelectView({
+ name: 'compute-environment',
+ required: true,
+ idAttributes: 'cid',
+ options: options,
+ value: this.computeEnv
+ });
+ }
+ }
}
});
diff --git a/client/project-config.js b/client/project-config.js
index 3bfc24ab2c..939dfd068b 100644
--- a/client/project-config.js
+++ b/client/project-config.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/reaction-types.js b/client/reaction-types.js
index f8a2e3f8e4..672f53c309 100644
--- a/client/reaction-types.js
+++ b/client/reaction-types.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/settings-view/settings-view.js b/client/settings-view/settings-view.js
index 00b37ea448..0eb4c271f6 100644
--- a/client/settings-view/settings-view.js
+++ b/client/settings-view/settings-view.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/settings-view/templates/timespanSettingsView.pug b/client/settings-view/templates/timespanSettingsView.pug
index 8947e27f52..f45dd2d67d 100644
--- a/client/settings-view/templates/timespanSettingsView.pug
+++ b/client/settings-view/templates/timespanSettingsView.pug
@@ -52,35 +52,38 @@ div#preview-settings.card
div.col-sm-3(data-hook="time-units")
- div.col-sm-6
+ div.col-sm-6.hidden(data-hook="timestep-size-container")
- if this.isSpatial
- div
+ div
+
+ div.row
+
+ div.col-sm-1
+
+ span.inline 1
- div.row
+ div.col-sm-8
- div.col-sm-1
-
- span.inline 1
+ span.mr-2.inline Current Value:
- div.col-sm-8
+ div.inline(data-hook="timestep-size-value")
- span.mr-2.inline Current Value:
+ div.col-sm-3.d-flex.justify-content-end
- div.inline(data-hook="timestep-size-value")
+ span.inline 1e-5
- div.col-sm-3.d-flex.justify-content-end
+ input.custom-range(
+ type="range"
+ min="0"
+ max="5"
+ step="1"
+ value=this.tssValue
+ data-hook="timestep-size-slider"
+ )
- span.inline 1e-5
+ div(data-hook="timestep-size-error")
- input.custom-range(
- type="range"
- min="0"
- max="5"
- step="1"
- value=this.tssValue
- data-hook="timestep-size-slider"
- )
+ p.text-danger 'Timestep Size' can't be greater than 'Sample Frequency'.
div.tab-pane(id=this.model.elementID + "-view-timespan" data-hook=this.model.elementID + "-view-timespan")
diff --git a/client/settings-view/views/parameter-settings-view.js b/client/settings-view/views/parameter-settings-view.js
index eddad1520c..d69b7b92a4 100644
--- a/client/settings-view/views/parameter-settings-view.js
+++ b/client/settings-view/views/parameter-settings-view.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/settings-view/views/spatial-settings-view.js b/client/settings-view/views/spatial-settings-view.js
index 7850abaeff..edd84e50b7 100644
--- a/client/settings-view/views/spatial-settings-view.js
+++ b/client/settings-view/views/spatial-settings-view.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/settings-view/views/sweep-parameter-view.js b/client/settings-view/views/sweep-parameter-view.js
index 8e9df918fd..34e7dc65d1 100644
--- a/client/settings-view/views/sweep-parameter-view.js
+++ b/client/settings-view/views/sweep-parameter-view.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/settings-view/views/timespan-settings-view.js b/client/settings-view/views/timespan-settings-view.js
index f8e0de13f3..8dc37c42d2 100644
--- a/client/settings-view/views/timespan-settings-view.js
+++ b/client/settings-view/views/timespan-settings-view.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -33,7 +33,7 @@ module.exports = View.extend({
'click [data-hook=collapse]' : 'changeCollapseButtonText',
'input [data-hook=timestep-size-slider]' : 'viewTimestepValue',
'change [data-hook=preview-time]' : 'updateViewer',
- 'change [data-hook=time-units]' : 'updateViewer',
+ 'change [data-hook=time-units]' : 'updateTimeStep',
'change [data-hook=timestep-size-slider]' : 'setTimestepSize'
},
initialize: function (attrs, options) {
@@ -55,8 +55,13 @@ module.exports = View.extend({
$(this.queryByHook(this.model.elementID + '-timespan-view-tab')).tab('show');
$(this.queryByHook(this.model.elementID + '-edit-timespan')).removeClass('active');
$(this.queryByHook(this.model.elementID + '-view-timespan')).addClass('active');
+ }else{
+ this.toggleTimestepSizeError();
+ }
+ if(this.isSpatial) {
+ $(this.queryByHook("timestep-size-container")).css("display", "inline-block");
+ $(this.queryByHook("timestep-size-value")).html(this.model.timestepSize);
}
- $(this.queryByHook("timestep-size-value")).html(this.model.timestepSize);
},
changeCollapseButtonText: function (e) {
app.changeCollapseButtonText(this, e);
@@ -64,8 +69,23 @@ module.exports = View.extend({
setTimestepSize: function (e) {
this.model.timestepSize = Number("1e-" + e.target.value);
$(this.queryByHook("view-timestep-size")).html(this.model.timestepSize);
+ this.toggleTimestepSizeError();
+ },
+ toggleTimestepSizeError: function () {
+ let errorMsg = $(this.queryByHook("timestep-size-error"));
+ if(this.isSpatial && this.model.timestepSize > this.model.timeStep) {
+ errorMsg.addClass('component-invalid');
+ errorMsg.removeClass('component-valid');
+ }else{
+ errorMsg.addClass('component-valid');
+ errorMsg.removeClass('component-invalid');
+ }
},
update: function (e) {},
+ updateTimeStep: function () {
+ this.updateViewer();
+ this.toggleTimestepSizeError();
+ },
updateValid: function () {},
updateViewer: function (e) {
$(this.queryByHook("view-end-sim")).html("0 to " + this.model.endSim);
diff --git a/client/settings-view/views/well-mixed-settings-view.js b/client/settings-view/views/well-mixed-settings-view.js
index 0409cd14c5..1f93e4ec87 100644
--- a/client/settings-view/views/well-mixed-settings-view.js
+++ b/client/settings-view/views/well-mixed-settings-view.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/styles/styles.css b/client/styles/styles.css
index 3f3e7906a3..c6eb3bbd9d 100644
--- a/client/styles/styles.css
+++ b/client/styles/styles.css
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -27,6 +27,10 @@ img.quickstart {
margin: 2em auto;
}
+svg {
+ pointer-events: none;
+}
+
.page {
padding-bottom: 150px;
}
@@ -43,7 +47,7 @@ img.quickstart {
.user-logs {
white-space: nowrap;
- max-height: 250px;
+ max-height: 335px;
padding: 5px;
margin-top: 10px;
width: 135%;
@@ -196,10 +200,25 @@ a.anchor {
white-space: normal;
}
+.dropdown-menu li {
+ position: relative;
+}
+
.dropdown-menu li:hover {
cursor: pointer;
}
+.dropdown-menu .dropdown-submenu {
+ display: none;
+ position: absolute;
+ left: 100%;
+ top: -7px;
+}
+
+.dropdown-menu > li:hover > .dropdown-submenu {
+ display: block;
+}
+
.name {
width: 8rem;
}
@@ -385,6 +404,10 @@ input[type="file"]::-ms-browse {
color: #0056b3;
}
+.full-btn {
+ width: 100%;
+}
+
#collapse-description {
padding: 0rem 2rem;
font-size: 12px;
@@ -762,4 +785,67 @@ span.checkbox {
.hidden {
display: none;
-}
\ No newline at end of file
+}
+
+.custom-range {
+ height: auto;
+}
+
+.switch {
+ position: relative;
+ display: inline-block;
+ width: 52px;
+ height: 26px;
+}
+
+.switch input {
+ opacity: 0;
+ width: 0;
+ height: 0;
+}
+
+.slider {
+ position: absolute;
+ cursor: pointer;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: #ccc;
+ -webkit-transition: .4s;
+ transition: .4s;
+}
+
+.slider:before {
+ position: absolute;
+ content: "";
+ height: 20px;
+ width: 20px;
+ left: 4px;
+ bottom: 4px;
+ background-color: white;
+ -webkit-transition: .4s;
+ transition: .4s;
+}
+
+input:checked + .slider {
+ background-color: #2196F3;
+}
+
+input:focus + .slider {
+ box-shadow: 0 0 1px #2196F3;
+}
+
+input:checked + .slider:before {
+ -webkit-transform: translateX(24px);
+ -ms-transform: translateX(24px);
+ transform: translateX(24px);
+}
+
+.slider.round {
+ border-radius: 34px;
+}
+
+.slider.round:before {
+ border-radius: 50%;
+}
diff --git a/client/templates/body.pug b/client/templates/body.pug
index 4d4844508b..1456df92ce 100644
--- a/client/templates/body.pug
+++ b/client/templates/body.pug
@@ -8,27 +8,42 @@ body
div#navbar.collapse.navbar-collapse.justify-content-end(role="navigation")
ul.navbar-nav.float-right
- li.nav-item: a.nav-link(href="http://www.stochss.org", target="_blank")
- | About
+ li.nav-item: a.nav-link(href="http://www.stochss.org" title="About" target="_blank")
+ svg(xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 512 512")
+ path(d="M256 512c141.4 0 256-114.6 256-256S397.4 0 256 0S0 114.6 0 256S114.6 512 256 512zM216 336h24V272H216c-13.3 0-24-10.7-24-24s10.7-24 24-24h48c13.3 0 24 10.7 24 24v88h8c13.3 0 24 10.7 24 24s-10.7 24-24 24H216c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-144c-17.7 0-32-14.3-32-32s14.3-32 32-32s32 14.3 32 32s-14.3 32-32 32z")
- li.nav-item: a.nav-link(href="https://docs.google.com/forms/d/12tAH4f8CJ-3F-lK44Q9uQHFio_mGoK0oY829q5lD7i4/viewform?embedded=true", target="_blank")
- | Register
+ li.nav-item: a.nav-link(href="https://docs.google.com/forms/d/12tAH4f8CJ-3F-lK44Q9uQHFio_mGoK0oY829q5lD7i4/viewform?embedded=true" title="Register" target="_blank")
+ svg(xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 384 512")
+ path(d="M101.5 64C114.6 26.7 150.2 0 192 0s77.4 26.7 90.5 64H320c35.3 0 64 28.7 64 64V448c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V128C0 92.7 28.7 64 64 64h37.5zM224 96c0-17.7-14.3-32-32-32s-32 14.3-32 32s14.3 32 32 32s32-14.3 32-32zM160 368c0 8.8 7.2 16 16 16H304c8.8 0 16-7.2 16-16s-7.2-16-16-16H176c-8.8 0-16 7.2-16 16zM96 392c13.3 0 24-10.7 24-24s-10.7-24-24-24s-24 10.7-24 24s10.7 24 24 24zm64-120c0 8.8 7.2 16 16 16H304c8.8 0 16-7.2 16-16s-7.2-16-16-16H176c-8.8 0-16 7.2-16 16zM96 296c13.3 0 24-10.7 24-24s-10.7-24-24-24s-24 10.7-24 24s10.7 24 24 24z")
- li.nav-item: a.nav-link(href="http://www.stochss.org/documentation", target="_blank")
- | Documentation
+ li.nav-item: a.nav-link(href="http://www.stochss.org/documentation" title="Documentation" target="_blank")
+ svg(xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 448 512")
+ path(d="M96 0C43 0 0 43 0 96V416c0 53 43 96 96 96H384h32c17.7 0 32-14.3 32-32s-14.3-32-32-32V384c17.7 0 32-14.3 32-32V32c0-17.7-14.3-32-32-32H384 96zm0 384H352v64H96c-17.7 0-32-14.3-32-32s14.3-32 32-32zm32-240c0-8.8 7.2-16 16-16H336c8.8 0 16 7.2 16 16s-7.2 16-16 16H144c-8.8 0-16-7.2-16-16zm16 48H336c8.8 0 16 7.2 16 16s-7.2 16-16 16H144c-8.8 0-16-7.2-16-16s7.2-16 16-16z")
- li.nav-item: a.nav-link(href="https://forms.gle/hpGJ1ruxR7wTT43h8" target="_blank")
- | Submit Feedback
+ li.nav-item
+ a.nav-link(href="https://discord.gg/QhMeFCJJ" title="Discord" target="_blank")
+ svg(xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-discord" viewBox="0 0 16 16")
+ path(d="M13.545 2.907a13.227 13.227 0 0 0-3.257-1.011.05.05 0 0 0-.052.025c-.141.25-.297.577-.406.833a12.19 12.19 0 0 0-3.658 0 8.258 8.258 0 0 0-.412-.833.051.051 0 0 0-.052-.025c-1.125.194-2.22.534-3.257 1.011a.041.041 0 0 0-.021.018C.356 6.024-.213 9.047.066 12.032c.001.014.01.028.021.037a13.276 13.276 0 0 0 3.995 2.02.05.05 0 0 0 .056-.019c.308-.42.582-.863.818-1.329a.05.05 0 0 0-.01-.059.051.051 0 0 0-.018-.011 8.875 8.875 0 0 1-1.248-.595.05.05 0 0 1-.02-.066.051.051 0 0 1 .015-.019c.084-.063.168-.129.248-.195a.05.05 0 0 1 .051-.007c2.619 1.196 5.454 1.196 8.041 0a.052.052 0 0 1 .053.007c.08.066.164.132.248.195a.051.051 0 0 1-.004.085 8.254 8.254 0 0 1-1.249.594.05.05 0 0 0-.03.03.052.052 0 0 0 .003.041c.24.465.515.909.817 1.329a.05.05 0 0 0 .056.019 13.235 13.235 0 0 0 4.001-2.02.049.049 0 0 0 .021-.037c.334-3.451-.559-6.449-2.366-9.106a.034.034 0 0 0-.02-.019Zm-8.198 7.307c-.789 0-1.438-.724-1.438-1.612 0-.889.637-1.613 1.438-1.613.807 0 1.45.73 1.438 1.613 0 .888-.637 1.612-1.438 1.612Zm5.316 0c-.788 0-1.438-.724-1.438-1.612 0-.889.637-1.613 1.438-1.613.807 0 1.451.73 1.438 1.613 0 .888-.631 1.612-1.438 1.612Z")
+
+ li.nav-item
+ a.nav-link(href="https://forms.gle/hpGJ1ruxR7wTT43h8" title="Submit Feedback" target="_blank")
+ svg(xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 512 512")
+ path(d="M256 448c141.4 0 256-93.1 256-208S397.4 32 256 32S0 125.1 0 240c0 45.1 17.7 86.8 47.7 120.9c-1.9 24.5-11.4 46.3-21.4 62.9c-5.5 9.2-11.1 16.6-15.2 21.6c-2.1 2.5-3.7 4.4-4.9 5.7c-.6 .6-1 1.1-1.3 1.4l-.3 .3 0 0 0 0 0 0 0 0c-4.6 4.6-5.9 11.4-3.4 17.4c2.5 6 8.3 9.9 14.8 9.9c28.7 0 57.6-8.9 81.6-19.3c22.9-10 42.4-21.9 54.3-30.6c31.8 11.5 67 17.9 104.1 17.9zM128 272c-17.7 0-32-14.3-32-32s14.3-32 32-32s32 14.3 32 32s-14.3 32-32 32zm128 0c-17.7 0-32-14.3-32-32s14.3-32 32-32s32 14.3 32 32s-14.3 32-32 32zm160-32c0 17.7-14.3 32-32 32s-32-14.3-32-32s14.3-32 32-32s32 14.3 32 32z")
+
+ li.nav-item
+ a.nav-link(href="https://github.com/StochSS/stochss/issues/new" title="Report an Issue" target="_blank")
+ svg(xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 496 512")
+ path(d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z")
+
+ li.nav-item
+ a.nav-link(href="/stochss/settings" title="Settings")
+ svg(xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 512 512")
+ path(d="M495.9 166.6c3.2 8.7 .5 18.4-6.4 24.6l-43.3 39.4c1.1 8.3 1.7 16.8 1.7 25.4s-.6 17.1-1.7 25.4l43.3 39.4c6.9 6.2 9.6 15.9 6.4 24.6c-4.4 11.9-9.7 23.3-15.8 34.3l-4.7 8.1c-6.6 11-14 21.4-22.1 31.2c-5.9 7.2-15.7 9.6-24.5 6.8l-55.7-17.7c-13.4 10.3-28.2 18.9-44 25.4l-12.5 57.1c-2 9.1-9 16.3-18.2 17.8c-13.8 2.3-28 3.5-42.5 3.5s-28.7-1.2-42.5-3.5c-9.2-1.5-16.2-8.7-18.2-17.8l-12.5-57.1c-15.8-6.5-30.6-15.1-44-25.4L83.1 425.9c-8.8 2.8-18.6 .3-24.5-6.8c-8.1-9.8-15.5-20.2-22.1-31.2l-4.7-8.1c-6.1-11-11.4-22.4-15.8-34.3c-3.2-8.7-.5-18.4 6.4-24.6l43.3-39.4C64.6 273.1 64 264.6 64 256s.6-17.1 1.7-25.4L22.4 191.2c-6.9-6.2-9.6-15.9-6.4-24.6c4.4-11.9 9.7-23.3 15.8-34.3l4.7-8.1c6.6-11 14-21.4 22.1-31.2c5.9-7.2 15.7-9.6 24.5-6.8l55.7 17.7c13.4-10.3 28.2-18.9 44-25.4l12.5-57.1c2-9.1 9-16.3 18.2-17.8C227.3 1.2 241.5 0 256 0s28.7 1.2 42.5 3.5c9.2 1.5 16.2 8.7 18.2 17.8l12.5 57.1c15.8 6.5 30.6 15.1 44 25.4l55.7-17.7c8.8-2.8 18.6-.3 24.5 6.8c8.1 9.8 15.5 20.2 22.1 31.2l4.7 8.1c6.1 11 11.4 22.4 15.8 34.3zM256 336c44.2 0 80-35.8 80-80s-35.8-80-80-80s-80 35.8-80 80s35.8 80 80 80z")
li.nav-item
- div.dropdown
- button.dropdown-toggle.nav-item.nav-link.dropbtn Contact
- div.dropdown-content
- a.nav-link(href="https://gitter.im/StochSS/community#", target="_blank") Community Chat
- a.nav-link(href="https://github.com/StochSS/stochss/issues/new", target="_blank") Report Issue
-
- li.nav-item: a.nav-link(href="/logout")
- | Sign Out
+ a.nav-link(href="/logout" title="Sign Out")
+ svg(xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 576 512")
+ path(d="M534.6 278.6c12.5-12.5 12.5-32.8 0-45.3l-128-128c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L434.7 224 224 224c-17.7 0-32 14.3-32 32s14.3 32 32 32l210.7 0-73.4 73.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0l128-128zM192 96c17.7 0 32-14.3 32-32s-14.3-32-32-32l-64 0c-53 0-96 43-96 96l0 256c0 53 43 96 96 96l64 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l-64 0c-17.7 0-32-14.3-32-32l0-256c0-17.7 14.3-32 32-32l64 0z")
div.container-fluid
div.row
diff --git a/client/templates/includes/editDomainType.pug b/client/templates/includes/editDomainType.pug
deleted file mode 100644
index 887eab716a..0000000000
--- a/client/templates/includes/editDomainType.pug
+++ /dev/null
@@ -1,28 +0,0 @@
-tr
-
- td: div(data-target='type-name' data-hook='type-' + this.model.typeID)
-
- td=this.model.numParticles
-
- td
- div="mass: " + this.model.mass
- div="vol: " + this.model.volume
- div="viscosity: " + this.model.nu
- div="fixed: " + this.model.fixed
-
- td: button.btn.btn-outline-secondary.box-shadow(data-type=this.model.typeID data-hook='edit-defaults-btn') Edit Defaults
-
- td: button.btn.btn-outline-secondary.box-shadow(data-type=this.model.typeID data-hook='unassign-all') Un-Assign Particles
-
- td
- button.btn.btn-outline-secondary.box-shadow.dropdown-toggle(
- id="delete-domain-type",
- data-toggle="dropdown",
- aria-haspopup="true",
- aria-expanded="false",
- type="button"
- ) Delete
-
- ul.dropdown-menu(aria-labelledby="delete-domain-type")
- li.dropdown-item(data-hook="delete-type" data-type=this.model.typeID) Type
- li.dropdown-item(data-hook="delete-all" data-type=this.model.typeID) Type and Particles
diff --git a/client/templates/pages/domainEditor.pug b/client/templates/pages/domainEditor.pug
index d97d85b6c2..ae0b5bc2e5 100644
--- a/client/templates/pages/domainEditor.pug
+++ b/client/templates/pages/domainEditor.pug
@@ -2,6 +2,13 @@ section.page
div: h2.inline Domain Editor
+ ul.mb-0
+ li Use the domain editor to create complex 3D 'Shapes'.
+ li Use 'Shapes' to define regions in space.
+ li Use 'Transformations' to move (rotate, etc...) 'Shapes' in space.
+ li Use 'Types' to classify particles (e.g. Cell_Wall, Cytoplasm, Nucleus).
+ li Use 'Actions' to fill (set properties, remove, import) 'Shapes' with particles.
+
div(data-hook="two-parent-breadcrumb-links" style="display: none")
h5
nav(aria-label="breadcrumb")
diff --git a/client/templates/pages/errorTemplate.pug b/client/templates/pages/errorTemplate.pug
index 2a66c42c7b..e27e4202d5 100644
--- a/client/templates/pages/errorTemplate.pug
+++ b/client/templates/pages/errorTemplate.pug
@@ -3,12 +3,12 @@ section.page
div#page-error.container.centered
div.logo(id="stochss-logo" data-hook="stochss-logo")
- img(src="/hub/static/stochss-logo.png")
+ img(src=this.logoPath)
div.presentation-header
- h1 404 PAGE NOT FOUND
+ h1=this.title
div
- h5="This " + this.fileType + " presentation was removed by the author and is no longer available."
\ No newline at end of file
+ h5=errMsg
\ No newline at end of file
diff --git a/client/templates/pages/exampleLibrary.pug b/client/templates/pages/exampleLibrary.pug
index f86ccf2180..97ed66634a 100644
--- a/client/templates/pages/exampleLibrary.pug
+++ b/client/templates/pages/exampleLibrary.pug
@@ -12,7 +12,19 @@ section.page
div.collapse.show(id="collapse-well-mixed")
- div.card-body(data-hook="well-mixed-examples")
+ div.card-body
+
+ div.mx-1.row.head.align-items-baseline
+
+ div.col-sm-3
+
+ h6 Name
+
+ div.col-sm-9
+
+ h6 Description
+
+ div.my-3(data-hook="well-mixed-examples")
div.card.mt-4#spatial-examples
@@ -24,4 +36,16 @@ section.page
div.collapse.show(id="collapse-spatial")
- div.card-body(data-hook="spatial-examples")
+ div.card-body
+
+ div.mx-1.row.head.align-items-baseline
+
+ div.col-sm-3
+
+ h6 Name
+
+ div.col-sm-9
+
+ h6 Description
+
+ div.my-3(data-hook="spatial-examples")
diff --git a/client/templates/pages/userSettings.pug b/client/templates/pages/userSettings.pug
new file mode 100644
index 0000000000..c5abde0fec
--- /dev/null
+++ b/client/templates/pages/userSettings.pug
@@ -0,0 +1,142 @@
+section.page
+
+ div.no-gutters
+
+ div
+
+ div.mx-1.head.align-items-baseline
+
+ h2 General
+
+ div.mt-3.row
+
+ div.col-sm-1
+
+ label.switch
+
+ input(data-hook="user-logs" type="checkbox" checked)
+
+ span.slider.round
+
+ div.col-sm-11
+
+ h5(style="verticle-align: middle;") User Logs
+
+ div
+
+ div.mt-5.mx-1.head.align-items-baseline
+
+ h2 AWS
+
+ div.mt-3.pl-3.py-3.hidden(data-hook="aws-config-msg" style="background-color: rgba(220, 53, 69, 0.5) !important;")
+
+ div AWS has not been setup to run jobs, check the following:
+ ul.mb-0
+ li Check that your credentials have been entered correctly.
+ li Ensure you have a compute node type and size are selected.
+ li Launch the compute node and ensure the status is running.
+
+ div.mt-3.mx-1.head.align-items-baseline
+
+ h3 Credentials
+
+ p See AWS Configuration Documentation for details on where your credetials can be found.
+
+ div.my-3.mx-1.row.head.align-items-baseline
+
+ div.col-sm-3
+
+ h6 Region
+
+ div.col-sm-4
+
+ h6 Access Key ID
+
+ div.col-sm-4
+
+ h6 Secret Access Key
+
+ div.row.my-2
+
+ div.col-sm-3
+
+ div.pl-2(data-hook="aws-region-container" data-target="aws-credentials")
+
+ div.col-sm-4
+
+ div(data-hook="aws-accesskeyid-container" data-target="aws-credentials")
+
+ div.col-sm-4
+
+ div(data-hook="aws-secretaccesskey-container")
+
+ div.hidden(data-hook="aws-compute-node-section")
+
+ hr
+
+ div.mx-1.row.head.align-items-baseline
+
+ h3 Compute Node
+
+ div.my-3.mx-1.row.head.align-items-baseline
+
+ div.col-sm-2
+
+ h6 Type
+
+ div.col-sm-2
+
+ h6 Size
+
+ div.col-sm-4
+
+ h6 Status
+
+ div.row.my-2
+
+ div.col-sm-2
+
+ div(data-hook="aws-instancetype-container")
+
+ div.col-sm-2
+
+ div(data-hook="aws-instancesize-container")
+
+ div.col-sm-4
+
+ button.btn.btn-outline-collapse.mr-2.inline(data-hook="refresh-aws-status")
+
+ svg(xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 512 512")
+
+ path(d="M105.1 202.6c7.7-21.8 20.2-42.3 37.8-59.8c62.5-62.5 163.8-62.5 226.3 0L386.3 160H336c-17.7 0-32 14.3-32 32s14.3 32 32 32H463.5c0 0 0 0 0 0h.4c17.7 0 32-14.3 32-32V64c0-17.7-14.3-32-32-32s-32 14.3-32 32v51.2L414.4 97.6c-87.5-87.5-229.3-87.5-316.8 0C73.2 122 55.6 150.7 44.8 181.4c-5.9 16.7 2.9 34.9 19.5 40.8s34.9-2.9 40.8-19.5zM39 289.3c-5 1.5-9.8 4.2-13.7 8.2c-4 4-6.7 8.8-8.1 14c-.3 1.2-.6 2.5-.8 3.8c-.3 1.7-.4 3.4-.4 5.1V448c0 17.7 14.3 32 32 32s32-14.3 32-32V396.9l17.6 17.5 0 0c87.5 87.4 229.3 87.4 316.7 0c24.4-24.4 42.1-53.1 52.9-83.7c5.9-16.7-2.9-34.9-19.5-40.8s-34.9 2.9-40.8 19.5c-7.7 21.8-20.2 42.3-37.8 59.8c-62.5 62.5-163.8 62.5-226.3 0l-.1-.1L125.6 352H176c17.7 0 32-14.3 32-32s-14.3-32-32-32H48.4c-1.6 0-3.2 .1-4.8 .3s-3.1 .5-4.6 1z")
+
+ div.inline(data-hook="aws-headnode-status")
+
+ div.col-sm-2
+
+ button.btn.btn-outline-secondary.box-shadow(data-hook="launch-aws-cluster" disabled) Launch
+
+ div.col-sm-2
+
+ button.btn.btn-outline-secondary.box-shadow(data-hook="terminate-aws-cluster" disabled) Terminate
+
+ div.mt-3(style="float: right;")
+
+ if this.path === null
+ button.btn.btn-primary.box-shadow(data-hook="apply-user-settings") Apply
+ else
+ button.btn.btn-primary.box-shadow(data-hook="apply-user-settings") Apply & Continue
+
+ div.mdl-edit-btn.saving-status.inline(data-hook="usa-in-progress")
+
+ div.spinner-grow.mr-2
+
+ span Applying settings ...
+
+ div.mdl-edit-btn.saved-status.inline(data-hook="usa-complete")
+
+ span Settings successfully applied
+
+ div.mdl-edit-btn.save-error-status(data-hook="usa-error")
+
+ span(data-hook="usa-action-error")
diff --git a/client/templates/pages/workflowManager.pug b/client/templates/pages/workflowManager.pug
index 2285ddb5b8..05582cab73 100644
--- a/client/templates/pages/workflowManager.pug
+++ b/client/templates/pages/workflowManager.pug
@@ -48,21 +48,36 @@ section.page
div.mdl-edit-btn
- button.btn.btn-primary.box-shadow(data-hook="save") Save
+ button.btn.btn-primary.box-shadow.inline(data-hook="save") Save
- div.mdl-edit-btn.saving-status(data-hook="saving-workflow")
+ div.mdl-edit-btn.saving-status.inline(data-hook="saving-workflow")
div.spinner-grow
span Saving...
- div.mdl-edit-btn.saved-status(data-hook="saved-workflow")
+ div.mdl-edit-btn.saved-status.inline(data-hook="saved-workflow")
span Saved
- button.btn.btn-primary.box-shadow(data-hook="start-job") Start Workflow
+ div.inline.hidden(data-hook="of-start-job")
+
+ button.btn.btn-primary.box-shadow(data-target="start-job" data-computeenv="local") Start Workflow
+
+ div.inline(data-hook="nf-start-job")
+
+ button.btn.btn-primary.dropdown-toggle.box-shadow#startNewJobBtn(
+ data-toggle="dropdown",
+ aria-haspopup="true",
+ aria-expanded="false",
+ type="button"
+ ) Start New Job using
+
+ ul.dropdown-menu(aria-labelledby="startNewJobBtn")
+ li.dropdown-item(data-target="start-job" data-computeenv="local") StochSS
+ li.dropdown-item(data-hook="aws-start-job" data-target="start-job" data-computeenv="aws") AWS Cloud
- button.btn.btn-primary.box-shadow(data-hook="edit-model") Edit Model
+ button.btn.btn-primary.box-shadow.inline(data-hook="edit-model") Edit Model
div(data-hook="status-container")
diff --git a/client/templates/pages/workflowSelection.pug b/client/templates/pages/workflowSelection.pug
index 5316f86ec1..ab308488dc 100644
--- a/client/templates/pages/workflowSelection.pug
+++ b/client/templates/pages/workflowSelection.pug
@@ -9,11 +9,21 @@ section.page
li.breadcrumb-item
a(href="stochss/project/manager?path="+this.projectPath data-hook="project-breadcrumb")=this.projectName
- div.alert-danger(data-hook="invalid-model-message" style="display: none;")
- | You cannot create a workflow if your model does not have at least one Variable and one Reaction, Event, or Rule
+ div.alert-danger.hidden(data-hook="invalid-model-message")
+ | You cannot create a workflow if your model does not have at least one Variable and one Reaction, Event, or Rule
- div.alert-info(data-hook="psweep-workflow-message" style="display: none;")
- | Parameter Sweep workflows require at least one parameter
+ div.alert-info.hidden(data-hook="psweep-workflow-message")
+ | Parameter Sweep workflows require at least one parameter
+
+ div
+
+ div.alert-info AWS Cloud is only supported in the Ensemble Simulation workflow
+
+ div.my-2
+
+ span.mr-2.inline(for="#compute-env-select") Compute Environment:
+
+ div.inline(id="compute-env-select" data-hook="compute-env-select")
table.table
thead
diff --git a/client/tooltips.js b/client/tooltips.js
index 09895c828a..8165377942 100644
--- a/client/tooltips.js
+++ b/client/tooltips.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -179,14 +179,24 @@ module.exports = {
parameterSweep: "Produced and compare results from model simulations by varying parameters over a range."
},
domainEditor: {
+ staticDomain: "If true, particles are fixed system wide. If false, Fixed property of particles is used.",
+
pressure: "Atmospheric or background pressure.",
- speed: "Approximate or artificial speed of sound"
+ speed: "Approximate or artificial speed of sound."
+ },
+ domainGeometry: {
+ name: "Unique identifier for Geometry. Cannot share a name with other model components.",
+
+ geometry: "The geometry formula can be any mathematical expression which evaluates to a boolean value in a python environment (i.e. x==5). This "+
+ "expression is evaluable within the a limited namespace, and only lower case variables (x, y, z), particles location in standard geometries, "+
+ "or other geometry names, combinatory geometries, can be referenced in the expression."
+ },
+ domainLattice: {
+ name: "Unique identifier for Lattice. Cannot share a name with other model components."
},
- domainType: {
- geometry: "The geometry expression can be any mathematical expression which evaluates to a boolean value in a python environment (i.e. x==5). This "+
- "expression is evaluable within the a limited namespace, and only lower case variables (x, y, z), particles location, and (cx, cy, cz), "+
- "center of the geometry, can be referenced in the expression."
+ domainTransformation: {
+ name: "Unique identifier for Transformation. Cannot share a name with other model components."
},
boundaryConditionsEditor: {
annotation: "An optional note about a boundary condition."
diff --git a/client/views/archive-listing.js b/client/views/archive-listing.js
index 8b89566052..54c28cebcc 100644
--- a/client/views/archive-listing.js
+++ b/client/views/archive-listing.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/views/creator-listing.js b/client/views/creator-listing.js
index 19fa76b160..3debb9c27c 100644
--- a/client/views/creator-listing.js
+++ b/client/views/creator-listing.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/views/edit-domain-type.js b/client/views/edit-domain-type.js
deleted file mode 100644
index bfd9bd4be6..0000000000
--- a/client/views/edit-domain-type.js
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
-StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 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 .
-*/
-
-//support files
-let app = require('../app');
-//views
-var View = require('ampersand-view');
-var InputView = require('./input');
-//templates
-let editTemplate = require('../templates/includes/editDomainType.pug');
-let viewTemplate = require('../templates/includes/viewDomainType.pug');
-
-module.exports = View.extend({
- events: {
- 'click [data-hook=unassign-all]' : 'handleUnassignParticles',
- 'click [data-hook=delete-type]' : 'handleDeleteType',
- 'click [data-hook=delete-all]' : 'handleDeleteTypeAndParticle',
- 'click [data-hook=edit-defaults-btn]' : 'handleEditDefaults',
- 'change [data-target=type-name]' : 'handleRenameType'
- },
- initialize: function (attrs, options) {
- View.prototype.initialize.apply(this, arguments);
- this.viewMode = attrs.viewMode ? attrs.viewMode : false;
- },
- render: function (attrs, options) {
- this.template = this.viewMode ? viewTemplate : editTemplate;
- View.prototype.render.apply(this, arguments);
- this.renderNameView()
- },
- handleDeleteType: function (e) {
- let type = Number(e.target.dataset.type);
- this.parent.deleteType(type);
- this.parent.renderDomainTypes();
- },
- handleDeleteTypeAndParticle: function (e) {
- let type = Number(e.target.dataset.type);
- this.parent.deleteTypeAndParticles(type);
- this.parent.renderDomainTypes();
- },
- handleEditDefaults: function (e) {
- this.parent.selectedType = this.model.typeID;
- this.parent.renderEditTypeDefaults();
- },
- handleRenameType: function (e) {
- let dataset = e.target.parentElement.parentElement.dataset;
- let type = Number(dataset.hook.split('-').pop());
- let name = e.target.value;
- this.parent.renameType(type, name);
- },
- handleUnassignParticles: function (e) {
- let type = Number(e.target.dataset.type);
- this.parent.unassignAllParticles(type);
- this.parent.renderDomainTypes();
- },
- renderNameView: function () {
- if(!this.viewMode) {
- let nameInput = new InputView({
- parent: this,
- required: true,
- name: 'name',
- valueType: 'string',
- value: this.model.name
- });
- app.registerRenderSubview(this, nameInput, "type-" + this.model.typeID)
- }
- },
- update: function () {},
- updateValid: function () {},
-});
\ No newline at end of file
diff --git a/client/views/edit-project.js b/client/views/edit-project.js
index 045fe6bea3..70fa82c789 100644
--- a/client/views/edit-project.js
+++ b/client/views/edit-project.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/views/input.js b/client/views/input.js
index 3e2ba786cc..e510b2f0a4 100644
--- a/client/views/input.js
+++ b/client/views/input.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/views/job-listing.js b/client/views/job-listing.js
index d5c263f4a4..c561d46dd4 100644
--- a/client/views/job-listing.js
+++ b/client/views/job-listing.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/views/jstree-view.js b/client/views/jstree-view.js
index ad1fab90e4..a110201253 100644
--- a/client/views/jstree-view.js
+++ b/client/views/jstree-view.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/views/main.js b/client/views/main.js
index bd8ae35a9e..f81ef9fa64 100644
--- a/client/views/main.js
+++ b/client/views/main.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -16,21 +16,22 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
-var _ = require('underscore');
-var $ = require('jquery');
-//var setFavicon = require('favicon-setter');
-var App = require('ampersand-app');
-var localLinks = require('local-links');
-var domify = require('domify');
-var path = require('path');
+let _ = require('underscore');
+let $ = require('jquery');
+let App = require('ampersand-app');
+let localLinks = require('local-links');
+let domify = require('domify');
+let path = require('path');
// support files
let app = require("../app");
+// models
+let Settings = require('../models/user-settings');
//views
-var View = require('ampersand-view');
-var ViewSwitcher = require('ampersand-view-switcher');
+let View = require('ampersand-view');
+let ViewSwitcher = require('ampersand-view-switcher');
//templates
-var headTemplate = require('!pug-loader!../templates/head.pug');
-var bodyTemplate = require('!pug-loader!../templates/body.pug');
+let headTemplate = require('!pug-loader!../templates/head.pug');
+let bodyTemplate = require('!pug-loader!../templates/body.pug');
String.prototype.toHtmlEntities = function() {
return this.replace(/./gm, function(s) {
@@ -41,31 +42,26 @@ String.prototype.toHtmlEntities = function() {
module.exports = View.extend({
template: bodyTemplate,
autoRender: true,
- initialize: function () {
- this.listenTo(App, 'page', this.handleNewPage);
- this.homePath = window.location.pathname.startsWith("/user") ? "/hub/spawn" : "stochss/home"
- this.ulClosed = false;
- },
events: {
'click [data-hook=registration-link-button]' : 'handleRegistrationLinkClick',
'click [data-hook=user-logs-collapse]' : 'collapseExpandLogs',
'click [data-hook=clear-user-logs]' : 'clearUserLogs',
'click [data-hook=close-user-logs]' : 'closeUserLogs',
},
+ initialize: function () {
+ this.listenTo(App, 'page', this.handleNewPage);
+ this.homePath = window.location.pathname.startsWith("/user") ? "/hub/spawn" : "stochss/home";
+ this.ulClosed = false;
+ },
render: function () {
-
document.head.appendChild(domify(headTemplate()));
-
this.renderWithTemplate(this);
-
this.pageContainer = this.queryByHook('page-container');
-
this.pageSwitcher = new ViewSwitcher({
el: this.pageContainer,
- show: function (newView, oldView) {
+ show: (newView, oldView) => {
document.title = _.result(newView, 'pageTitle') || 'StochSS';
document.scrollTop = 0;
-
App.currentPage = newView;
}
});
@@ -73,32 +69,30 @@ module.exports = View.extend({
$("#presentation-nav-link").css("display", "none");
}
this.setupUserLogs();
- this.getExampleLibrary();
return this;
},
addNewLogBlock: function () {
if(this.logBlock.length > 0) {
let logBlock = this.logBlock.join("
");
this.logBlock = [];
- $("#user-logs").append("" + logBlock + "
")
- return ""
+ $("#user-logs").append("" + logBlock + "
");
+ return "";
}
- return "
"
+ return "
";
},
addNewLogs: function (newLogs) {
- let self = this;
- let logList = newLogs.map(function (log) {
+ let logList = newLogs.map((log) => {
if(log.includes("$ ")){
- let head = self.addNewLogBlock();
- var newLog = self.formatLog(log);
+ let head = this.addNewLogBlock();
+ var newLog = this.formatLog(log);
$("#user-logs").append(head + newLog.toHtmlEntities());
}else{
var newLog = log;
if(newLog.trim()) {
- self.logBlock.push(newLog.toHtmlEntities());
+ this.logBlock.push(newLog.toHtmlEntities());
}
}
- self.logs.push(newLog);
+ this.logs.push(newLog);
});
this.addNewLogBlock();
},
@@ -156,25 +150,24 @@ module.exports = View.extend({
},
getUserLogs: function () {
if(this.ulClosed) { return; }
- let self = this;
- let queryStr = "?logNum=" + this.logs.length;
+ let queryStr = `?logNum=${this.logs.length}`;
let endpoint = path.join(app.getApiPath(), "user-logs") + queryStr;
app.getXHR(endpoint, {
- success: function (err, response, body) {
+ success: (err, response, body) => {
if(body) {
- let scrolled = self.scrolled;
- self.addNewLogs(body.logs);
- if(!self.scrolled){
+ let scrolled = this.scrolled;
+ this.addNewLogs(body.logs);
+ if(!this.scrolled){
let element = document.querySelector("#user-logs");
element.scrollTop = element.scrollHeight;
- }else if(self.scrollCount < 60) {
- self.scrollCount += 1;
+ }else if(this.scrollCount < 60) {
+ this.scrollCount += 1;
}else{
- self.scrolled = false;
- self.scrollCount = 0;
+ this.scrolled = false;
+ this.scrollCount = 0;
}
}
- self.updateUserLogs();
+ this.updateUserLogs();
}
});
},
@@ -183,47 +176,33 @@ module.exports = View.extend({
let date = new Date(time);
let months = ['Jan.', 'Feb.', 'Mar.', 'Apr.', 'May', 'Jun.', 'Jul.', 'Aug.', 'Sept.', 'Oct.', 'Nov.', 'Dec.'];
var stamp = months[date.getMonth()] + " ";
- stamp += date.getDate() + ", ";
- stamp += date.getFullYear() + " ";
+ stamp += `${date.getDate()}, ${date.getFullYear()} `;
let hours = date.getHours();
- stamp += (hours < 10 ? "0" + hours : hours) + ":";
+ stamp += `${(hours < 10 ? `0${hours}` : hours)}:`;
let minutes = date.getMinutes();
- stamp += (minutes < 10 ? '0' + minutes : minutes) + ":";
+ stamp += (minutes < 10 ? `0${minutes}` : minutes) + ":";
return log.replace(time, stamp);
},
- getExampleLibrary: function () {
- let endpoint = path.join(app.getApiPath(), "example-library");
- app.getXHR(endpoint, {
- success: (err, response, body) => {
- $("#exampleLibraryDropdown").html(body);
- }
- });
- },
handleNewPage: function (view) {
this.pageSwitcher.set(view);
- //this.updateActiveNav();
},
-
handleLinkClick: function (e) {
- var localPath = localLinks.pathname(e);
-
+ let localPath = localLinks.pathname(e);
if (localPath) {
e.preventDefault();
this.navigate(localPath);
}
},
-
handleRegistrationLinkClick: function () {
$(this.queryByHook("registration-form")).collapse('show');
$(this.queryByHook("registration-link")).collapse();
},
-
navigate: function (page) {
window.location = url;
},
setupUserLogs: function ({getLogs = true}={}) {
let message = app.getBasePath() === "/" ? "Welcome to StochSS!" : "Welcome to StochSS Live!";
- $("#user-logs").html(message)
+ $("#user-logs").html(message);
this.logBlock = [];
this.logs = [];
this.scrolled = false;
diff --git a/client/views/meta-data.js b/client/views/meta-data.js
index f2c26ec83d..b683d404fe 100644
--- a/client/views/meta-data.js
+++ b/client/views/meta-data.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/views/model-listing.js b/client/views/model-listing.js
index 100749d3c3..c4b9756b59 100644
--- a/client/views/model-listing.js
+++ b/client/views/model-listing.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/views/presentation-view.js b/client/views/presentation-view.js
index 19032585f4..ef81f7affa 100644
--- a/client/views/presentation-view.js
+++ b/client/views/presentation-view.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/views/tests.js b/client/views/tests.js
index 7773d5c350..539c997826 100644
--- a/client/views/tests.js
+++ b/client/views/tests.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -16,14 +16,38 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
-let startChar = (text) => {
- if (/^[0-9]+/.test(text)) {
- return "Field cannot start with a number. Start with letter or underscore."
+let optionalStartChar = (text) => {
+ return startChar(text, {optional: true});
+}
+
+let startChar = (text, {optional=false}={}) => {
+ var failed = false;
+ if(optional) {
+ if(text !== "" && /^[0-9]+/.test(text)) {
+ failed = true;
+ }
+ }else if(/^[0-9]+/.test(text)) {
+ failed = true;
+ }
+ if(failed) {
+ return "Field cannot start with a number. Start with letter or underscore."
}
}
-let invalidChar = (text) => {
- if (!/^[a-zA-Z0-9_]+$/.test(text)) {
+let optionalInvalidChar = (text) => {
+ return invalidChar(text, {optional: true});
+}
+
+let invalidChar = (text, {optional=false}={}) => {
+ var failed = false;
+ if(optional) {
+ if(text !== "" && !/^[a-zA-Z0-9_]+$/.test(text)) {
+ failed = true;
+ }
+ }else if(!/^[a-zA-Z0-9_]+$/.test(text)) {
+ failed = true;
+ }
+ if(failed) {
return "Invalid characters. Please only use letters, numbers, or underscores."
}
}
@@ -52,9 +76,13 @@ let negValue = (value) => {
module.exports = {
invalidChar: invalidChar,
+ optionalInvalidChar: optionalInvalidChar,
+ optionalNameTests: [optionalStartChar, optionalInvalidChar],
+ optionalStartChar: optionalStartChar,
nameTests: [startChar, invalidChar],
nanValue: nanValue,
negValue: negValue,
startChar: startChar,
+ intTest: nanValue,
valueTests: [nanValue, negValue]
}
\ No newline at end of file
diff --git a/client/views/view-sweep-parameter.js b/client/views/view-sweep-parameter.js
index 46df3985da..dee9357a40 100644
--- a/client/views/view-sweep-parameter.js
+++ b/client/views/view-sweep-parameter.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/views/workflow-group-listing.js b/client/views/workflow-group-listing.js
index 4dcb3d6c26..820bf4ba01 100644
--- a/client/views/workflow-group-listing.js
+++ b/client/views/workflow-group-listing.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/views/workflow-listing.js b/client/views/workflow-listing.js
index 0ec73261c0..a7aa775da6 100644
--- a/client/views/workflow-listing.js
+++ b/client/views/workflow-listing.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/client/views/workflow-status.js b/client/views/workflow-status.js
index 49043b9f51..1eeedd9f92 100644
--- a/client/views/workflow-status.js
+++ b/client/views/workflow-status.js
@@ -1,6 +1,6 @@
/*
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/jupyterhub/Dockerfile.jupyterhub b/jupyterhub/Dockerfile.jupyterhub
index e281edb163..be8fd417b6 100644
--- a/jupyterhub/Dockerfile.jupyterhub
+++ b/jupyterhub/Dockerfile.jupyterhub
@@ -46,8 +46,8 @@ RUN python3 -m pip install --no-cache-dir \
psycopg2-binary==2.9.* \
nbviewer==1.0.1 \
notebook \
- gillespy2==1.7.0 \
- spatialpy==1.1.0 \
+ gillespy2==1.7.2 \
+ spatialpy==1.1.2 \
plotly
# build the PUG pages
diff --git a/jupyterhub/handlers.py b/jupyterhub/handlers.py
index ba2a765c3a..547457d44e 100644
--- a/jupyterhub/handlers.py
+++ b/jupyterhub/handlers.py
@@ -1,6 +1,6 @@
'''
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/jupyterhub/job_presentation.py b/jupyterhub/job_presentation.py
index b67ec88f9d..09e4c3c14b 100644
--- a/jupyterhub/job_presentation.py
+++ b/jupyterhub/job_presentation.py
@@ -1,6 +1,6 @@
'''
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/jupyterhub/model_presentation.py b/jupyterhub/model_presentation.py
index 34dd9a0fd2..825be8b89e 100644
--- a/jupyterhub/model_presentation.py
+++ b/jupyterhub/model_presentation.py
@@ -1,6 +1,6 @@
'''
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -15,15 +15,20 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
'''
-
+import os
import ast
import json
import logging
+import traceback
+import numpy
import plotly
+import spatialpy
+
from presentation_base import StochSSBase, get_presentation_from_user
-from presentation_error import StochSSAPIError, report_error
+from presentation_error import StochSSAPIError, DomainUpdateError, StochSSModelFormatError, \
+ report_error
from jupyterhub.handlers.base import BaseHandler
@@ -50,8 +55,13 @@ async def get(self):
log.debug(f"Name to the file: {file}")
self.set_header('Content-Type', 'application/json')
try:
- model = get_presentation_from_user(owner=owner, file=file, kwargs={"file": file},
- process_func=process_model_presentation)
+ process_funcs = {
+ "mdl": process_wmmodel_presentation, "smdl": process_smodel_presentation
+ }
+ ext = file.split(".").pop()
+ model = get_presentation_from_user(
+ owner=owner, file=file, kwargs={"file": file}, process_func=process_funcs[ext]
+ )
log.debug(f"Contents of the json file: {model['model']}")
self.write(model)
except StochSSAPIError as load_err:
@@ -76,19 +86,27 @@ async def get(self, owner, file):
log.debug(f"Name to the file: {file}")
self.set_header('Content-Type', 'application/json')
try:
- model = get_presentation_from_user(owner=owner, file=file,
- kwargs={"for_download": True},
- process_func=process_model_presentation)
+ process_funcs = {
+ "mdl": process_wmmodel_presentation, "smdl": process_smodel_presentation
+ }
ext = file.split(".").pop()
- self.set_header('Content-Disposition', f'attachment; filename="{model["name"]}.{ext}"')
- log.debug(f"Contents of the json file: {model}")
- self.write(model)
+ data = get_presentation_from_user(
+ owner=owner, file=file, kwargs={"for_download": True},
+ process_func=process_funcs[ext]
+ )
+ if "name" in data:
+ filename = f"{data['name']}.{ext}"
+ else:
+ filename = f"{data['model']['name']}.{ext}"
+ self.set_header('Content-Disposition', f'attachment; filename="{filename}"')
+ log.debug(f"Contents of the json file: {data}")
+ self.write(data)
except StochSSAPIError as load_err:
report_error(self, log, load_err)
self.finish()
-def process_model_presentation(path, file=None, for_download=False):
+def process_wmmodel_presentation(path, file=None, for_download=False):
'''
Get the model presentation data from the file.
@@ -105,13 +123,39 @@ def process_model_presentation(path, file=None, for_download=False):
model = json.load(mdl_file)
if for_download:
return model
- file_objs = {"mdl":StochSSModel, "smdl":StochSSSpatialModel}
- ext = file.split(".").pop()
- file_obj = file_objs[ext](model=model)
+ file_obj = StochSSModel(model=model)
model_pres = file_obj.load()
file_obj.print_logs(log)
return model_pres
+def process_smodel_presentation(path, file=None, for_download=False):
+ '''
+ Get the model presentation data from the file.
+
+ Attributes
+ ----------
+ path : str
+ Path to the model presentation file.
+ file : str
+ Name of the presentation file.
+ for_download : bool
+ Whether or not the model presentation is being downloaded.
+ '''
+ with open(path, "r", encoding="utf-8") as mdl_file:
+ body = json.load(mdl_file)
+ if "files" not in body:
+ body = StochSSSpatialModel(model=body).load(v1_domain=True)
+ if for_download:
+ return StochSSSpatialModel.get_presentation(**body)
+ for entry in body['files'].values():
+ if not os.path.exists(os.path.dirname(entry['pres_path'])):
+ os.makedirs(os.path.dirname(entry['pres_path']))
+ with open(entry['pres_path'], "w", encoding="utf-8") as entry_fd:
+ entry_fd.write(entry['body'])
+ file_obj = StochSSSpatialModel(model=body['model'])
+ model_pres = file_obj.load()
+ file_obj.print_logs(log)
+ return model_pres
class StochSSModel(StochSSBase):
'''
@@ -132,7 +176,6 @@ def __init__(self, model):
super().__init__()
self.model = model
-
@classmethod
def __update_event_assignments(cls, event, param_ids):
if "eventAssignments" not in event.keys():
@@ -147,14 +190,12 @@ def __update_event_assignments(cls, event, param_ids):
except ValueError:
pass
-
def __update_events(self, param_ids):
if "eventsCollection" not in self.model.keys() or not param_ids:
return
for event in self.model['eventsCollection']:
self.__update_event_assignments(event=event, param_ids=param_ids)
-
def __update_parameters(self):
if "parameters" not in self.model.keys():
return []
@@ -170,7 +211,6 @@ def __update_parameters(self):
pass
return param_ids
-
def __update_reactions(self):
if "reactions" not in self.model.keys():
return
@@ -184,7 +224,6 @@ def __update_reactions(self):
except ValueError:
pass
-
def __update_rules(self, param_ids):
if "rules" not in self.model.keys() or not param_ids:
return
@@ -198,7 +237,6 @@ def __update_rules(self, param_ids):
except ValueError:
pass
-
def load(self):
'''
Reads the model file, updates the model to the current format, and stores it in self.model
@@ -226,7 +264,6 @@ class StochSSSpatialModel(StochSSBase):
StochSS spatial model object
################################################################################################
'''
-
def __init__(self, model):
'''
Intitialize a spatial model object
@@ -239,9 +276,318 @@ def __init__(self, model):
super().__init__()
self.model = model
+ @classmethod
+ def __build_geometry(cls, geometry, name=None, formula=None):
+ if formula is None:
+ formula = geometry['formula']
+ if name is None:
+ name = geometry['name']
+
+ class NewGeometry(spatialpy.Geometry): # pylint: disable=too-few-public-methods
+ '''
+ ########################################################################################
+ Custom SpatialPy Geometry
+ ########################################################################################
+ '''
+ __class__ = f"__main__.{name}"
+ def __init__(self):
+ pass
+
+ def inside(self, point, on_boundary): # pylint: disable=no-self-use
+ '''
+ Custom inside function for geometry
+ '''
+ namespace = {'x': point[0], 'y': point[1], 'z': point[2]}
+ return eval(formula, {}, namespace)
+ return NewGeometry()
+
+ @classmethod
+ def __build_stochss_domain_particles(cls, domain):
+ particles = []
+ for i, vertex in enumerate(domain.vertices):
+ viscosity = domain.nu[i]
+ fixed = bool(domain.fixed[i])
+ type_id = domain.typeNdxMapping[domain.type_id[i]]
+ particle = {
+ "particle_id": i + 1, "point": list(vertex), "type": type_id,
+ "volume": domain.vol[i], "mass": domain.mass[i], "nu": viscosity,
+ "rho": domain.rho[i], "c": domain.c[i], "fixed": fixed
+ }
+ particles.append(particle)
+ return particles
+
+ def __convert_actions(self, domain, s_domain, type_ids):
+ geometries = self.__convert_geometries(s_domain)
+ lattices = self.__convert_lattices(s_domain)
+ transformations = self.__convert_transformations(s_domain, geometries, lattices)
+ try:
+ actions = sorted(s_domain['actions'], key=lambda action: action['priority'])
+ for i, action in enumerate(actions):
+ # Get geometry. 'Multi Particle' scope uses geometry from action
+ if action['geometry'] == "":
+ geometry = None
+ elif action['geometry'] in geometries:
+ geometry = geometries[action['geometry']]
+ else:
+ geometry = transformations[action['geometry']]
+ # Build props arg
+ if action['type'] in ('Fill Action', 'Set Action'):
+ kwargs = {
+ 'type_id': type_ids[action['typeID']], 'mass': action['mass'],
+ 'vol': action['vol'], 'rho': action['rho'], 'nu': action['nu'],
+ 'c': action['c'], 'fixed': action['fixed']
+ }
+ else:
+ kwargs = {}
+ # Apply actions
+ if action['type'] == "Fill Action":
+ if not action['useProps']:
+ kwargs = {}
+ if action['scope'] == 'Multi Particle':
+ if action['lattice'] in lattices:
+ lattice = lattices[action['lattice']]
+ else:
+ lattice = transformations[action['lattice']]
+ _ = domain.add_fill_action(
+ lattice=lattice, geometry=geometry, enable=action['enable'],
+ apply_action=action['enable'], **kwargs
+ )
+ else:
+ point = [action['point']['x'], action['point']['y'], action['point']['z']]
+ domain.add_point(point, **kwargs)
+ else:
+ # Get proper geometry for scope
+ # 'Single Particle' scope creates a geometry using actions point.
+ if action['scope'] == 'Single Particle':
+ p_x = action['point']['x']
+ p_y = action['point']['y']
+ p_z = action['point']['z']
+ formula = f"x == {p_x} and y == {p_y} and z == {p_z}"
+ geometry = self.__build_geometry(
+ None, name=f"SPAGeometry{i + 1}", formula=formula
+ )
+ if action['type'] == "Set Action":
+ domain.add_set_action(
+ geometry=geometry, enable=action['enable'],
+ apply_action=action['enable'], **kwargs
+ )
+ if action['scope'] == "Single Particle":
+ curr_pnt = numpy.array([
+ action['point']['x'], action['point']['y'], action['point']['z']
+ ])
+ new_pnt = numpy.array([
+ action['newPoint']['x'], action['newPoint']['y'],
+ action['newPoint']['z']
+ ])
+ if numpy.count_nonzero(curr_pnt - new_pnt) > 0:
+ for j, vertex in enumerate(domain.vertices):
+ if numpy.count_nonzero(curr_pnt - vertex) <= 0:
+ domain.vertices[j] = new_pnt
+ break
+ else:
+ domain.add_remove_action(
+ geometry=geometry, enable=action['enable'],
+ apply_action=action['enable']
+ )
+ except KeyError as err:
+ message = "Spatial actions are not properly formatted or "
+ message += f"are referenced incorrectly: {str(err)}"
+ raise StochSSModelFormatError(message, traceback.format_exc()) from err
+
+ def __convert_domain(self, type_ids, s_domain):
+ try:
+ xlim = tuple(s_domain['x_lim'])
+ ylim = tuple(s_domain['y_lim'])
+ zlim = tuple(s_domain['z_lim'])
+ rho0 = s_domain['rho_0']
+ c_0 = s_domain['c_0']
+ p_0 = s_domain['p_0']
+ gravity = s_domain['gravity']
+ if gravity == [0, 0, 0]:
+ gravity = None
+ domain = spatialpy.Domain(
+ 0, xlim, ylim, zlim, rho0=rho0, c0=c_0, P0=p_0, gravity=gravity
+ )
+ self.__convert_actions(domain, s_domain, type_ids)
+ self.__convert_types(domain, type_ids)
+ return domain
+ except KeyError as err:
+ message = "Spatial model domain properties are not properly formatted or "
+ message += f"are referenced incorrectly: {str(err)}"
+ raise StochSSModelFormatError(message, traceback.format_exc()) from err
+
+ @classmethod
+ def __convert_types(cls, domain, type_ids):
+ domain.typeNdxMapping = {"type_UnAssigned": 0}
+ domain.typeNameMapping = {0: "type_UnAssigned"}
+ domain.listOfTypeIDs = [0]
+ for ndx, name in type_ids.items():
+ name = f"type_{name}"
+ domain.typeNdxMapping[name] = ndx
+ domain.typeNameMapping[ndx] = name
+ domain.listOfTypeIDs.append(ndx)
+
+ def __convert_geometries(self, s_domain):
+ try:
+ geometries = {}
+ comb_geoms = []
+ for s_geometry in s_domain['geometries']:
+ if s_geometry['type'] == "Standard Geometry":
+ geometries[s_geometry['name']] = self.__build_geometry(s_geometry)
+ else:
+ name = s_geometry['name']
+ comb_geometry = spatialpy.CombinatoryGeometry("", {})
+ comb_geometry.formula = s_geometry['formula']
+ geometries[name] = comb_geometry
+ comb_geoms.append(name)
+ for name in comb_geoms:
+ geo_namespace = {
+ key: geometry for key, geometry in geometries.items() if key != name
+ }
+ geometries[name].geo_namespace = geo_namespace
+ return geometries
+ except KeyError as err:
+ message = "Spatial geometries are not properly formatted or "
+ message += f"are referenced incorrectly: {str(err)}"
+ raise StochSSModelFormatError(message, traceback.format_exc()) from err
@classmethod
- def __get_trace_data(cls, particles, name=""):
+ def __convert_lattices(cls, s_domain):
+ try:
+ lattices = {}
+ for s_lattice in s_domain['lattices']:
+ name = s_lattice['name']
+ center = [
+ s_lattice['center']['x'], s_lattice['center']['y'], s_lattice['center']['z']
+ ]
+ if s_lattice['type'] == "Cartesian Lattice":
+ lattice = spatialpy.CartesianLattice(
+ s_lattice['xmin'], s_lattice['xmax'], s_lattice['deltax'], center=center,
+ ymin=s_lattice['ymin'], ymax=s_lattice['ymax'], deltay=s_lattice['deltay'],
+ zmin=s_lattice['zmin'], zmax=s_lattice['zmax'], deltaz=s_lattice['deltaz']
+ )
+ elif s_lattice['type'] == "Spherical Lattice":
+ lattice = spatialpy.SphericalLattice(
+ s_lattice['radius'], s_lattice['deltas'],
+ center=center, deltar=s_lattice['deltar']
+ )
+ elif s_lattice['type'] == "Cylindrical Lattice":
+ lattice = spatialpy.CylindricalLattice(
+ s_lattice['radius'], s_lattice['length'], s_lattice['deltas'],
+ center=center, deltar=s_lattice['deltar']
+ )
+ elif s_lattice['type'] == "XML Mesh Lattice":
+ lattice = spatialpy.XMLMeshLattice(
+ s_lattice['filename'], center=center,
+ subdomain_file=s_lattice['subdomainFile']
+ )
+ elif s_lattice['type'] == "Mesh IO Lattice":
+ lattice = spatialpy.MeshIOLattice(
+ s_lattice['filename'], center=center,
+ subdomain_file=s_lattice['subdomainFile']
+ )
+ else:
+ lattice = spatialpy.StochSSLattice(s_lattice['filename'], center=center)
+ lattices[name] = lattice
+ return lattices
+ except KeyError as err:
+ message = "Spatial lattices are not properly formatted or "
+ message += f"are referenced incorrectly: {str(err)}"
+ raise StochSSModelFormatError(message, traceback.format_exc()) from err
+
+ @classmethod
+ def __convert_transformations(cls, s_domain, geometries, lattices):
+ try:
+ transformations = {}
+ nested_trans = {}
+ for s_transformation in s_domain['transformations']:
+ name = s_transformation['name']
+ vector = [
+ [
+ s_transformation['vector'][0]['x'],
+ s_transformation['vector'][0]['y'],
+ s_transformation['vector'][0]['z']
+ ],
+ [
+ s_transformation['vector'][1]['x'],
+ s_transformation['vector'][1]['y'],
+ s_transformation['vector'][1]['z']
+ ]
+ ]
+ if s_transformation['geometry'] != "":
+ geometry = geometries[s_transformation['geometry']]
+ else:
+ geometry = None
+ if s_transformation['lattice'] != "":
+ lattice = lattices[s_transformation['lattice']]
+ else:
+ lattice = None
+ if s_transformation['transformation'] != "":
+ nested_trans[name] = s_transformation['transformation']
+ if s_transformation['type'] == "Translate Transformation":
+ transformation = spatialpy.TranslationTransformation(
+ vector, geometry=geometry, lattice=lattice
+ )
+ elif s_transformation['type'] == "Rotate Transformation":
+ transformation = spatialpy.RotationTransformation(
+ vector, s_transformation['angle'], geometry=geometry, lattice=lattice
+ )
+ elif s_transformation['type'] == "Reflect Transformation":
+ normal = numpy.array([
+ s_transformation['normal']['x'], s_transformation['normal']['y'],
+ s_transformation['normal']['z']
+ ])
+ point1 = numpy.array([
+ s_transformation['point1']['x'], s_transformation['point1']['y'],
+ s_transformation['point1']['z']
+ ])
+ point2 = numpy.array([
+ s_transformation['point2']['x'], s_transformation['point2']['y'],
+ s_transformation['point2']['z']
+ ])
+ point3 = numpy.array([
+ s_transformation['point3']['x'], s_transformation['point3']['y'],
+ s_transformation['point3']['z']
+ ])
+ if numpy.count_nonzero(point3 - point1) <= 0 or \
+ numpy.count_nonzero(point2 - point1) <= 0:
+ point2 = None
+ point3 = None
+ else:
+ normal = None
+ transformation = spatialpy.ReflectionTransformation(
+ point1, normal=normal, point2=point2, point3=point3,
+ geometry=geometry, lattice=lattice
+ )
+ else:
+ center = numpy.array([
+ s_transformation['center']['x'], s_transformation['center']['y'],
+ s_transformation['center']['z']
+ ])
+ transformation = spatialpy.ScalingTransformation(
+ s_transformation['factor'], center=center,
+ geometry=geometry, lattice=lattice
+ )
+ transformations[name] = transformation
+ for trans, nested_tran in nested_trans.items():
+ transformations[trans].transformation = transformations[nested_tran]
+ return transformations
+ except KeyError as err:
+ message = "Spatial transformations are not properly formatted or "
+ message += f"are referenced incorrectly: {str(err)}"
+ raise StochSSModelFormatError(message, traceback.format_exc()) from err
+
+ @classmethod
+ def __get_trace_data(cls, particles, name="", index=None):
+ common_rgb_values = [
+ '#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd', '#8c564b', '#e377c2', '#7f7f7f',
+ '#bcbd22', '#17becf', '#ff0000', '#00ff00', '#0000ff', '#ffff00', '#00ffff', '#ff00ff',
+ '#800000', '#808000', '#008000', '#800080', '#008080', '#000080', '#ff9999', '#ffcc99',
+ '#ccff99', '#cc99ff', '#ffccff', '#62666a', '#8896bb', '#77a096', '#9d5a6c', '#9d5a6c',
+ '#eabc75', '#ff9600', '#885300', '#9172ad', '#a1b9c4', '#18749b', '#dadecf', '#c5b8a8',
+ '#000117', '#13a8fe', '#cf0060', '#04354b', '#0297a0', '#037665', '#eed284', '#442244',
+ '#ffddee', '#702afb'
+ ]
ids = []
x_data = []
y_data = []
@@ -251,92 +597,211 @@ def __get_trace_data(cls, particles, name=""):
x_data.append(particle['point'][0])
y_data.append(particle['point'][1])
z_data.append(particle['point'][2])
+ marker = {"size":5}
+ if index is not None:
+ marker["color"] = common_rgb_values[(index) % len(common_rgb_values)]
return plotly.graph_objs.Scatter3d(ids=ids, x=x_data, y=y_data, z=z_data,
- name=name, mode="markers", marker={"size":5})
-
+ name=name, mode="markers", marker=marker)
- def __load_domain_plot(self):
+ def __update_domain_to_current(self, domain=None):
domain = self.model['domain']
- trace_list = []
- for i, d_type in enumerate(domain['types']):
- if len(domain['types']) > 1:
- particles = list(filter(lambda particle, key=i: particle['type'] == key,
- domain['particles']))
- else:
- particles = domain['particles']
- trace = self.__get_trace_data(particles=particles, name=d_type['name'])
- trace_list.append(trace)
- layout = {"scene":{"aspectmode":'data'}, "autosize":True}
- if len(domain['x_lim']) == 2:
- layout["xaxis"] = {"range":domain['x_lim']}
- if len(domain['y_lim']) == 2:
- layout["yaxis"] = {"range":domain['y_lim']}
- return json.dumps({"data":trace_list, "layout":layout, "config":{"responsive":True}},
- cls=plotly.utils.PlotlyJSONEncoder)
-
- def __update_domain(self, domain=None):
- if domain is None:
- if "domain" not in self.model.keys() or len(self.model['domain'].keys()) < 6:
- raise StochSSAPIError(
- 406, "Domain Error", "The model does not contain a domain", None
+
+ if domain['template_version'] == self.DOMAIN_TEMPLATE_VERSION:
+ return None
+
+ self.__update_domain_to_v1(domain)
+ # Create version 1 domain directory if needed.
+ v1_dir = os.path.join('/tmp/presentation_cache', f"{self.model['name']}_domain_files")
+ if not os.path.exists(v1_dir):
+ os.mkdir(v1_dir)
+ # Get the file name for the version 1 domain file
+ v1_domain = None
+ file = self.get_file().replace(".smdl", ".domn")
+ filename = os.path.join(v1_dir, file)
+ if self.path == filename:
+ errmsg = f"{self.get_file()} may be a dependency of another doamin (.domn) "
+ errmsg += "or a spatial model (.smdl) and can't be updated."
+ raise DomainUpdateError(errmsg)
+ if os.path.exists(filename):
+ with open(filename, "r", encoding="utf-8") as v1_domain_fd:
+ v1_domain = json.dumps(json.load(v1_domain_fd), sort_keys=True, indent=4)
+ curr_domain = json.dumps(domain, sort_keys=True, indent=4)
+ if v1_domain != curr_domain:
+ filename, _ = self.get_unique_path(
+ self.get_file().replace(".smdl", ".domn"), dirname=v1_dir
)
- domain = self.model['domain']
- if "template_version" not in domain or domain['template_version'] != self.TEMPLATE_VERSION:
- if "static" not in domain.keys():
- domain['static'] = True
- type_changes = {}
- for i, d_type in enumerate(domain['types']):
- if d_type['typeID'] != i:
- type_changes[d_type['typeID']] = i
- d_type['typeID'] = i
- if "rho" not in d_type.keys():
- d_type['rho'] = d_type['mass'] / d_type['volume']
- if "c" not in d_type.keys():
- d_type['c'] = 10
- if "geometry" not in d_type.keys():
- d_type['geometry'] = ""
- if domain['particles']:
- for particle in domain['particles']:
- if particle['type'] in type_changes:
- particle['type'] = type_changes[particle['type']]
- if "rho" not in particle.keys():
- particle['rho'] = particle['mass'] / particle['volume']
- if "c" not in particle.keys():
- particle['c'] = 10
- domain['template_version'] = self.TEMPLATE_VERSION
+ v1_domain = None
+ entry = {
+ 'body': json.dumps(domain, sort_keys=True, indent=4), 'name': file,
+ 'dwn_path': os.path.join(f"{self.model['name']}_domain_files", file),
+ 'pres_path': filename
+ }
+
+ geometries = []
+ for d_type in domain['types']:
+ if 'geometry' in d_type and d_type['geometry']:
+ geometries.append({
+ 'name': f"geometry{len(geometries) + 1}",
+ 'type': 'Standard Geometry',
+ 'formula': d_type['geometry']
+ })
+ domain['actions'] = [{
+ 'type': 'Fill Action', 'scope': 'Multi Particle', 'priority': 1, 'enable': True,
+ 'geometry': '', 'lattice': 'lattice1', 'useProps': False,
+ 'point': {'x': 0, 'y': 0, 'z': 0}, 'newPoint': {'x': 0, 'y': 0, 'z': 0},
+ 'c': 10, 'fixed': False, 'mass': 1.0, 'nu': 0.0, 'rho': 1.0, 'typeID': 0, 'vol': 0.0
+ }]
+ domain['geometries'] = geometries
+ domain['lattices'] = [{
+ 'name': 'lattice1', 'type': 'StochSS Lattice',
+ 'filename': filename.replace(f'{self.user_dir}/', ''), 'subdomainFile': '',
+ 'center': {'x': 0, 'y': 0, 'z': 0}, 'length': 0, 'radius': 0,
+ 'deltar': 0,'deltas': 0,'deltax': 0,'deltay': 0,'deltaz': 0,
+ 'xmax': 0,'xmin': 0,'ymax': 0,'ymin': 0,'zmax': 0,'zmin': 0
+ }]
+ domain['transformations'] = []
+ domain['template_version'] = self.DOMAIN_TEMPLATE_VERSION
+
+ return {'lattice1': entry}
- def load(self):
+ @classmethod
+ def __update_domain_to_v1(cls, domain=None):
+ if domain['template_version'] == 1:
+ return
+
+ if "static" not in domain.keys():
+ domain['static'] = True
+ type_changes = {}
+ for i, d_type in enumerate(domain['types']):
+ if d_type['typeID'] != i:
+ type_changes[d_type['typeID']] = i
+ d_type['typeID'] = i
+ if "rho" not in d_type.keys():
+ d_type['rho'] = d_type['mass'] / d_type['volume']
+ if "c" not in d_type.keys():
+ d_type['c'] = 10
+ if "geometry" not in d_type.keys():
+ d_type['geometry'] = ""
+ if domain['particles']:
+ for particle in domain['particles']:
+ if particle['type'] in type_changes:
+ particle['type'] = type_changes[particle['type']]
+ if "rho" not in particle.keys():
+ particle['rho'] = particle['mass'] / particle['volume']
+ if "c" not in particle.keys():
+ particle['c'] = 10
+
+ def __update_model_to_current(self):
+ if self.model['template_version'] == self.TEMPLATE_VERSION:
+ return
+
+ if not self.model['defaultMode']:
+ self.model['defaultMode'] = "discrete"
+ elif self.model['defaultMode'] == "dynamic":
+ self.model['defaultMode'] = "discrete-concentration"
+ if "timestepSize" not in self.model['modelSettings'].keys():
+ self.model['modelSettings']['timestepSize'] = 1e-5
+ if "boundaryConditions" not in self.model.keys():
+ self.model['boundaryConditions'] = []
+ for species in self.model['species']:
+ if "types" not in species.keys():
+ species['types'] = list(range(1, len(self.model['domain']['types'])))
+ if "diffusionConst" not in species.keys():
+ if "diffusionCoeff" not in species.keys():
+ diff = 0.0
+ else:
+ diff = species['diffusionCoeff']
+ species['diffusionConst'] = diff
+ for reaction in self.model['reactions']:
+ if "odePropensity" not in reaction.keys():
+ reaction['odePropensity'] = reaction['propensity']
+ if "types" not in reaction.keys():
+ reaction['types'] = list(range(1, len(self.model['domain']['types'])))
+
+ self.model['template_version'] = self.TEMPLATE_VERSION
+
+ @classmethod
+ def get_presentation(cls, model=None, files=None):
+ ''' Get the presentation for download. '''
+ # Check if the domain has lattices
+ if len(model['domain']['lattices']) == 0:
+ return {'model': model, 'files': files}
+ # Process file based lattices
+ file_based_types = ('XML Mesh Lattice', 'Mesh IO Lattice', 'StochSS Lattice')
+ for lattice in model['domain']['lattices']:
+ if lattice['type'] in file_based_types:
+ lattice['filename'] = files[lattice['name']]['dwn_path']
+ if lattice['subdomainFile'] != "":
+ entry = files[f"{lattice['name']}_sdf"]
+ lattice['subdomainFile'] = entry['dwn_path']
+ return {'model': model, 'files': files}
+
+ def get_domain_plot(self, domain, s_domain):
'''
- Reads the spatial model file, updates it to the current format, and stores it in self.model
+ Get a plotly plot of the models domain or a prospective domain
Attributes
----------
+ domain : spatialpy.Domain
+ SpatialPy domain object used to generate plot data.
+ s_domain : stochss.Domain
+ StochSS domain object used to generate plot data.
'''
- if "template_version" not in self.model or \
- self.model['template_version'] != self.TEMPLATE_VERSION:
- if not self.model['defaultMode']:
- self.model['defaultMode'] = "discrete"
- elif self.model['defaultMode'] == "dynamic":
- self.model['defaultMode'] = "discrete-concentration"
- if "timestepSize" not in self.model['modelSettings'].keys():
- self.model['modelSettings']['timestepSize'] = 1e-5
- self.__update_domain()
- if "boundaryConditions" not in self.model.keys():
- self.model['boundaryConditions'] = []
- for species in self.model['species']:
- if "types" not in species.keys():
- species['types'] = list(range(1, len(self.model['domain']['types'])))
- if "diffusionConst" not in species.keys():
- if "diffusionCoeff" not in species.keys():
- diff = 0.0
- else:
- diff = species['diffusionCoeff']
- species['diffusionConst'] = diff
- for reaction in self.model['reactions']:
- if "odePropensity" not in reaction.keys():
- reaction['odePropensity'] = reaction['propensity']
- if "types" not in reaction.keys():
- reaction['types'] = list(range(1, len(self.model['domain']['types'])))
- self.model['template_version'] = self.TEMPLATE_VERSION
- plot = json.loads(self.__load_domain_plot())
- return {"model": self.model, "domainPlot": plot}
+ fig = domain.plot_types(return_plotly_figure=True)
+ # Case #3: 1 or more particles and one type
+ if len(s_domain['types']) == 1:
+ fig['data'][0]['name'] = "Un-Assigned"
+ ids = list(filter(lambda particle: particle['particle_id'], s_domain['particles']))
+ fig['data'][0]['ids'] = ids
+ # Case #4: 1 or more particles and multiple types
+ else:
+ for index, d_type in enumerate(s_domain['types']):
+ if d_type['name'] == "Un-Assigned":
+ t_test = lambda trace: trace['name'] in ("Un-Assigned", "UnAssigned")
+ else:
+ t_test = lambda trace, name=d_type['name']: trace['name'] == name
+ traces = list(filter(t_test, fig['data']))
+ if len(traces) == 0:
+ fig['data'].insert(index, self.__get_trace_data(
+ particles=[], name=d_type['name'], index=index
+ ))
+ else:
+ particles = list(filter(
+ lambda particle, key=d_type['typeID']: particle['type'] == key,
+ s_domain['particles']
+ ))
+ ids = list(map(lambda particle: particle['particle_id'], particles))
+ trace = traces[0]
+ trace['name'] = d_type['name']
+ trace['ids'] = ids
+ fig['layout']['width'] = None
+ fig['layout']['height'] = None
+ fig['layout']['autosize'] = True
+ fig['config'] = {"responsive":True}
+ return json.dumps(fig, cls=plotly.utils.PlotlyJSONEncoder)
+
+ def load(self, v1_domain=False):
+ '''
+ Reads the spatial model file, updates it to the current format, and stores it in self.model
+ '''
+ if "template_version" not in self.model:
+ self.model['template_version'] = 0
+ if "template_version" not in self.model['domain']:
+ self.model['domain']['template_version'] = 0
+
+ self.__update_model_to_current()
+ files = self.__update_domain_to_current()
+ if v1_domain:
+ return {'model': self.model, 'files': files}
+ plot = self.load_action_preview()
+ return {"model": self.model, "domainPlot": json.loads(plot)}
+
+ def load_action_preview(self):
+ ''' Get a domain preview of all enabled actions. '''
+ s_domain = self.model['domain']
+ types = sorted(s_domain['types'], key=lambda d_type: d_type['typeID'])
+ type_ids = {d_type['typeID']: d_type['name'] for d_type in types}
+ domain = self.__convert_domain(type_ids, s_domain)
+ # xlim, ylim, zlim = domain.get_bounding_box()
+ s_domain['particles'] = self.__build_stochss_domain_particles(domain)
+ return self.get_domain_plot(domain, s_domain)#, [list(xlim), list(ylim), list(zlim)]
diff --git a/jupyterhub/notebook_presentation.py b/jupyterhub/notebook_presentation.py
index e86fa47faa..c148af278b 100644
--- a/jupyterhub/notebook_presentation.py
+++ b/jupyterhub/notebook_presentation.py
@@ -1,6 +1,6 @@
'''
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/jupyterhub/presentation_base.py b/jupyterhub/presentation_base.py
index 7c4f7b04a3..778d533c16 100644
--- a/jupyterhub/presentation_base.py
+++ b/jupyterhub/presentation_base.py
@@ -1,6 +1,6 @@
'''
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -96,6 +96,7 @@ class StochSSBase():
'''
user_dir = os.path.expanduser("~") # returns the path to the users home directory
TEMPLATE_VERSION = 1
+ DOMAIN_TEMPLATE_VERSION = 2
def __init__(self, path=None):
'''
@@ -109,6 +110,17 @@ def __init__(self, path=None):
self.path = path
self.logs = []
+ def get_file(self, path=None):
+ '''
+ Get the file from the path
+
+ Attributes
+ ----------
+ path : str
+ Path to a file object
+ '''
+ file = self.path if path is None else path
+ return file.split('/').pop()
def log(self, level, message):
'''
@@ -123,7 +135,6 @@ def log(self, level, message):
'''
self.logs.append({"level":level, "message":message})
-
def print_logs(self, log):
'''
Display all internal logs to the console
diff --git a/jupyterhub/presentation_error.py b/jupyterhub/presentation_error.py
index 58da43af51..72185a90d0 100644
--- a/jupyterhub/presentation_error.py
+++ b/jupyterhub/presentation_error.py
@@ -1,6 +1,6 @@
'''
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -117,6 +117,47 @@ def __init__(self, msg, trace=None):
'''
super().__init__(406, "File Data Not JSON Format", msg, trace)
+class StochSSModelFormatError(StochSSAPIError):
+ '''
+ ################################################################################################
+ StochSS Model Not In Proper Format
+ ################################################################################################
+ '''
+
+ def __init__(self, msg, trace=None):
+ '''
+ Indicates that the model does not meet the current format requirements
+
+ Attributes
+ ----------
+ msg : str
+ Details on what caused the error
+ trace : str
+ Error traceback for the error
+ '''
+ super().__init__(406, "StochSS Model Not In Proper Format", msg, trace)
+
+class DomainUpdateError(StochSSAPIError):
+ '''
+ ################################################################################################
+ Domain File Can't Be Updated
+ ################################################################################################
+ '''
+
+ def __init__(self, msg, trace=None):
+ '''
+ Indicates that the domain file can't be updated as it may be a
+ dependency of another doamin or a spatial model.
+
+ Attributes
+ ----------
+ msg : str
+ Details on what caused the error
+ trace : str
+ Error traceback for the error
+ '''
+ super().__init__(405, "Domain File Can't Be Updated.", msg, trace)
+
####################################################################################################
# Job Errors
####################################################################################################
diff --git a/requirements.txt b/requirements.txt
index bd89812cc4..2488d89577 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -3,6 +3,7 @@ python-libsedml==2.0.9
python-libcombine==0.2.7
pyabc==0.12.6
escapism==1.0.1
-gillespy2==1.7.1
-spatialpy==1.1.1
+gillespy2==1.8.0
+spatialpy==1.2.1
+stochss-compute[AWS]==1.0.1
git+https://github.com/StochSS/sciope.git@master
diff --git a/stochss/handlers/__init__.py b/stochss/handlers/__init__.py
index 6907767ae5..ba5eadd179 100644
--- a/stochss/handlers/__init__.py
+++ b/stochss/handlers/__init__.py
@@ -1,6 +1,6 @@
'''
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -36,80 +36,95 @@ def get_page_handlers(route_start):
The base url for the web app
'''
handlers = [
- #
- ## Page Handlers
- #
+ ##############################################################################
+ # Page Handlers & API Handlers #
+ ##############################################################################
(r'/stochss/home\/?', UserHomeHandler),
+ (r'/stochss/quickstart\/?', QuickstartHandler),
+ (r'/stochss/example-library\/?', ExampleLibraryHandler),
(r'/stochss/files\/?$', ModelBrowserHandler),
(r'/stochss/models/edit\/?', ModelEditorHandler),
(r'/stochss/domain/edit\/?', DomainEditorHandler),
(r'/stochss/workflow/selection\/?', WorkflowSelectionHandler),
(r'/stochss/workflow/edit\/?', WorkflowEditorHandler),
- (r'/stochss/quickstart\/?', QuickstartHandler),
(r'/stochss/project/manager\/?', ProjectManagerHandler),
(r'/stochss/loading-page\/?', LoadingPageHandler),
(r'/stochss/multiple-plots\/?', MultiplePlotsHandler),
- (r'/stochss/example-library\/?', ExampleLibraryHandler),
- #
- ## API Handlers
- #
+ (r'/stochss/settings\/?', UserSettingsHandler),
(r'/stochss/api/user-logs\/?', UserLogsAPIHandler),
- (r'/stochss/api/example-library\/?', ImportFromLibrary),
(r'/stochss/api/clear-user-logs\/?', ClearUserLogsAPIHandler),
+ (r'/stochss/api/user-settings\/?', LoadUserSettings),
+ (r'/stochss/api/aws/job-config-check\/?', ConfirmAWSConfigHandler),
+ (r'/stochss/api/aws/launch-cluster\/?', LaunchAWSClusterHandler),
+ (r'/stochss/api/aws/cluster-status\/?', AWSClusterStatusHandler),
+ (r'/stochss/api/aws/terminate-cluster\/?', TerminateAWSClusterHandler),
+ ##############################################################################
+ # File Browser API Handlers #
+ ##############################################################################
(r"/stochss/api/file/browser-list\/?", ModelBrowserFileList),
- (r"/stochss/api/file/upload\/?", UploadFileAPIHandler),
- (r"/stochss/api/file/upload-from-link\/?", UploadFileFromLinkAPIHandler),
- (r"/stochss/api/file/move\/?", MoveFileAPIHandler),
(r"/stochss/api/file/empty-trash\/?", EmptyTrashAPIHandler),
(r"/stochss/api/file/delete\/?", DeleteFileAPIHandler),
+ (r"/stochss/api/file/move\/?", MoveFileAPIHandler),
+ (r"/stochss/api/file/duplicate\/?", DuplicateModelHandler),
+ (r"/stochss/api/directory/duplicate\/?", DuplicateDirectoryHandler),
(r"/stochss/api/file/rename\/?", RenameAPIHandler),
+ (r"/stochss/api/model/to-spatial\/?", ConvertToSpatialAPIHandler),
+ (r"/stochss/api/spatial/to-model\/?", ConvertToModelAPIHandler),
+ (r"/stochss/api/model/to-sbml\/?", ModelToSBMLAPIHandler),
+ (r"/stochss/api/sbml/to-model\/?", SBMLToModelAPIHandler),
(r"/stochss/api/file/download\/?", DownloadAPIHandler),
(r"/stochss/api/file/download-zip\/?", DownloadZipFileAPIHandler),
- (r"/stochss/api/file/json-data\/?", JsonFileAPIHandler),
- (r"/stochss/api/file/duplicate\/?", DuplicateModelHandler),
+ (r"/stochss/api/directory/create\/?", CreateDirectoryHandler),
+ (r"/stochss/api/file/upload\/?", UploadFileAPIHandler),
+ (r"/stochss/api/workflow/duplicate\/?", DuplicateWorkflowAsNewHandler),
+ (r"/stochss/api/workflow/edit-model\/?", GetWorkflowModelPathAPIHandler),
+ (r"/stochss/api/file/upload-from-link\/?", UploadFileFromLinkAPIHandler),
(r"/stochss/api/file/unzip\/?", UnzipFileAPIHandler),
- (r"/stochss/api/file/presentations\/?", PresentationListAPIHandler),
(r"/stochss/api/notebook/presentation\/?", NotebookPresentationAPIHandler),
- (r"/stochss/api/directory/duplicate\/?", DuplicateDirectoryHandler),
- (r"/stochss/api/directory/create\/?", CreateDirectoryHandler),
- (r"/stochss/api/spatial/to-model\/?", ConvertToModelAPIHandler),
- (r"/stochss/api/sbml/to-model\/?", SBMLToModelAPIHandler),
- (r"/stochss/api/model/to-notebook\/?", ModelToNotebookHandler),
- (r"/stochss/api/model/to-spatial\/?", ConvertToSpatialAPIHandler),
- (r"/stochss/api/model/to-sbml\/?", ModelToSBMLAPIHandler),
+ (r"/stochss/api/file/presentations\/?", PresentationListAPIHandler),
+ (r'/stochss/api/example-library\/?', ImportFromLibrary),
+ ##############################################################################
+ # Model API Handlers #
+ ##############################################################################
+ (r"/stochss/api/file/json-data\/?", JsonFileAPIHandler),
+ (r"/stochss/api/spatial-model/load-domain\/?", LoadDomainEditorAPIHandler),
+ (r"/stochss/api/spatial-model/domain-plot\/?", LoadDomainAPIHandler),
(r"/stochss/api/model/run\/?", RunModelAPIHandler),
(r"/stochss/api/model/exists\/?", ModelExistsAPIHandler),
- (r"/stochss/api/model/presentation\/?", ModelPresentationAPIHandler),
- (r"/stochss/api/model/new-bc\/?", CreateNewBoundCondAPIHandler),
+ (r"/stochss/api/spatial-model/import-mesh\/?", ImportMeshAPIHandler),
(r"/stochss/api/spatial-model/domain-list\/?", LoadExternalDomains),
- (r"/stochss/api/spatial-model/types-list\/?", LoadParticleTypesDescriptions),
+ (r"/stochss/api/model/new-bc\/?", CreateNewBoundCondAPIHandler),
+ (r"/stochss/api/model/presentation\/?", ModelPresentationAPIHandler),
+ (r"/stochss/api/spatial-model/lattice-files\/?", LoadLatticeFiles),
(r"/stochss/api/spatial-model/domain-plot\/?", LoadDomainAPIHandler),
(r"/stochss/api/spatial-model/load-domain\/?", LoadDomainEditorAPIHandler),
- (r"/stochss/api/spatial-model/particle-types\/?", GetParticlesTypesAPIHandler),
(r"/stochss/api/spatial-model/import-mesh\/?", ImportMeshAPIHandler),
- (r"/stochss/api/spatial-model/3d-domain\/?", Create3DDomainAPIHandler),
- (r"/stochss/api/spatial-model/apply-geometry\/?", ApplyGeometryAPIHandler),
- (r"/stochss/api/spatial-model/fill-geometry\/?", FillGeometryAPIHandler),
+ ##############################################################################
+ # Project API Handlers #
+ ##############################################################################
(r"/stochss/api/project/new-project\/?", NewProjectAPIHandler),
(r"/stochss/api/project/load-project\/?", LoadProjectAPIHandler),
(r"/stochss/api/project/load-browser\/?", LoadProjectBrowserAPIHandler),
+ (r"/stochss/api/project/load-project\/?", LoadProjectAPIHandler),
+ (r"/stochss/api/project/new-project\/?", NewProjectAPIHandler),
(r"/stochss/api/project/new-model\/?", NewModelAPIHandler),
(r"/stochss/api/project/add-existing-model\/?", AddExistingModelAPIHandler),
(r"/stochss/api/project/extract-model\/?", ExtractModelAPIHandler),
(r"/stochss/api/project/extract-workflow\/?", ExtractWorkflowAPIHandler),
- (r"/stochss/api/project/export-combine\/?", ExportAsCombineAPIHandler),
(r"/stochss/api/project/meta-data\/?", ProjectMetaDataAPIHandler),
+ (r"/stochss/api/project/export-combine\/?", ExportAsCombineAPIHandler),
(r"/stochss/api/project/save-annotation\/?", UpdateAnnotationAPIHandler),
(r"/stochss/api/project/update-format\/?", UpadteProjectAPIHandler),
- (r"/stochss/api/workflow/notebook\/?", WorkflowNotebookHandler),
+ ##############################################################################
+ # Workflow API Handlers #
+ ##############################################################################
(r"/stochss/api/workflow/new\/?", NewWorkflowAPIHandler),
(r"/stochss/api/workflow/load-workflow\/?", LoadWorkflowAPIHandler),
(r"/stochss/api/workflow/init-job\/?", InitializeJobAPIHandler),
(r"/stochss/api/workflow/run-job\/?", RunWorkflowAPIHandler),
(r"/stochss/api/workflow/workflow-status\/?", WorkflowStatusAPIHandler),
(r"/stochss/api/workflow/plot-results\/?", PlotWorkflowResultsAPIHandler),
- (r"/stochss/api/workflow/duplicate\/?", DuplicateWorkflowAsNewHandler),
- (r"/stochss/api/workflow/edit-model\/?", GetWorkflowModelPathAPIHandler),
+ (r"/stochss/api/workflow/notebook\/?", WorkflowNotebookHandler),
(r"/stochss/api/workflow/save-plot\/?", SavePlotAPIHandler),
(r"/stochss/api/workflow/save-annotation\/?", SaveAnnotationAPIHandler),
(r"/stochss/api/workflow/update-format\/?", UpadteWorkflowAPIHandler),
diff --git a/stochss/handlers/file_browser.py b/stochss/handlers/file_browser.py
index db3b011afa..03eb4c7693 100644
--- a/stochss/handlers/file_browser.py
+++ b/stochss/handlers/file_browser.py
@@ -1,6 +1,6 @@
'''
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -30,7 +30,7 @@
from .util import StochSSBase, StochSSFolder, StochSSFile, StochSSModel, StochSSSpatialModel, \
StochSSSBMLModel, StochSSNotebook, StochSSWorkflow, StochSSJob, StochSSProject, \
- StochSSAPIError, report_error
+ StochSSAPIError, report_error, report_critical_error
log = logging.getLogger('stochss')
@@ -62,42 +62,8 @@ async def get(self):
self.write(node)
except StochSSAPIError as err:
report_error(self, log, err)
- self.finish()
-
-
-class ModelToNotebookHandler(APIHandler):
- '''
- ################################################################################################
- Handler for handling conversions from model (.mdl) file to Jupyter Notebook
- (.ipynb) file.
- ################################################################################################
- '''
- @web.authenticated
- async def get(self):
- '''
- Runs the convert_to_notebook function from the convert_to_notebook script
- to build a Jupyter Notebook version of the model and write it to a file.
-
- Attributes
- ----------
- '''
- path = self.get_query_argument(name="path")
- log.debug(f"Path to the model file: {path}")
- self.set_header('Content-Type', 'application/json')
- try:
- is_spatial = path.endswith(".smdl")
- log.info(f"Getting data from {path.split('/').pop()}")
- model = StochSSSpatialModel(path=path) if is_spatial else StochSSModel(path=path)
- data = model.get_notebook_data()
- log.debug(f"Notebook data: {data}")
- log.info(f"Converting {path.split('/').pop()} to notebook")
- notebook = StochSSNotebook(**data)
- resp = notebook.create_ses_notebook() if is_spatial else notebook.create_es_notebook()
- log.info(f"Successfully created the notebook for {path.split('/').pop()}")
- log.debug(f"Notebook file path: {resp}")
- self.write(resp)
- except StochSSAPIError as err:
- report_error(self, log, err)
+ except Exception as err:
+ report_critical_error(self, log, err)
self.finish()
@@ -127,6 +93,8 @@ def get(self):
self.write(resp)
except StochSSAPIError as err:
report_error(self, log, err)
+ except Exception as err:
+ report_critical_error(self, log, err)
self.finish()
@@ -155,6 +123,8 @@ async def get(self):
self.write(resp)
except StochSSAPIError as err:
report_error(self, log, err)
+ except Exception as err:
+ report_critical_error(self, log, err)
self.finish()
@@ -189,6 +159,8 @@ async def get(self):
self.write(resp)
except StochSSAPIError as err:
report_error(self, log, err)
+ except Exception as err:
+ report_critical_error(self, log, err)
self.finish()
@@ -220,6 +192,8 @@ async def get(self):
self.write(resp)
except StochSSAPIError as err:
report_error(self, log, err)
+ except Exception as err:
+ report_critical_error(self, log, err)
self.finish()
@@ -251,6 +225,8 @@ async def get(self):
self.write(resp)
except StochSSAPIError as err:
report_error(self, log, err)
+ except Exception as err:
+ report_critical_error(self, log, err)
self.finish()
@@ -293,6 +269,8 @@ async def get(self):
self.write(resp)
except StochSSAPIError as err:
report_error(self, log, err)
+ except Exception as err:
+ report_critical_error(self, log, err)
self.finish()
@@ -325,6 +303,8 @@ async def get(self):
self.write(resp)
except StochSSAPIError as err:
report_error(self, log, err)
+ except Exception as err:
+ report_critical_error(self, log, err)
self.finish()
@@ -357,6 +337,8 @@ async def get(self):
self.write(resp)
except StochSSAPIError as err:
report_error(self, log, err)
+ except Exception as err:
+ report_critical_error(self, log, err)
self.finish()
@@ -391,6 +373,8 @@ async def get(self):
self.write(resp)
except StochSSAPIError as err:
report_error(self, log, err)
+ except Exception as err:
+ report_critical_error(self, log, err)
self.finish()
@@ -434,6 +418,8 @@ async def get(self):
self.write(resp)
except StochSSAPIError as err:
report_error(self, log, err)
+ except Exception as err:
+ report_critical_error(self, log, err)
self.finish()
@@ -461,6 +447,8 @@ async def get(self):
self.write(data)
except StochSSAPIError as err:
report_error(self, log, err)
+ except Exception as err:
+ report_critical_error(self, log, err)
self.finish()
@@ -498,6 +486,8 @@ async def get(self):
self.write(resp)
except StochSSAPIError as err:
report_error(self, log, err)
+ except Exception as err:
+ report_critical_error(self, log, err)
self.finish()
@@ -526,6 +516,8 @@ async def get(self):
self.write(f"{directories} was successfully created!")
except StochSSAPIError as err:
report_error(self, log, err)
+ except Exception as err:
+ report_critical_error(self, log, err)
self.finish()
@@ -574,6 +566,8 @@ async def post(self):
self.write(json.dumps(resp))
except StochSSAPIError as err:
report_error(self, log, err)
+ except Exception as err:
+ report_critical_error(self, log, err)
self.finish()
@@ -632,6 +626,8 @@ async def get(self):
self.write(resp)
except StochSSAPIError as err:
report_error(self, log, err)
+ except Exception as err:
+ report_critical_error(self, log, err)
self.finish()
@@ -661,6 +657,8 @@ async def get(self):
self.write(resp)
except StochSSAPIError as err:
report_error(self, log, err)
+ except Exception as err:
+ report_critical_error(self, log, err)
self.finish()
@@ -692,28 +690,36 @@ async def get(self):
self.write(resp)
except StochSSAPIError as err:
report_error(self, log, err)
+ except Exception as err:
+ report_critical_error(self, log, err)
elif cmd is None:
- overwrite = self.get_query_argument(name='overwrite', default=False)
- outfile = f"{str(uuid.uuid4()).replace('-', '_')}.tmp"
- log.debug(f"Response file name: {outfile}")
- exec_cmd = [script, f'{path}', f'{outfile}'] # Script commands for read run_cmd
- if overwrite:
- exec_cmd.append('-o')
- log.debug(f"Exec command: {exec_cmd}")
- pipe = subprocess.Popen(exec_cmd)
- resp = {"responsePath": outfile}
- log.debug(f"Response: {resp}")
- self.write(resp)
+ try:
+ overwrite = self.get_query_argument(name='overwrite', default=False)
+ outfile = f"{str(uuid.uuid4()).replace('-', '_')}.tmp"
+ log.debug(f"Response file name: {outfile}")
+ exec_cmd = [script, f'{path}', f'{outfile}'] # Script commands for read run_cmd
+ if overwrite:
+ exec_cmd.append('-o')
+ log.debug(f"Exec command: {exec_cmd}")
+ pipe = subprocess.Popen(exec_cmd)
+ resp = {"responsePath": outfile}
+ log.debug(f"Response: {resp}")
+ self.write(resp)
+ except Exception as err:
+ report_critical_error(self, log, err)
else:
- exec_cmd = [script, 'None', f'{path}'] # Script commands for read run_cmd
- log.debug(f"Exec command: {exec_cmd}")
- pipe = subprocess.Popen(exec_cmd, stdout=subprocess.PIPE, text=True)
- results, error = pipe.communicate()
- if error is not None:
- log.error(f"Errors thrown by the subprocess: {error}")
- resp = json.loads(results)
- log.debug(f"Response: {resp}")
- self.write(resp)
+ try:
+ exec_cmd = [script, 'None', f'{path}'] # Script commands for read run_cmd
+ log.debug(f"Exec command: {exec_cmd}")
+ pipe = subprocess.Popen(exec_cmd, stdout=subprocess.PIPE, text=True)
+ results, error = pipe.communicate()
+ if error is not None:
+ log.error(f"Errors thrown by the subprocess: {error}")
+ resp = json.loads(results)
+ log.debug(f"Response: {resp}")
+ self.write(resp)
+ except Exception as err:
+ report_critical_error(self, log, err)
self.finish()
@@ -741,6 +747,8 @@ async def get(self):
self.write(resp)
except StochSSAPIError as err:
report_error(self, log, err)
+ except Exception as err:
+ report_critical_error(self, log, err)
self.finish()
@@ -775,6 +783,8 @@ async def get(self):
self.write(resp)
except StochSSAPIError as err:
report_error(self, log, err)
+ except Exception as err:
+ report_critical_error(self, log, err)
self.finish()
@@ -801,6 +811,8 @@ async def get(self):
self.write({"presentations": presentations})
except StochSSAPIError as err:
report_error(self, log, err)
+ except Exception as err:
+ report_critical_error(self, log, err)
self.finish()
class ImportFromLibrary(APIHandler):
@@ -829,4 +841,6 @@ async def get(self):
self.write(examples)
except StochSSAPIError as err:
report_error(self, log, err)
+ except Exception as err:
+ report_critical_error(self, log, err)
self.finish()
diff --git a/stochss/handlers/log.py b/stochss/handlers/log.py
index 0a366b86ed..98f06d7b95 100644
--- a/stochss/handlers/log.py
+++ b/stochss/handlers/log.py
@@ -1,6 +1,6 @@
'''
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -22,6 +22,7 @@
from tornado.log import LogFormatter
log = logging.getLogger('stochss')
+spy_log = logging.getLogger('SpatialPy')
def init_log():
'''
@@ -33,6 +34,7 @@ def init_log():
relocate_old_logs()
setup_stream_handler()
setup_file_handler()
+ setup_spy_file_handler()
log.setLevel(logging.DEBUG)
log.propagate = False
@@ -119,12 +121,33 @@ def rotator(src, dst):
os.rename(src, dst)
os.remove(src)
+ fmt = '%(asctime)s$ %(message)s'
+ formatter = LogFormatter(fmt=fmt, datefmt="%b %d, %Y %I:%M %p UTC")
+
path = os.path.join(os.path.expanduser("~"), ".user-logs.txt")
handler = logging.handlers.RotatingFileHandler(path, maxBytes=500000, backupCount=1)
handler.namer = namer
handler.rotator = rotator
+ handler.setFormatter(formatter)
+ handler.setLevel(logging.INFO)
+
+ log.addHandler(handler)
+
+
+def setup_spy_file_handler():
+ '''
+ Initialize the SpatialPy file handler
+
+ Attributes
+ ----------
+ '''
+ print(spy_log.handlers)
fmt = '%(asctime)s$ %(message)s'
formatter = LogFormatter(fmt=fmt, datefmt="%b %d, %Y %I:%M %p UTC")
+
+ path = os.path.join(os.path.expanduser("~"), ".user-logs.txt")
+ handler = logging.FileHandler(path)
handler.setFormatter(formatter)
handler.setLevel(logging.INFO)
- log.addHandler(handler)
+
+ spy_log.addHandler(handler)
diff --git a/stochss/handlers/models.py b/stochss/handlers/models.py
index 90a5b9c6ab..16401b1bd6 100644
--- a/stochss/handlers/models.py
+++ b/stochss/handlers/models.py
@@ -1,6 +1,6 @@
'''
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -28,7 +28,7 @@
# Use finish() for json, write() for text
from .util import StochSSFolder, StochSSModel, StochSSSpatialModel, StochSSNotebook, \
- StochSSAPIError, report_error
+ StochSSAPIError, report_error, report_critical_error
log = logging.getLogger('stochss')
@@ -83,6 +83,8 @@ async def get(self):
report_error(self, log, new_model_err)
else:
report_error(self, log, load_err)
+ except Exception as err:
+ report_critical_error(self, log, err)
self.finish()
@@ -111,6 +113,8 @@ async def post(self):
log.info(f"Successfully saved {model.get_file(path=path)}")
except StochSSAPIError as err:
report_error(self, log, err)
+ except Exception as err:
+ report_critical_error(self, log, err)
self.finish()
@@ -146,6 +150,8 @@ async def get(self):
self.write(resp)
except StochSSAPIError as err:
report_error(self, log, err)
+ except Exception as err:
+ report_critical_error(self, log, err)
self.finish()
@@ -156,7 +162,7 @@ class LoadDomainAPIHandler(APIHandler):
################################################################################################
'''
@web.authenticated
- async def get(self):
+ async def post(self):
'''
Load and return the domain plot.
@@ -164,25 +170,21 @@ async def get(self):
----------
'''
self.set_header('Content-Type', 'application/json')
- path = self.get_query_argument(name="path", default=None)
- log.debug(f"Path to the spatial model: {path}")
- d_path = self.get_query_argument(name="domain_path", default=None)
- if d_path is not None:
- log.debug(f"Path to the domain file: {d_path}")
- new = self.get_query_argument(name="new", default=False)
- log.debug(f"The domain is new: {new}")
+ domain = json.loads(self.request.body.decode())
log.info("Generating the domain plot")
try:
- model = StochSSSpatialModel(path=path)
- fig, trace_temp = model.get_domain_plot(path=d_path, new=new)
+ model = StochSSSpatialModel(path="")
+ fig, limits = model.load_action_preview(domain)
log.info("Loading the domain plot")
if isinstance(fig, str):
fig = json.loads(fig)
- resp = {"fig":fig, "trace_temp": trace_temp}
+ resp = {"fig":fig, "particles": domain['particles'], "limits": limits}
log.debug(f"Response: {resp}")
self.write(resp)
except StochSSAPIError as err:
report_error(self, log, err)
+ except Exception as err:
+ report_critical_error(self, log, err)
self.finish()
@@ -214,33 +216,39 @@ async def get(self):
target = self.get_query_argument(name="target", default=None)
resp = {"Running":False, "Outfile":outfile, "Results":""}
if run_cmd == "start":
- model = StochSSModel(path=path)
- if os.path.exists(f".{model.get_name()}-preview.json"):
- os.remove(f".{model.get_name()}-preview.json")
- exec_cmd = ['/stochss/stochss/handlers/util/scripts/run_preview.py',
- f'{path}', f'{outfile}']
- if target is not None:
- exec_cmd.insert(1, "--target")
- exec_cmd.insert(2, f"{target}")
- log.debug(f"Script commands for running a preview: {exec_cmd}")
- subprocess.Popen(exec_cmd)
- resp['Running'] = True
- log.debug(f"Response to the start command: {resp}")
- self.write(resp)
- else:
- model = StochSSModel(path=path)
- log.info("Check for preview results ...")
- results = model.get_preview_results(outfile=outfile)
- log.debug(f"Results for the model preview: {results}")
- if results is None:
+ try:
+ model = StochSSModel(path=path)
+ if os.path.exists(f".{model.get_name()}-preview.json"):
+ os.remove(f".{model.get_name()}-preview.json")
+ exec_cmd = ['/stochss/stochss/handlers/util/scripts/run_preview.py',
+ f'{path}', f'{outfile}']
+ if target is not None:
+ exec_cmd.insert(1, "--target")
+ exec_cmd.insert(2, f"{target}")
+ log.debug(f"Script commands for running a preview: {exec_cmd}")
+ subprocess.Popen(exec_cmd)
resp['Running'] = True
- resp['Results'] = model.get_live_results()
- log.info("The preview is still running")
- else:
- resp['Results'] = results
- log.info("Loading the preview results")
- log.debug(f"Response to the read command: {resp}")
- self.write(resp)
+ log.debug(f"Response to the start command: {resp}")
+ self.write(resp)
+ except Exception as err:
+ report_critical_error(self, log, err)
+ else:
+ try:
+ model = StochSSModel(path=path)
+ log.info("Check for preview results ...")
+ results = model.get_preview_results(outfile=outfile)
+ log.debug(f"Results for the model preview: {results}")
+ if results is None:
+ resp['Running'] = True
+ resp['Results'] = model.get_live_results()
+ log.info("The preview is still running")
+ else:
+ resp['Results'] = results
+ log.info("Loading the preview results")
+ log.debug(f"Response to the read command: {resp}")
+ self.write(resp)
+ except Exception as err:
+ report_critical_error(self, log, err)
self.finish()
@@ -261,10 +269,13 @@ async def get(self):
self.set_header('Content-Type', 'application/json')
path = self.get_query_argument(name="path")
log.debug(f"Path to the file: {path}")
- model = StochSSModel(path=path)
- resp = {"exists":os.path.exists(model.get_path(full=True))}
- log.debug(f"Response: {resp}")
- self.write(resp)
+ try:
+ model = StochSSModel(path=path)
+ resp = {"exists":os.path.exists(model.get_path(full=True))}
+ log.debug(f"Response: {resp}")
+ self.write(resp)
+ except Exception as err:
+ report_critical_error(self, log, err)
self.finish()
@@ -283,25 +294,32 @@ async def post(self):
----------
'''
self.set_header('Content-Type', 'application/json')
- log.info(f"Loading the domain from {self.request.files['datafile'][0]['filename']}")
- data = self.request.files['datafile'][0]['body'].decode()
+ dirname = os.path.dirname(self.request.body_arguments['path'][0].decode())
+ if dirname == '.':
+ dirname = ""
+ elif ".wkgp" in dirname:
+ dirname = os.path.dirname(dirname)
+ mesh_file = self.request.files['datafile'][0]
+ log.info(f"Importing mesh: {mesh_file['filename']}")
if "typefile" in self.request.files.keys():
- log.info(f"Loading the particle types from \
- {self.request.files['typefile'][0]['filename']}")
- types = self.request.files['typefile'][0]['body'].decode().strip().split("\n")
+ type_file = self.request.files['typefile'][0]
+ log.info(f"Importing type descriptions: {type_file['filename']}")
else:
- types = None
- log.info("Loading particle data")
- particle_data = json.loads(self.request.body_arguments['particleData'][0].decode())
+ type_file = None
try:
- log.info("Generating new particles")
- resp = StochSSSpatialModel.get_particles_from_remote(domain=data, data=particle_data,
- types=types)
- log.debug(f"Number of Particles: {len(resp['particles'])}")
- log.info("Successfully created new particles")
+ folder = StochSSFolder(path=dirname)
+ mesh_resp = folder.upload('file', mesh_file['filename'], mesh_file['body'])
+ resp = {'meshPath': mesh_resp['path'], 'meshFile': mesh_resp['file']}
+ if type_file is not None:
+ types_resp = folder.upload('file', type_file['filename'], type_file['body'])
+ resp['typesPath'] = types_resp['path']
+ resp['typesFile'] = types_resp['file']
+ log.info("Successfully uploaded files")
self.write(resp)
except StochSSAPIError as err:
report_error(self, log, err)
+ except Exception as err:
+ report_critical_error(self, log, err)
self.finish()
@@ -328,95 +346,47 @@ async def get(self):
self.write(resp)
except StochSSAPIError as err:
report_error(self, log, err)
+ except Exception as err:
+ report_critical_error(self, log, err)
self.finish()
-class LoadParticleTypesDescriptions(APIHandler):
+class LoadLatticeFiles(APIHandler):
'''
################################################################################################
- Handler for getting particle type description files.
+ Handler for getting mesh/domain and type description files.
################################################################################################
'''
@web.authenticated
async def get(self):
'''
- Get text files on disc for particles type description selection.
+ Get mesh/domain and type description files on disc for file selections.
Attributes
----------
'''
self.set_header('Content-Type', 'application/json')
+ target_ext = self.get_query_argument(name="ext")
+ include_types = bool(self.get_query_argument(name="includeTypes", default=False))
try:
folder = StochSSFolder(path="")
test = lambda ext, root, file: bool(
- "trash" in root.split("/") or file.startswith('.') or 'wkfl' in root or root.startswith('.')
+ "trash" in root.split("/") or file.startswith('.') or \
+ 'wkfl' in root or root.startswith('.')
)
- resp = folder.get_file_list(ext=".txt", test=test)
- log.debug("Response: {resp}")
- self.write(resp)
- except StochSSAPIError as err:
- report_error(self, log, err)
- self.finish()
-
-
-class Create3DDomainAPIHandler(APIHandler):
- '''
- ################################################################################################
- Handler for creating a 3D domain.
- ################################################################################################
- '''
- @web.authenticated
- async def post(self):
- '''
- Create a 3D domain and return its particles.
-
- Attributes
- ----------
- '''
- self.set_header('Content-Type', 'application/json')
- log.info("Loading particle data")
- data = json.loads(self.request.body.decode())
- log.debug("Data used to create the domain: {data}")
- try:
- log.info("Generating new particles")
- model = StochSSSpatialModel(path="")
- resp = model.get_particles_from_3d_domain(data=data)
- log.debug(f"Number of Particles: {len(resp['particles'])}")
- log.info("Successfully created new particles")
- self.write(resp)
- except StochSSAPIError as err:
- report_error(self, log, err)
- self.finish()
-
-
-class GetParticlesTypesAPIHandler(APIHandler):
- '''
- ################################################################################################
- Handler getting particle types.
- ################################################################################################
- '''
- @web.authenticated
- async def get(self):
- '''
- Get particle types from a text file.
-
- Attributes
- ----------
- '''
- self.set_header('Content-Type', 'application/json')
- path = self.get_query_argument(name="path")
- log.debug("Path to the file: {}", path)
- try:
- log.info(f"Loading particle types from {path.split('/').pop()}")
- model = StochSSSpatialModel(path="")
- resp = model.get_types_from_file(path=path)
- log.debug(f"Number of Particles: {len(resp['types'])}")
+ mesh_files = folder.get_file_list(ext=target_ext, test=test)
+ resp = {'meshFiles': mesh_files}
+ if include_types:
+ type_files = folder.get_file_list(ext=".txt", test=test)
+ resp['typeFiles'] = type_files
+ log.debug(f"Response: {resp}")
self.write(resp)
except StochSSAPIError as err:
report_error(self, log, err)
+ except Exception as err:
+ report_critical_error(self, log, err)
self.finish()
-
class ModelPresentationAPIHandler(APIHandler):
'''
################################################################################################
@@ -444,13 +414,16 @@ async def get(self):
message = f"A presentation for {model.get_name()} already exists."
else:
message = f"Successfully published the {model.get_name()} presentation."
- file_objs[ext](**data)
+ if ext == "mdl":
+ file_objs[ext](**data)
resp = {"message": message, "links": links}
log.info(resp['message'])
log.debug(f"Response Message: {resp}")
self.write(resp)
except StochSSAPIError as err:
report_error(self, log, err)
+ except Exception as err:
+ report_critical_error(self, log, err)
self.finish()
@@ -482,59 +455,6 @@ async def post(self):
self.write(resp)
except StochSSAPIError as err:
report_error(self, log, err)
- self.finish()
-
-class ApplyGeometryAPIHandler(APIHandler):
- '''
- ################################################################################################
- Handler apply type and properties to particles.
- ################################################################################################
- '''
- @web.authenticated
- async def post(self):
- '''
- Apply type and properties to particles.
-
- Attributes
- ----------
- '''
- self.set_header('Content-Type', 'application/json')
- data = json.loads(self.request.body.decode())
- log.debug(f"Type used to apply geometry: {data['type']}")
- try:
- log.info("Applying type and properties to particles.")
- resp = StochSSSpatialModel.apply_geometry(data['particles'], data['type'], data['center'])
- log.info("Successfully applied type and properties to particles.")
- log.debug(f"Response Message: {resp}")
- self.write(resp)
- except StochSSAPIError as err:
- report_error(self, log, err)
- self.finish()
-
-class FillGeometryAPIHandler(APIHandler):
- '''
- ################################################################################################
- Handler fill geometry with particles.
- ################################################################################################
- '''
- @web.authenticated
- async def post(self):
- '''
- Fill a geomatry with particles.
-
- Attributes
- ----------
- '''
- self.set_header('Content-Type', 'application/json')
- data = json.loads(self.request.body.decode())
- log.debug(f"Agrs passed to Domain.fill_with_particles: {data['kwargs']}")
- log.debug(f"Type used to fill geometry: {data['type']}")
- try:
- log.info("Creating particles within the geometry.")
- resp = StochSSSpatialModel.fill_geometry(data['kwargs'], data['type'])
- log.info("Successfully filled the geometry particles.")
- log.debug(f"Response Message: {resp}")
- self.write(resp)
- except StochSSAPIError as err:
- report_error(self, log, err)
+ except Exception as err:
+ report_critical_error(self, log, err)
self.finish()
diff --git a/stochss/handlers/pages.py b/stochss/handlers/pages.py
index 58cf4d8100..5057e9fd24 100644
--- a/stochss/handlers/pages.py
+++ b/stochss/handlers/pages.py
@@ -1,6 +1,6 @@
'''
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -17,7 +17,10 @@
'''
import os
+import json
+import time
import logging
+import subprocess
from tornado import web
from notebook.base.handlers import IPythonHandler, APIHandler
# APIHandler documentation:
@@ -25,6 +28,9 @@
# Note APIHandler.finish() sets Content-Type handler to 'application/json'
# Use finish() for json, write() for text
+from .util import StochSSBase, report_error
+from .util.stochss_errors import AWSConfigurationError
+
log = logging.getLogger('stochss')
# pylint: disable=abstract-method
@@ -265,6 +271,23 @@ async def get(self):
self.render("multiple-plots-page.html", server_path=self.get_server_path())
+class UserSettingsHandler(PageHandler):
+ '''
+ ################################################################################################
+ StochSS User Settings Page Handler
+ ################################################################################################
+ '''
+ @web.authenticated
+ async def get(self):
+ '''
+ Render the StochSS user settings page.
+
+ Attributes
+ ----------
+ '''
+ self.render("stochss-user-settings.html", server_path=self.get_server_path())
+
+
class UserLogsAPIHandler(APIHandler):
'''
################################################################################################
@@ -317,3 +340,171 @@ async def get(self):
os.remove(f'{path}.bak')
open(path, "w", encoding="utf-8").close()
self.finish()
+
+
+class LoadUserSettings(APIHandler):
+ '''
+ ################################################################################################
+ StochSS handler for loading user settings
+ ################################################################################################
+ '''
+ @web.authenticated
+ async def get(self):
+ '''
+ Load the existing user settings.
+
+ Attributes
+ ----------
+ '''
+ file = StochSSBase(path='.user-settings.json')
+ settings = file.load_user_settings()
+
+ i_path = "/stochss/stochss_templates/instance_types.txt"
+ with open(i_path, "r", encoding="utf-8") as itype_fd:
+ instance_types = itype_fd.read().strip().split('|')
+ instances = {}
+ for instance_type in instance_types:
+ (i_type, size) = instance_type.split('.')
+ if i_type in instances:
+ instances[i_type].append(size)
+ else:
+ instances[i_type] = [size]
+
+ self.write({"settings": settings, "instances": instances})
+ self.finish()
+
+ @web.authenticated
+ async def post(self):
+ '''
+ Save the user settings.
+
+ Attributes
+ ----------
+ '''
+ data = json.loads(self.request.body.decode())
+ log.debug(f"Settings data to be saved: {data}")
+
+ def check_env_data(data):
+ if data['settings']['awsRegion'] == "":
+ return False
+ if data['settings']['awsAccessKeyID'] == "":
+ return False
+ if data['secret_key'] is None:
+ return False
+ return True
+
+ if check_env_data(data):
+ if not os.path.exists(".aws"):
+ os.mkdir(".aws")
+ with open(".aws/awsec2.env", "w", encoding="utf-8") as env_fd:
+ contents = "\n".join([
+ f"AWS_DEFAULT_REGION={data['settings']['awsRegion']}",
+ f"AWS_ACCESS_KEY_ID={data['settings']['awsAccessKeyID']}",
+ f"AWS_SECRET_ACCESS_KEY={data['secret_key']}"
+ ])
+ env_fd.write(contents)
+
+ with open(".user-settings.json", "w", encoding="utf-8") as usrs_fd:
+ json.dump(data['settings'], usrs_fd, indent=4, sort_keys=True)
+ self.finish()
+
+class ConfirmAWSConfigHandler(APIHandler):
+ '''
+ ################################################################################################
+ StochSS handler for comfirming AWS configuration
+ ################################################################################################
+ '''
+ @web.authenticated
+ async def get(self):
+ '''
+ Confirm that AWS is configured for running jobs..
+
+ Attributes
+ ----------
+ '''
+ file = StochSSBase(path='.user-settings.json')
+ settings = file.load_user_settings()
+ if settings['awsHeadNodeStatus'] != "running":
+ file.update_aws_status(settings['headNode'])
+ err = AWSConfigurationError("AWS is not properly configured for running jobs.")
+ report_error(self, log, err)
+
+ self.write({"configured": True})
+ self.finish()
+
+class LaunchAWSClusterHandler(APIHandler):
+ '''
+ ################################################################################################
+ StochSS handler for launching the AWS cluster
+ ################################################################################################
+ '''
+ @web.authenticated
+ async def get(self):
+ '''
+ Launch the AWS cluster.
+
+ Attributes
+ ----------
+ '''
+ file = StochSSBase(path='.user-settings.json')
+
+ script = "/stochss/stochss/handlers/util/scripts/aws_compute.py"
+ exec_cmd = [f"{script}", "-lv"]
+ with subprocess.Popen(exec_cmd):
+ print("Launching AWS")
+
+ time.sleep(1)
+ settings = file.load_user_settings()
+
+ self.write({"settings": settings})
+ self.finish()
+
+class AWSClusterStatusHandler(APIHandler):
+ '''
+ ################################################################################################
+ StochSS handler for updating the AWS cluster status
+ ################################################################################################
+ '''
+ @web.authenticated
+ async def get(self):
+ '''
+ Update the AWS cluster status.
+
+ Attributes
+ ----------
+ '''
+ file = StochSSBase(path='.user-settings.json')
+
+ settings = file.load_user_settings()
+
+ file.update_aws_status(settings['headNode'])
+
+ self.write({"settings": settings})
+ self.finish()
+
+class TerminateAWSClusterHandler(APIHandler):
+ '''
+ ################################################################################################
+ StochSS handler for terminating the AWS cluster
+ ################################################################################################
+ '''
+ @web.authenticated
+ async def get(self):
+ '''
+ Terminate the AWS cluster.
+
+ Attributes
+ ----------
+ '''
+ file = StochSSBase(path='.user-settings.json')
+
+ script = "/stochss/stochss/handlers/util/scripts/aws_compute.py"
+ exec_cmd = [f"{script}", "-tv"]
+ with subprocess.Popen(exec_cmd):
+ print("Terminating AWS")
+
+ time.sleep(1)
+ settings = file.load_user_settings()
+
+ self.write({"settings": settings})
+ self.finish()
diff --git a/stochss/handlers/project.py b/stochss/handlers/project.py
index a41c68adbb..1b5b63b6a5 100644
--- a/stochss/handlers/project.py
+++ b/stochss/handlers/project.py
@@ -1,6 +1,6 @@
'''
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -28,7 +28,7 @@
# Use finish() for json, write() for text
from .util import StochSSFolder, StochSSProject, StochSSModel, StochSSSpatialModel, \
- StochSSAPIError, report_error
+ StochSSAPIError, report_error, report_critical_error
log = logging.getLogger('stochss')
@@ -57,6 +57,8 @@ async def get(self):
self.write(data)
except StochSSAPIError as err:
report_error(self, log, err)
+ except Exception as err:
+ report_critical_error(self, log, err)
self.finish()
@@ -85,6 +87,8 @@ def get(self):
self.write(s_project)
except StochSSAPIError as err:
report_error(self, log, err)
+ except Exception as err:
+ report_critical_error(self, log, err)
self.finish()
@@ -115,6 +119,8 @@ def get(self):
self.write(resp)
except StochSSAPIError as err:
report_error(self, log, err)
+ except Exception as err:
+ report_critical_error(self, log, err)
self.finish()
@@ -132,7 +138,6 @@ def get(self):
Attributes
----------
'''
- log.setLevel(logging.DEBUG)
self.set_header('Content-Type', 'application/json')
path = self.get_query_argument(name="path")
log.debug(f"Path to the project: {path}")
@@ -146,7 +151,8 @@ def get(self):
self.write(resp)
except StochSSAPIError as err:
report_error(self, log, err)
- log.setLevel(logging.WARNING)
+ except Exception as err:
+ report_critical_error(self, log, err)
self.finish()
@@ -178,6 +184,8 @@ def get(self):
self.write(data)
except StochSSAPIError as err:
report_error(self, log, err)
+ except Exception as err:
+ report_critical_error(self, log, err)
self.finish()
@@ -207,6 +215,8 @@ def post(self):
self.write(resp)
except StochSSAPIError as err:
report_error(self, log, err)
+ except Exception as err:
+ report_critical_error(self, log, err)
self.finish()
@@ -243,6 +253,8 @@ def get(self):
self.write(message)
except StochSSAPIError as err:
report_error(self, log, err)
+ except Exception as err:
+ report_critical_error(self, log, err)
self.finish()
@@ -274,6 +286,8 @@ def get(self):
self.write(resp)
except StochSSAPIError as err:
report_error(self, log, err)
+ except Exception as err:
+ report_critical_error(self, log, err)
self.finish()
@@ -303,6 +317,8 @@ def post(self):
log.info("Successfully saved the metadata")
except StochSSAPIError as err:
report_error(self, log, err)
+ except Exception as err:
+ report_critical_error(self, log, err)
self.finish()
@@ -361,6 +377,8 @@ def post(self):
log.info("Successfully saved the annotation")
except StochSSAPIError as err:
report_error(self, log, err)
+ except Exception as err:
+ report_critical_error(self, log, err)
self.finish()
@@ -385,4 +403,6 @@ async def get(self):
proj.update_project_format()
except StochSSAPIError as err:
report_error(self, log, err)
+ except Exception as err:
+ report_critical_error(self, log, err)
self.finish()
diff --git a/stochss/handlers/util/__init__.py b/stochss/handlers/util/__init__.py
index cbc45fad0a..c0b67a89ef 100644
--- a/stochss/handlers/util/__init__.py
+++ b/stochss/handlers/util/__init__.py
@@ -1,6 +1,6 @@
'''
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -30,4 +30,4 @@
from .stochss_project import StochSSProject
from .ensemble_simulation import EnsembleSimulation
from .parameter_sweep import ParameterSweep
-from .stochss_errors import StochSSAPIError, report_error
+from .stochss_errors import StochSSAPIError, report_error, report_critical_error
diff --git a/stochss/handlers/util/ensemble_simulation.py b/stochss/handlers/util/ensemble_simulation.py
index bc6bd61f74..b47d0c1464 100644
--- a/stochss/handlers/util/ensemble_simulation.py
+++ b/stochss/handlers/util/ensemble_simulation.py
@@ -1,6 +1,6 @@
'''
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -18,12 +18,13 @@
import os
-import numpy
+import copy
import pickle
import logging
import traceback
-from gillespy2 import TimeSpan
+import gillespy2
+from stochss_compute import RemoteSimulation
from .stochss_job import StochSSJob
from .stochss_errors import StochSSAPIError, StochSSJobResultsError
@@ -89,21 +90,10 @@ def __update_timespan(self):
end = self.settings['timespanSettings']['endSim']
step_size = self.settings['timespanSettings']['timeStep']
self.g_model.timespan(
- TimeSpan.arange(step_size, t=end + step_size)
+ gillespy2.TimeSpan.arange(step_size, t=end + step_size)
)
-
- def run(self, preview=False, verbose=True):
- '''
- Run a GillesPy2 ensemble simulation job
-
- Attributes
- ----------
- preview : bool
- Indicates whether or not to run a 5 sec preivew
- verbose : bool
- Indicates whether or not to print debug statements
- '''
+ def __run(self, run_func, preview=False, verbose=True):
if preview:
if verbose:
log.info(f"Running {self.g_model.name} preview simulation")
@@ -117,16 +107,14 @@ def run(self, preview=False, verbose=True):
plot["layout"]["autosize"] = True
plot["config"] = {"responsive": True, "displayModeBar": True}
return plot
- if verbose:
- log.info("Running the ensemble simulation")
if self.settings['simulationSettings']['isAutomatic']:
self.__update_timespan()
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)
+ results = run_func(verbose=verbose, number_of_trajectories=1 if is_ode else 100)
else:
kwargs = self.__get_run_settings()
self.__update_timespan()
- results = self.g_model.run(**kwargs)
+ results = run_func(verbose=verbose, **kwargs)
if verbose:
log.info("The ensemble simulation has completed")
log.info("Storing the results as pickle")
@@ -140,3 +128,38 @@ def run(self, preview=False, verbose=True):
trace = str(pkl_err)
raise StochSSJobResultsError(message, trace)
return None
+
+ def __run_in_aws(self, verbose=False, **kwargs):
+ aws_kwargs = copy.deepcopy(kwargs)
+ if 'solver' in kwargs:
+ aws_kwargs['solver'] = kwargs['solver'].__class__
+ if verbose:
+ log.info("Running the ensemble simulation in AWS")
+
+ cluster = self.get_aws_cluster()
+ # Run the simulation
+ simulation = RemoteSimulation(self.g_model, server=cluster)
+ aws_results = simulation.run(**aws_kwargs)
+ return aws_results.get_gillespy2_results()
+
+ def __run_local(self, verbose=False, **kwargs):
+ if verbose:
+ log.info("Running the ensemble simulation locally")
+ return self.g_model.run(**kwargs)
+
+ def run(self, preview=False, verbose=True):
+ '''
+ Run a GillesPy2 ensemble simulation job
+
+ Attributes
+ ----------
+ preview : bool
+ Indicates whether or not to run a 5 sec preivew
+ verbose : bool
+ Indicates whether or not to print debug statements
+ '''
+ compute_env = self.load_info()['compute_env']
+ if compute_env == 'AWS':
+ results = self.__run(self.__run_in_aws, preview=preview, verbose=verbose)
+ return results
+ return self.__run(self.__run_local, preview=preview, verbose=verbose)
diff --git a/stochss/handlers/util/parameter_scan.py b/stochss/handlers/util/parameter_scan.py
index b0a4f54f44..1d306d1fd2 100644
--- a/stochss/handlers/util/parameter_scan.py
+++ b/stochss/handlers/util/parameter_scan.py
@@ -1,6 +1,6 @@
'''
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/stochss/handlers/util/parameter_sweep.py b/stochss/handlers/util/parameter_sweep.py
index e6b733cce6..a83957e0ee 100644
--- a/stochss/handlers/util/parameter_sweep.py
+++ b/stochss/handlers/util/parameter_sweep.py
@@ -1,6 +1,6 @@
'''
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/stochss/handlers/util/parameter_sweep_1d.py b/stochss/handlers/util/parameter_sweep_1d.py
index 96cf7aed89..796f97210b 100644
--- a/stochss/handlers/util/parameter_sweep_1d.py
+++ b/stochss/handlers/util/parameter_sweep_1d.py
@@ -1,6 +1,6 @@
'''
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/stochss/handlers/util/parameter_sweep_2d.py b/stochss/handlers/util/parameter_sweep_2d.py
index 41fd65f84e..82fecc8918 100644
--- a/stochss/handlers/util/parameter_sweep_2d.py
+++ b/stochss/handlers/util/parameter_sweep_2d.py
@@ -1,6 +1,6 @@
'''
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/stochss/handlers/util/parameter_sweep_notebook.py b/stochss/handlers/util/parameter_sweep_notebook.py
index f213f7d33e..18513e1b2c 100644
--- a/stochss/handlers/util/parameter_sweep_notebook.py
+++ b/stochss/handlers/util/parameter_sweep_notebook.py
@@ -1,6 +1,6 @@
'''
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -16,9 +16,11 @@
along with this program. If not, see .
'''
+import traceback
from nbformat import v4 as nbf
from .stochss_notebook import StochSSNotebook
+from .stochss_errors import StochSSModelFormatError
class StochSSParamSweepNotebook(StochSSNotebook):
'''
@@ -36,307 +38,323 @@ def __init__(self, path, new=False, models=None, settings=None):
Path to the notebook'''
super().__init__(path=path, new=new, models=models, settings=settings)
-
- def __create_1d_class_cell(self):
- pad = " "
- run_str = self.__create_1d_run_str()
- plt_strs = [f"{pad}def plot(c):", f"{pad*2}from matplotlib import pyplot as plt",
- f"{pad*2}from mpl_toolkits.axes_grid1 import make_axes_locatable",
- f"{pad*2}fig, ax = plt.subplots(figsize=(8, 8))",
- pad * 2 + "plt.title(f'Parameter Sweep - Variable:{c.variable_of_interest}')",
- f"{pad*2}plt.errorbar(c.p1_range, c.data[:, 0], c.data[:, 1])",
- f"{pad*2}plt.xlabel(c.p1, fontsize=16, fontweight='bold')",
- f"{pad*2}plt.ylabel('Population', fontsize=16, fontweight='bold')"]
- pltly_str = self.__create_1d_plotly_str()
- class_cell = ["class ParameterSweep1D():", "", run_str, "", "",
- "\n".join(plt_strs), "", "", pltly_str]
- return nbf.new_code_cell("\n".join(class_cell))
-
-
- def __create_1d_config_cell(self):
- pad = " "
- if "CSolver" in self.settings['solver']:
- model_str = f"{pad}model = {self.get_class_name()}()"
- else:
- model_str = f"{pad}ps_class = {self.get_class_name()}"
- config_cell = ["class ParameterSweepConfig(ParameterSweep1D):",
- f"{pad}# What class defines the GillesPy2 model", model_str]
- settings = self.settings['parameterSweepSettings']
- eval_str = "float(eval(model.get_parameter(p1).expression))"
- number_of_trajectories = self.settings['simulationSettings']['realizations']
- if not settings['parameters']:
- param = self.s_model['parameters'][0]
- p_min = f"0.5 * {eval_str}"
- p_max = f"1.5 * {eval_str}"
- p_steps = "11"
- spec_of_interest = self.s_model['species'][0]
- else:
- param = settings['parameters'][0]
- p_min = param['min']
- p_max = param['max']
- p_steps = param['steps']
- spec_of_interest = settings['speciesOfInterest']
- config_cell.extend([f"{pad}# ENTER PARAMETER HERE", f"{pad}p1 = '{param['name']}'",
- f"{pad}# ENTER START VALUE FOR P1 RANGE HERE", f"{pad}p1_min = {p_min}",
- f"{pad}# ENTER END VALUE FOR P1 RANGE HERE", f"{pad}p1_max = {p_max}",
- f"{pad}# ENTER THE NUMBER OF STEPS FOR P1 HERE",
- f"{pad}p1_steps = {p_steps}",
- f"{pad}p1_range = np.linspace(p1_min, p1_max, p1_steps)",
- f"{pad}# ENTER VARIABLE OF INTEREST HERE",
- f"{pad}variable_of_interest = '{spec_of_interest['name']}'",
- f"{pad}number_of_trajectories = {number_of_trajectories}",
- f"{pad}# What feature of the simulation are we examining",
- f"{pad}feature_extraction = population_at_last_timepoint",
- f"{pad}# for ensemble resutls: how do we aggreggate the values",
- f"{pad}ensemble_aggragator = mean_std_of_ensemble"])
- return nbf.new_code_cell("\n".join(config_cell))
-
-
- @classmethod
- def __create_1d_plotly_str(cls):
- pad = " "
- trace_str = f"{pad*2}trace_list = [go.Scatter(x=c.p1_range, y=c.data[:, 0]"
- trace_str += ", error_y=error_y)]"
- title_str = f"{pad*2}title = dict(text=f'Parameter Sweep - Variable: "
- title_str += "{c.variable_of_interest}', x=0.5)"
- lyout_str = f"{pad*2}layout = go.Layout(title=title, xaxis=xaxis_label, yaxis=yaxis_label)"
- pltly_strs = [f"{pad}def plotplotly(c, return_plotly_figure=False):",
- f"{pad*2}from plotly.offline import iplot",
- f"{pad*2}import plotly.graph_objs as go", "",
- f"{pad*2}visible = c.number_of_trajectories > 1",
- f"{pad*2}error_y = dict(type='data', array=c.data[:, 1], visible=visible)",
- "", trace_str, "", title_str,
- f"{pad*2}yaxis_label = dict(title='Population')",
- pad * 2 + "xaxis_label = dict(title=f'{c.p1}')", "",
- lyout_str, "", f"{pad*2}fig = dict(data=trace_list, layout=layout)", "",
- f"{pad*2}if return_plotly_figure:",
- f"{pad*3}return fig", f"{pad*2}iplot(fig)"]
- return "\n".join(pltly_strs)
-
-
- def __create_1d_run_str(self):
- pad = " "
- run_strs = [f"{pad}def run(c, kwargs, verbose=False):",
- f"{pad*2}c.verbose = verbose",
- f"{pad*2}fn = c.feature_extraction",
- f"{pad*2}ag = c.ensemble_aggragator",
- f"{pad*2}data = np.zeros((len(c.p1_range), 2)) # mean and std",
- f"{pad*2}for i, v1 in enumerate(c.p1_range):"]
- res_str = f"{pad*4}tmp_results = "
- if self.settings['solver'] == "SSACSolver":
- res_str += "model.run(**kwargs, variables={c.p1:v1})"
- else:
- res_str += "tmp_model.run(**kwargs)"
- run_strs.extend([f"{pad*3}tmp_model = c.ps_class()",
- f"{pad*3}tmp_model.listOfParameters[c.p1].expression = v1"])
- run_strs.extend([f"{pad*3}if c.verbose:",
- pad * 4 + "print(f'running {c.p1}={v1}')",
- f"{pad*3}if(c.number_of_trajectories > 1):",
- res_str,
- f"{pad*4}(m, s) = ag([fn(x) for x in tmp_results])",
- f"{pad*4}data[i, 0] = m",
- f"{pad*4}data[i, 1] = s",
- f"{pad*3}else:",
- res_str.replace("results", "result"),
- f"{pad*4}data[i, 0] = c.feature_extraction(tmp_result)",
- f"{pad*2}c.data = data"])
- return "\n".join(run_strs)
-
-
- def __create_2d_class_cell(self):
- pad = " "
- run_str = self.__create_2d_run_str()
- plt_strs = [f"{pad}def plot(c):", f"{pad*2}from matplotlib import pyplot as plt",
- f"{pad*2}from mpl_toolkits.axes_grid1 import make_axes_locatable",
- f"{pad*2}fig, ax = plt.subplots(figsize=(8, 8))",
- f"{pad*2}plt.imshow(c.data)",
- f"{pad*2}ax.set_xticks(np.arange(c.data.shape[1]) + 0.5, minor=False)",
- f"{pad*2}ax.set_yticks(np.arange(c.data.shape[0]) + 0.5, minor=False)",
- pad * 2 + "plt.title(f'Parameter Sweep - Variable: {c.variable_of_interest}')",
- f"{pad*2}ax.set_xticklabels(c.p1_range, minor=False, rotation=90)",
- f"{pad*2}ax.set_yticklabels(c.p2_range, minor=False)",
- f"{pad*2}ax.set_xlabel(c.p1, fontsize=16, fontweight='bold')",
- f"{pad*2}ax.set_ylabel(c.p2, fontsize=16, fontweight='bold')",
- f"{pad*2}divider = make_axes_locatable(ax)",
- f"{pad*2}cax = divider.append_axes('right', size='5%', pad=0.2)",
- f"{pad*2}_ = plt.colorbar(ax=ax, cax=cax)"]
- pltly_str = self.__create_2d_plotly_str()
- class_cell = ["class ParameterSweep2D():", "", run_str, "", "",
- "\n".join(plt_strs), "", "", pltly_str]
- return nbf.new_code_cell("\n".join(class_cell))
-
-
- def __create_2d_config_cell(self):
- pad = " "
- if "CSolver" in self.settings['solver']:
- model_str = f"{pad}model = {self.get_class_name()}()"
+ def __create_1d_aggregator(self):
+ pad = ' '
+ nb_agg = [
+ f"{pad}def ensemble_aggregator(self, results, key='avg', verbose=False):",
+ f"{pad*2}self.visible = len(results[0]) > 1",
+ f"{pad*2}data = [[self.func_map[key](result), numpy.std(result)] for result in results]",
+ f"{pad*2}if verbose:",
+ f"{pad*3}print(f'" + r"{key} and std of the ensembles: {data}')",
+ f"{pad*2}return numpy.array(data)", ""
+ ]
+ return '\n'.join(nb_agg)
+
+ def __create_1d_plot(self):
+ pad = ' '
+ nb_plot = [
+ f"{pad}def plot(self, species, fe_key='final', ea_key='avg', verbose=False):",
+ f"{pad*2}x_data = self.parameters[0]['range']",
+ f"{pad*2}fe_data = self.feature_extractor(species, key=fe_key, verbose=verbose)",
+ f"{pad*2}data = self.ensemble_aggregator(fe_data, key=ea_key, verbose=verbose)", "",
+ f"{pad*2}fig, ax = plt.subplots(figsize=(15, 6))",
+ f"{pad*2}plt.title(f'Parameter Sweep - Variable: " + r"{species}', fontsize=18, fontweight='bold')",
+ f"{pad*2}plt.errorbar(x_data, data[:, 0], data[:, 1])",
+ f"{pad*2}plt.xlabel(self.parameters[0]['param'], fontsize=16, fontweight='bold')",
+ f"{pad*2}plt.ylabel('Population', fontsize=16, fontweight='bold')", ""
+ ]
+ return '\n'.join(nb_plot)
+
+ def __create_1d_plotplotly(self):
+ pad = ' '
+ nb_plotplotly = [
+ f"{pad}def plotplotly(self, species, fe_key='final', ea_key='avg',",
+ f"{pad*6}return_plotly_figure=False, verbose=False):",
+ f"{pad*2}x_data = self.parameters[0]['range']",
+ f"{pad*2}fe_data = self.feature_extractor(species, key=fe_key, verbose=verbose)",
+ f"{pad*2}data = self.ensemble_aggregator(fe_data, key=ea_key, verbose=verbose)", "",
+ f"{pad*2}error_y = dict(type='data', array=data[:, 1], visible=self.visible)",
+ f"{pad*2}trace_list = [go.Scatter(x=x_data, y=data[:, 0], error_y=error_y)]", "",
+ f"{pad*2}title = f'Parameter Sweep - Variable: " + r"{species}'",
+ f"{pad*2}layout = go.Layout(", f"{pad*3}title=dict(text=title, x=0.5),",
+ f"{pad*3}xaxis=dict(title=f'" + r'{self.parameters[0]["param"]}' + "'),",
+ f"{pad*3}yaxis=dict(title='Population')", f"{pad*2})", "",
+ f"{pad*2}fig = dict(data=trace_list, layout=layout)",
+ f"{pad*2}if return_plotly_figure:", f"{pad*3}return fig", f"{pad*2}iplot(fig)"
+ ]
+ return '\n'.join(nb_plotplotly)
+
+ def __create_2d_aggregator(self):
+ pad = ' '
+ nb_agg = [
+ f"{pad}def ensemble_aggregator(self, results, key='avg', verbose=False):",
+ f"{pad*2}data = [self.func_map[key](result) for result in results]",
+ f"{pad*2}if verbose:", f"{pad*3}print(f'" + r"{key} of the ensembles: {data}')",
+ f"{pad*2}return numpy.array(data)", ""
+ ]
+ return '\n'.join(nb_agg)
+
+ def __create_2d_plot(self):
+ pad = ' '
+ nb_plot = [
+ f"{pad}def plot(self, species, fe_key='final', ea_key='avg', verbose=False):",
+ f"{pad*2}x_data = self.parameters[0]['range']",
+ f"{pad*2}y_data = self.parameters[1]['range']",
+ f"{pad*2}fe_data = self.feature_extractor(species, key=fe_key, verbose=verbose)",
+ f"{pad*2}ea_data = self.ensemble_aggregator(fe_data, key=ea_key, verbose=verbose)",
+ f"{pad*2}shape = (len(self.parameters[1]['range']), len(self.parameters[0]['range']))",
+ f"{pad*2}data = numpy.flip(numpy.reshape(ea_data, shape))", "",
+ f"{pad*2}fig, ax = plt.subplots(figsize=(16, 2 * len(data)))",
+ f"{pad*2}plt.imshow(data)",
+ f"{pad*2}ax.set_xticks(numpy.arange(data.shape[1]) + 0.5, minor=False)",
+ f"{pad*2}ax.set_yticks(numpy.arange(data.shape[0]) + 0.5, minor=False)",
+ f"{pad*2}plt.title(f'Parameter Sweep - Variable: " + r"{species}', fontsize=18, fontweight='bold')",
+ f"{pad*2}ax.set_xticklabels(x_data)", f"{pad*2}ax.set_yticklabels(y_data)",
+ f"{pad*2}ax.set_xlabel(self.parameters[1]['param'], fontsize=16, fontweight='bold')",
+ f"{pad*2}ax.set_ylabel(self.parameters[0]['param'], fontsize=16, fontweight='bold')",
+ f"{pad*2}ax.tick_params(axis='x', labelsize=14, labelrotation=90)",
+ f"{pad*2}ax.tick_params(axis='y', labelsize=14)",
+ f"{pad*2}divider = make_axes_locatable(ax)",
+ f"{pad*2}cax = divider.append_axes('right', size='5%', pad=0.2)",
+ f"{pad*2}_ = plt.colorbar(ax=ax, cax=cax)", ""
+ ]
+ return '\n'.join(nb_plot)
+
+ def __create_2d_plotplotly(self):
+ pad = ' '
+ nb_plotplotly = [
+ f"{pad}def plotplotly(self, species, fe_key='final', ea_key='avg',",
+ f"{pad*6}return_plotly_figure=False, verbose=False):",
+ f"{pad*2}x_data = self.parameters[1]['range']",
+ f"{pad*2}y_data = self.parameters[0]['range']",
+ f"{pad*2}fe_data = self.feature_extractor(species, key=fe_key, verbose=verbose)",
+ f"{pad*2}ea_data = self.ensemble_aggregator(fe_data, key=ea_key, verbose=verbose)",
+ f"{pad*2}shape = (len(self.parameters[1]['range']), len(self.parameters[0]['range']))",
+ f"{pad*2}data = numpy.reshape(ea_data, shape)", "",
+ f"{pad*2}trace_list = [go.Heatmap(z=data, x=x_data, y=y_data)]", "",
+ f"{pad*2}title = f'Parameter Sweep - Variable: " + r"{species}'",
+ f"{pad*2}layout = go.Layout(", f"{pad*3}title=dict(text=title, x=0.5),",
+ f"{pad*3}xaxis=dict(title=f'" + '{self.parameters[1]["param"]}' + "'),",
+ f"{pad*3}yaxis=dict(title=f'" + '{self.parameters[0]["param"]}' + "')",
+ f"{pad*2})", "", f"{pad*2}fig = dict(data=trace_list, layout=layout)",
+ f"{pad*2}if return_plotly_figure:", f"{pad*3}return fig", f"{pad*2}iplot(fig)"
+ ]
+ return '\n'.join(nb_plotplotly)
+
+ def __create_header_cells(self, cells):
+ cells.extend([
+ nbf.new_markdown_cell("***\n## Parameter Sweep\n***"),
+ nbf.new_markdown_cell("### Instantiate the Parameter Sweep"),
+ nbf.new_markdown_cell("***\n## Configure the Parameter Sweep\n***"),
+ nbf.new_markdown_cell("***\n## Run the Parameter Sweep\n***"),
+ nbf.new_markdown_cell("***\n## Visualization\n***")
+ ])
+
+ def __create_import_cells(self, cells, results=None):
+ base_imports = ["import copy", "import hashlib"]
+ if results is not None:
+ base_imports.insert(0, "import os")
+ base_imports.insert(2, "import pickle")
+ cells.insert(1, nbf.new_code_cell("\n".join(base_imports)))
+ cells.insert(2, nbf.new_code_cell("import numpy"))
+ cells.insert(3, nbf.new_markdown_cell(
+ "MatPlotLib and Plotly are used for creating custom visualizations"
+ ))
+ mpl_imports = ["from matplotlib import pyplot as plt"]
+ if self.nb_type == self.PARAMETER_SWEEP_2D:
+ mpl_imports.append("from mpl_toolkits.axes_grid1 import make_axes_locatable")
+ cells.insert(4, nbf.new_code_cell("\n".join(mpl_imports)))
+ cells.insert(5, nbf.new_code_cell(
+ "from plotly.offline import iplot\nimport plotly.graph_objs as go"
+ ))
+
+ def __create_parameter_sweep_class(self):
+ pad = ' '
+ nb_psweep_class = [
+ "class ParameterSweep:", f"{pad}def __init__(self, model):",
+ f"{pad*2}self.model = model", f"{pad*2}self.settings = " + r"{}",
+ f"{pad*2}self.parameters = []", f"{pad*2}self.__parameters = " + r"{}",
+ "", f"{pad*2}self.results = " + r"{}", f"{pad*2}self.__results = " + r"{}",
+ "", f"{pad}def __reset(self):", f"{pad*2}self.results = " + r"{}",
+ f"{pad*2}self.__results = " + r"{}", "",
+ f"{pad}def __run(self, variables, index, verbose=False):",
+ f"{pad*2}if index < len(self.parameters):",
+ f"{pad*3}parameter = self.parameters[index]", f"{pad*3}index += 1",
+ f"{pad*3}for value in parameter['range']:",
+ f"{pad*4}variables[parameter['param']] = value",
+ f"{pad*4}self.__run(variables, index, verbose=verbose)", f"{pad*2}else:",
+ f"{pad*3}sim_key = hashlib.md5(str(variables).encode('utf-8')).hexdigest()",
+ f"{pad*3}if sim_key not in self.results:",
+ f"{pad*4}model = self.__setup_model(variables=variables)",
+ f"{pad*4}if verbose:", f"{pad*5}print(f'--> running: " + "{str(variables)}" + "')",
+ f"{pad*4}self.results[sim_key] = model.run(**self.settings)", "",
+ f"{pad}def __resume(self, resume):", f"{pad*2}orig_params = " + r"{}",
+ f"{pad*2}self.results = resume.data", f"{pad*2}for parameter in resume.parameters:",
+ f"{pad*3}orig_params[parameter['param']] = parameter['range']",
+ f"{pad*3}self.add_parameter(parameter['param'], values= parameter['range'])",
+ f"{pad*2}new_params = [param for param in self.__parameters if param not in orig_params]",
+ f"{pad*2}if len(new_params) > 0:", f"{pad*3}variables = " + \
+ "{new_param: self.model.listOfParameters[new_param].value for new_param in new_params}",
+ f"{pad*3}self.__update_results(resume.parameters, variables, " + r"{})",
+ f"{pad*3}self.results = self.__results", "", f"{pad}def __setup_model(self, variables):",
+ f"{pad*2}if 'solver' in self.settings and 'CSolver' in self.settings['solver'].name:",
+ f"{pad*3}self.settings['variables'] = copy.deepcopy(variables)",
+ f"{pad*3}return self.model", f"{pad*2}model = copy.deepcopy(self.model)",
+ f"{pad*2}for name, value in variables.items():",
+ f"{pad*3}model.listOfParameters[name].expression = str(value)",f"{pad*2}return model",
+ "", f"{pad}def __update_results(self, parameters, variables, orig_vars, index=0):",
+ f"{pad*2}if index < len(parameters):", f"{pad*3}parameter = parameters[index]",
+ f"{pad*3}index += 1", f"{pad*3}for value in parameter['range']:",
+ f"{pad*4}orig_vars[parameter['param']] = value",
+ f"{pad*4}variables[parameter['param']] = value",
+ f"{pad*4}self.__update_results(parameters, variables, orig_vars, index=index)",
+ f"{pad*2}else:",
+ f"{pad*3}o_sim_key = hashlib.md5(str(orig_vars).encode('utf-8')).hexdigest()",
+ f"{pad*3}n_sim_key = hashlib.md5(str(variables).encode('utf-8')).hexdigest()",
+ f"{pad*3}self.__results[n_sim_key] = self.results[o_sim_key]", "",
+ f"{pad}def add_parameter(self, param, bounds=None, steps=11, values=None):",
+ f"{pad*2}if values is None:",
+ f"{pad*3}values = numpy.linspace(bounds[0], bounds[1], steps)", f"{pad*2}else:",
+ f"{pad*3}values = numpy.array(values)",
+ f"{pad*2}if param in self.__parameters:", f"{pad*3}for value in values:",
+ f"{pad*4}if value not in self.__parameters[param]:",
+ f"{pad*5}self.__parameters[param] = numpy.append(self.__parameters[param], value)",
+ f"{pad*3}self.__parameters[param] = numpy.sort(self.__parameters[param])",
+ f"{pad*2}else:", f"{pad*3}self.__parameters[param] = values",
+ f"{pad*2}self.parameters = [" + \
+ "{'param': param, 'range': ps_range} for param, ps_range in self.__parameters.items()]",
+ "", f"{pad}def run(self, settings=None, resume=None, verbose=False):",
+ f"{pad*2}if settings is not None:", f"{pad*3}self.settings = settings", "",
+ f"{pad*2}if resume is not None:", f"{pad*3}self.__resume(resume)",
+ f"{pad*2}index = 0", f"{pad*2}variables = " + r"{}",
+ f"{pad*2}self.__run(variables, index, verbose=verbose)", "",
+ f"{pad*2}results = Results(self.results, self.parameters)",
+ f"{pad*2}self.__reset()", f"{pad*2}return results"
+ ]
+ return nbf.new_code_cell('\n'.join(nb_psweep_class))
+
+ def __create_parameter_sweep_results_class(self):
+ pad = ' '
+ if self.nb_type == self.PARAMETER_SWEEP_1D:
+ aggregator = self.__create_1d_aggregator()
+ plot = self.__create_1d_plot()
+ plotplotly = self.__create_1d_plotplotly()
else:
- model_str = f"{pad}ps_class = {self.get_class_name()}"
- config_cell = ["class ParameterSweepConfig(ParameterSweep2D):",
- f"{pad}# What class defines the GillesPy2 model", model_str]
- settings = self.settings['parameterSweepSettings']
- p1_eval_str = "float(eval(model.get_parameter(p1).expression))"
- p2_eval_str = "float(eval(model.get_parameter(p2).expression))"
- number_of_trajectories = self.settings['simulationSettings']['realizations']
- if not settings['parameters']:
- param1 = self.s_model['parameters'][0]
- p1_min = f"0.5 * {p1_eval_str}"
- p1_max = f"1.5 * {p1_eval_str}"
- param2 = self.s_model['parameters'][1]
- p2_min = f"0.5 * {p2_eval_str}"
- p2_max = f"1.5 * {p2_eval_str}"
- spec_of_interest = self.s_model['species'][0]
+ aggregator = self.__create_2d_aggregator()
+ plot = self.__create_2d_plot()
+ plotplotly = self.__create_2d_plotplotly()
+ nb_results = [
+ "class Results:", f"{pad}func_map = " + "{",
+ f"{pad*2}'min': numpy.min, 'max': numpy.max, 'avg': numpy.mean,",
+ f"{pad*2}'var': numpy.var, 'final': lambda res: res[-1]", f"{pad}" + "}", "",
+ f"{pad}def __init__(self, data, parameters):", f"{pad*2}self.data = data",
+ f"{pad*2}self.visible = False", f"{pad*2}self.parameters = parameters", "",
+ f"{pad}def feature_extractor(self, species, results=None, key='final', verbose=False):",
+ f"{pad*2}if results is None:", f"{pad*3}results = self.data.values()",
+ f"{pad*2}data = [[self.func_map[key](traj[species]) for traj in result] for result in results]",
+ f"{pad*2}if verbose:",
+ f"{pad*3}print(f'" + r"{key} populations for {species}: {data}')",
+ f"{pad*2}return numpy.array(data)", "", aggregator, plot, plotplotly
+ ]
+ return nbf.new_code_cell('\n'.join(nb_results))
+
+ def __create_parameter_sweep_config(self, cells, index, count, results):
+ pad = ' '
+ if results is None:
+ tmp_val = "float(eval(model.get_parameter('__NAME__').expression))"
+ tmp_bounds = f"p__I___bounds = (\n{pad}0.5 * {tmp_val},\n{pad}1.5 * {tmp_val}\n)"
+ tmp = f"{tmp_bounds}\npsweep.add_parameter('__NAME__', bounds=p1_bounds, steps=11)"
+ try:
+ for i in range(count):
+ parameter = self.s_model['parameters'][i]
+ psweep_config = tmp.replace("__I__", str(i + 1))
+ psweep_config = psweep_config.replace("__NAME__", parameter['name'])
+ cells.insert(index, nbf.new_code_cell(psweep_config))
+ index += 1
+ except KeyError as err:
+ message = "Parameters are not properly formatted or "
+ message += f"are referenced incorrectly for notebooks: {str(err)}"
+ raise StochSSModelFormatError(message, traceback.format_exc()) from err
else:
- param1 = settings['parameters'][0]
- p1_min = param1['min']
- p1_max = param1['max']
- param2 = settings['parameters'][1]
- p2_min = param2['min']
- p2_max = param2['max']
- spec_of_interest = settings['speciesOfInterest']
- config_cell.extend([f"{pad}# ENTER PARAMETER 1 HERE", f"{pad}p1 = '{param1['name']}'",
- f"{pad}# ENTER PARAMETER 2 HERE", f"{pad}p2 = '{param2['name']}'",
- f"{pad}# ENTER START VALUE FOR P1 RANGE HERE",
- f"{pad}p1_min = {p1_min}",
- f"{pad}# ENTER END VALUE FOR P1 RANGE HERE", f"{pad}p1_max = {p1_max}",
- f"{pad}# ENTER THE NUMBER OF STEPS FOR P1 HERE",
- f"{pad}p1_steps = {param1['steps'] if settings['parameters'] else 11}",
- f"{pad}p1_range = np.linspace(p1_min, p1_max, p1_steps)",
- f"{pad}# ENTER START VALUE FOR P2 RANGE HERE",
- f"{pad}p2_min = {p2_min}",
- f"{pad}# ENTER END VALUE FOR P2 RANGE HERE", f"{pad}p2_max = {p2_max}",
- f"{pad}# ENTER THE NUMBER OF STEPS FOR P2 HERE",
- f"{pad}p2_steps = {param2['steps'] if settings['parameters'] else 11}",
- f"{pad}p2_range = np.linspace(p2_min, p2_max, p2_steps)",
- f"{pad}# ENTER VARIABLE OF INTEREST HERE",
- f"{pad}variable_of_interest = '{spec_of_interest['name']}'",
- f"{pad}number_of_trajectories = {number_of_trajectories}",
- f"{pad}# What feature of the simulation are we examining",
- f"{pad}feature_extraction = population_at_last_timepoint",
- f"{pad}# for ensemble resutls: how do we aggreggate the values",
- f"{pad}ensemble_aggragator = average_of_ensemble"])
- return nbf.new_code_cell("\n".join(config_cell))
-
-
- @classmethod
- def __create_2d_plotly_str(cls):
- pad = " "
- title_str = f"{pad*2}title = dict(text=f'Parameter Sweep - Variable: "
- title_str += "{c.variable_of_interest}', x=0.5)"
- lyout_str = f"{pad*2}layout = go.Layout(title=title, xaxis=xaxis_label, yaxis=yaxis_label)"
- pltly_strs = [f"{pad}def plotplotly(c, return_plotly_figure=False):",
- f"{pad*2}from plotly.offline import init_notebook_mode, iplot",
- f"{pad*2}import plotly.graph_objs as go", "",
- f"{pad*2}xaxis_ticks = c.p1_range", f"{pad*2}yaxis_ticks = c.p2_range", "",
- f"{pad*2}trace_list = [go.Heatmap(z=c.data, x=xaxis_ticks, y=yaxis_ticks)]",
- title_str, pad * 2 + "xaxis_label = dict(title=f'{c.p1}')",
- pad * 2 + "yaxis_label = dict(title=f'{c.p2}')", "",
- lyout_str, "", f"{pad*2}fig = dict(data=trace_list, layout=layout)", "",
- f"{pad*2}if return_plotly_figure:",
- f"{pad*3}return fig", f"{pad*2}iplot(fig)"]
- return "\n".join(pltly_strs)
-
-
- def __create_2d_run_str(self):
- pad = " "
- run_strs = [f"{pad}def run(c, kwargs, verbose=False):",
- f"{pad*2}c.verbose = verbose",
- f"{pad*2}fn = c.feature_extraction",
- f"{pad*2}ag = c.ensemble_aggragator",
- f"{pad*2}data = np.zeros((len(c.p1_range), len(c.p2_range)))",
- f"{pad*2}for i, v1 in enumerate(c.p1_range):",
- f"{pad*3}for j, v2 in enumerate(c.p2_range):"]
- res_str = f"{pad*5}tmp_results = "
- if self.settings['solver'] == "SSACSolver":
- res_str += "model.run(**kwargs, variables={c.p1:v1, c.p2:v2})"
+ tmp = "psweep.add_parameter('__NAME__', values=__VALUE__)"
+ try:
+ for parameter in self.settings['parameterSweepSettings']['parameters']:
+ psweep_config = tmp.replace("__NAME__", parameter['name'])
+ values = [str(value) for value in parameter['range']]
+ psweep_config = psweep_config.replace(
+ "__VALUE__", f"[\n{pad}{', '.join(values)}\n]"
+ )
+ cells.insert(index, nbf.new_code_cell(psweep_config))
+ index += 1
+ except KeyError as err:
+ message = "Parameter sweep settings are not properly formatted or "
+ message += f"are referenced incorrectly for notebooks: {str(err)}"
+ raise StochSSModelFormatError(message, traceback.format_exc()) from err
+ return index + 1
+
+ def __create_visualization(self, results):
+ if results is None:
+ spec_of_interest = self.s_model['species'][0]['name']
else:
- res_str += "tmp_model.run(**kwargs)"
- run_strs.extend([f"{pad*4}tmp_model = c.ps_class()",
- f"{pad*4}tmp_model.listOfParameters[c.p1].expression = v1",
- f"{pad*4}tmp_model.listOfParameters[c.p2].expression = v2"])
- run_strs.extend([f"{pad*4}if c.verbose:",
- pad * 5 + "print(f'running {c.p1}={v1}, {c.p2}={v2}')",
- f"{pad*4}if(c.number_of_trajectories > 1):",
- res_str,
- f"{pad*5}data[i, j] = ag([fn(x) for x in tmp_results])",
- f"{pad*4}else:",
- res_str.replace("results", "result"),
- f"{pad*5}data[i, j] = c.feature_extraction(tmp_result)",
- f"{pad*2}c.data = data"])
- return "\n".join(run_strs)
-
+ spec_of_interest = self.settings['parameterSweepSettings']['speciesOfInterest']['name']
+ return nbf.new_code_cell(f"results.plotplotly('{spec_of_interest}')")
- def __create_post_process_cells(self):
- pad = " "
- fe_vbs_pnt = f"{pad*2}print(f'population_at_last_timepoint"
- fe_vbs_pnt += " {c.variable_of_interest}={res[c.variable_of_interest][-1]}')"
- # feature extraction cell
- fe_cell = ["# What value(s) do you want to extract from the simulation trajectory",
- "def population_at_last_timepoint(c, res):", f"{pad}if c.verbose:",
- fe_vbs_pnt, f"{pad}return res[c.variable_of_interest][-1]"]
- # mean std aggragator cell
- msa_cell = ["# How do we combine the values from multiple trajectores",
- "def mean_std_of_ensemble(c, data):", f"{pad}a = np.average(data)",
- f"{pad}s = np.std(data)", f"{pad}if c.verbose:",
- pad * 2 + "print(f'mean_std_of_ensemble m:{a} s:{s}')",
- f"{pad}return (a, s)"]
- # average aggragator cell
- aa_cell = [msa_cell[0], "def average_of_ensemble(c, data):",
- f"{pad}a = np.average(data)", f"{pad}if c.verbose:",
- pad * 2 + "print(f'average_of_ensemble = {a}')",
- f"{pad}return a"]
- cells = [nbf.new_markdown_cell("# Post Processing"),
- nbf.new_markdown_cell("## Feature extraction function"),
- nbf.new_code_cell('\n'.join(fe_cell)),
- nbf.new_markdown_cell("## Aggregation function")]
- if self.nb_type == self.PARAMETER_SWEEP_1D:
- cells.append(nbf.new_code_cell('\n'.join(msa_cell)))
+ def __create_run(self, results):
+ nb_run = ["kwargs = configure_simulation()"]
+ if results is None:
+ nb_run.append("results = psweep.run(settings=kwargs)")
else:
- cells.append(nbf.new_code_cell('\n'.join(aa_cell)))
- return cells
-
-
- def create_1d_notebook(self):
- '''Create a 1D parameter sweep jupiter notebook for a StochSS model/workflow
-
- Attributes
- ----------'''
+ nb_load_res = [
+ "# results = psweep.run(settings=kwargs)", f"path = '{results}'",
+ "with open(os.path.join(os.path.expanduser('~'), path), 'rb') as results_file:",
+ " results = Results(pickle.load(results_file), psweep.parameters)"
+ ]
+ nb_run.extend(nb_load_res)
+ nb_run = "\n".join(nb_run)
+ return nbf.new_code_cell(nb_run)
+
+ def create_1d_notebook(self, results=None, compute="StochSS"):
+ '''Create a 1D parameter sweep jupiter notebook for a StochSS model/workflow'''
self.nb_type = self.PARAMETER_SWEEP_1D
- self.settings['solver'] = self.get_gillespy2_solver_name()
- run_strs = ["kwargs = configure_simulation()", "ps = ParameterSweepConfig()",
- "%time ps.run(kwargs)"]
cells = self.create_common_cells()
- cells.extend(self.__create_post_process_cells())
- cells.extend([nbf.new_markdown_cell("# Parameter Sweep"),
- self.__create_1d_class_cell(),
- self.__create_1d_config_cell(),
- nbf.new_code_cell("\n".join(run_strs)),
- nbf.new_markdown_cell("# Visualization"),
- nbf.new_code_cell("ps.plot()"),
- nbf.new_code_cell("ps.plotplotly()")])
+ self.__create_import_cells(cells, results)
+ self.__create_header_cells(cells)
+
+ self.settings['solver'] = self.get_gillespy2_solver_name()
+ cells.insert(14, self.__create_parameter_sweep_class())
+ cells.insert(15, self.__create_parameter_sweep_results_class())
+ cells.insert(17, nbf.new_code_cell("psweep = ParameterSweep(model=model)"))
+ index = self.__create_parameter_sweep_config(cells, 19, 1, results)
+ cells.insert(index, self.__create_run(results))
+ cells.append(self.__create_visualization(results))
+ if compute != "StochSS":
+ self.log(
+ "warning",
+ "AWS Cloud compute environment is not supported by 1D parameter sweep workflows."
+ )
message = self.write_notebook_file(cells=cells)
return {"Message":message, "FilePath":self.get_path(), "File":self.get_file()}
-
- def create_2d_notebook(self):
- '''Create a 2D parameter sweep jupiter notebook for a StochSS model/workflow
-
- Attributes
- ----------'''
+ def create_2d_notebook(self, results=None, compute="StochSS"):
+ '''Create a 2D parameter sweep jupiter notebook for a StochSS model/workflow'''
self.nb_type = self.PARAMETER_SWEEP_2D
- self.settings['solver'] = self.get_gillespy2_solver_name()
- run_strs = ["kwargs = configure_simulation()", "ps = ParameterSweepConfig()",
- "%time ps.run(kwargs)"]
cells = self.create_common_cells()
- cells.extend(self.__create_post_process_cells())
- cells.extend([nbf.new_markdown_cell("# Parameter Sweep"),
- self.__create_2d_class_cell(),
- self.__create_2d_config_cell(),
- nbf.new_code_cell("\n".join(run_strs)),
- nbf.new_markdown_cell("# Visualization"),
- nbf.new_code_cell("ps.plot()"),
- nbf.new_code_cell("ps.plotplotly()")])
+ self.__create_import_cells(cells, results)
+ self.__create_header_cells(cells)
+
+ self.settings['solver'] = self.get_gillespy2_solver_name()
+ cells.insert(14, self.__create_parameter_sweep_class())
+ cells.insert(15, self.__create_parameter_sweep_results_class())
+ cells.insert(17, nbf.new_code_cell("psweep = ParameterSweep(model=model)"))
+ index = self.__create_parameter_sweep_config(cells, 19, 2, results)
+ cells.insert(index, self.__create_run(results))
+ cells.append(self.__create_visualization(results))
+ if compute != "StochSS":
+ self.log(
+ "warning",
+ "AWS Cloud compute environment is not supported by 2D parameter sweep workflows."
+ )
message = self.write_notebook_file(cells=cells)
return {"Message":message, "FilePath":self.get_path(), "File":self.get_file()}
diff --git a/stochss/handlers/util/sciope_notebook.py b/stochss/handlers/util/sciope_notebook.py
index acaee8471c..63ec8d093a 100644
--- a/stochss/handlers/util/sciope_notebook.py
+++ b/stochss/handlers/util/sciope_notebook.py
@@ -1,6 +1,6 @@
'''
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -36,184 +36,216 @@ def __init__(self, path, new=False, models=None, settings=None):
Path to the notebook'''
super().__init__(path=path, new=new, models=models, settings=settings)
-
- @classmethod
- def __create_me_expres_cells(cls):
- # res conf cell
- param_conf = "met.data.configurations['listOfParameters'] = "
- param_conf += "list(model.listOfParameters.keys())"
- rconf_strs = ["# First lets add some appropiate information about the model and features",
- param_conf,
- "met.data.configurations['listOfSpecies'] = list(model.listOfSpecies.keys())",
- "met.data.configurations['listOfSummaries'] = met.summaries.features",
- "met.data.configurations['timepoints'] = model.tspan"]
- # met explore cell
- mtexp_strs = ["# Here we use UMAP for dimension reduction", "met.explore(dr_method='umap')"]
- # supervised train cell
- sptrn_strs = ["from sciope.models.label_propagation import LPModel",
- "# here lets use the dimension reduction embedding as input data",
- "data = met.dr_model.embedding_", "",
- "model_lp = LPModel()", "# train using basinhopping",
- "model_lp.train(data, met.data.user_labels, min_=0.01, max_=10, niter=50)"]
- # map label cell
- cmt_str = "# just to vislualize the result we will map the label distribution "
- cmt_str += "to the user_labels\n# (will enable us to see the LP model output "
- cmt_str += "when using method 'explore')"
- mplbl_strs = [cmt_str, "user_labels = np.copy(met.data.user_labels)",
- "# takes the label corresponding to index 0",
- "met.data.user_labels = model_lp.model.label_distributions_[:, 0]"]
- cells = [nbf.new_markdown_cell("## Explore the result"),
- nbf.new_code_cell("\n".join(rconf_strs)),
- nbf.new_code_cell("\n".join(mtexp_strs)),
- nbf.new_code_cell("\n".join(sptrn_strs)),
- nbf.new_code_cell("\n".join(mplbl_strs)),
- nbf.new_code_cell("met.explore(dr_method='umap')"),
- nbf.new_code_cell("met.data.user_labels = user_labels")]
- return cells
-
-
- def __create_me_setup_cells(self):
- spec_of_interest = list(self.model.get_all_species().keys())
- # Wrapper cell
- sim_str = "simulator = wrapper.get_simulator(gillespy_model=model, "
- sim_str += f"run_settings=settings, species_of_interest={spec_of_interest})"
- sim_strs = ["from sciope.utilities.gillespy2 import wrapper",
- "settings = configure_simulation()", sim_str,
- "expression_array = wrapper.get_parameter_expression_array(model)"]
- # Dask cell
- dask_strs = ["from dask.distributed import Client", "", "c = Client()"]
- # lhc cell
- lhc_str = "lhc = latin_hypercube_sampling.LatinHypercube("
- lhc_str += "xmin=expression_array, xmax=expression_array*3)"
- lhc_strs = ["from sciope.designs import latin_hypercube_sampling",
- "from sciope.utilities.summarystats.auto_tsfresh import SummariesTSFRESH", "",
- lhc_str, "lhc.generate_array(1000) # creates a LHD of size 1000", "",
- "# will use default minimal set of features",
- "summary_stats = SummariesTSFRESH()"]
- # stochmet cell
- ism_strs = ["from sciope.stochmet.stochmet import StochMET", "",
- "met = StochMET(simulator, lhc, summary_stats)"]
- cells = [nbf.new_markdown_cell("## Define simulator function (using gillespy2 wrapper)"),
- nbf.new_code_cell("\n".join(sim_strs)),
- nbf.new_markdown_cell("## Start local cluster using dask client"),
- nbf.new_code_cell("\n".join(dask_strs)),
- nbf.new_markdown_cell("## Define parameter sampler/design and summary statistics"),
- nbf.new_code_cell("\n".join(lhc_strs)),
- nbf.new_markdown_cell("## Initiate StochMET"),
- nbf.new_code_cell("\n".join(ism_strs))]
- return cells
-
-
- def __create_mi_setup_cells(self):
- pad = " "
- priors = ["from sciope.utilities.priors import uniform_prior", "",
- "# take default from mode 1 as reference",
- "default_param = np.array(list(model.listOfParameters.items()))[:, 1]",
- "", "bound = []", "for exp in default_param:",
- f"{pad}bound.append(float(exp.expression))", "", "# Set the bounds",
- "bound = np.array(bound)", "dmin = bound * 0.1", "dmax = bound * 2.0",
- "", "# Here we use uniform prior",
- "uni_prior = uniform_prior.UniformPrior(dmin, dmax)"]
- stat_dist = ["from sciope.utilities.summarystats import auto_tsfresh",
- "from sciope.utilities.distancefunctions import naive_squared", "",
- "# Function to generate summary statistics",
- "summ_func = auto_tsfresh.SummariesTSFRESH()", "",
- "# Distance", "ns = naive_squared.NaiveSquaredDistance()"]
- cells = [nbf.new_markdown_cell("## Define prior distribution"),
- nbf.new_code_cell("\n".join(priors)),
- nbf.new_markdown_cell("## Define simulator"),
- self.__create_mi_simulator_cell(),
- nbf.new_markdown_cell("## Define summary statistics and distance function"),
- nbf.new_code_cell("\n".join(stat_dist))]
- return cells
-
-
- def __create_mi_simulator_cell(self):
- pad = " "
- comment = f"{pad}# params - array, need to have the same order as model.listOfParameters"
- loop = f"{pad}for e, pname in enumerate(model.listOfParameters.keys()):"
- if self.settings['solver'] == "SSACSolver":
- comment += "\n"+ pad +"variables = {}"
- func_def = "def get_variables(params, model):"
- body = f"{pad*2}variables[pname] = params[e]"
- return_str = f"{pad}return variables"
- call = f"{pad}variables = get_variables(params, model)"
- run = f"{pad}res = model.run(**kwargs, variables=variables)"
+ def __create_abc_cells(self, cells):
+ pad = ' '
+ cell1 = [
+ "abc = ABC(", f"{pad}fixed_data, sim=simulator2, prior_function=uni_prior,",
+ f"{pad}summaries_function=summ_func.compute, distance_function=ns", ")"
+ ]
+ cells.insert(27, nbf.new_code_cell("\n".join(cell1)))
+ cells.append(nbf.new_code_cell("abc.compute_fixed_mean(chunk_size=2)"))
+ cells.append(nbf.new_code_cell(
+ "res = abc.infer(num_samples=100, batch_size=10, chunk_size=2)"
+ ))
+ cells.append(nbf.new_code_cell(
+ "mae_inference = mean_absolute_error(bound, abc.results['inferred_parameters'])"
+ ))
+
+ def __create_explore_cells(self, cells):
+ cell1 = [
+ "met.data.configurations['listOfParameters'] = list(model.listOfParameters.keys())",
+ "met.data.configurations['listOfSpecies'] = list(model.listOfSpecies.keys())",
+ "met.data.configurations['listOfSummaries'] = met.summaries.features",
+ "met.data.configurations['timepoints'] = model.tspan",
+ ]
+ cell3 = [
+ "data = met.dr_model.embedding_", "",
+ "model_lp = LPModel()", "# train using basinhopping",
+ "model_lp.train(data, met.data.user_labels, min_=0.01, max_=10, niter=50)"
+ ]
+ cell4 = [
+ "user_labels = numpy.copy(met.data.user_labels)",
+ "# takes the label corresponding to index 0",
+ "met.data.user_labels = model_lp.model.label_distributions_[:, 0]"
+ ]
+ cells.insert(21, nbf.new_code_cell("\n".join(cell1)))
+ cells.insert(23, nbf.new_code_cell("met.explore(dr_method='umap')"))
+ cells.insert(25, nbf.new_code_cell("\n".join(cell3)))
+ cells.insert(27, nbf.new_code_cell("\n".join(cell4)))
+ cells.insert(28, nbf.new_code_cell("met.explore(dr_method='umap')"))
+ cells.insert(29, nbf.new_code_cell("met.data.user_labels = user_labels"))
+
+ def __create_import_cells(self, cells):
+ base_imports = ["import numpy", "from dask.distributed import Client"]
+ if self.nb_type == self.MODEL_INFERENCE:
+ base_imports.insert(
+ 1, "from tsfresh.feature_extraction.settings import MinimalFCParameters"
+ )
+ base_imports.append("from sklearn.metrics import mean_absolute_error")
+ sciope_imports = [
+ "from sciope.utilities.priors import uniform_prior",
+ "from sciope.utilities.summarystats import auto_tsfresh",
+ "from sciope.utilities.distancefunctions import naive_squared",
+ "from sciope.inference.abc_inference import ABC"
+ ]
else:
- func_def = "def set_model_parameters(params, model):"
- body = f"{pad*2}model.get_parameter(pname).expression = params[e]"
- return_str = f"{pad}return model"
- call = f"{pad}model_update = set_model_parameters(params, model)"
- run = f"{pad}res = model_update.run(**kwargs)"
- sim_strs = [func_def, comment, loop, body, return_str, ""]
- simulator = ["# Here we use the GillesPy2 Solver", "def simulator(params, model):",
- call, "", run, f"{pad}res = res.to_array()",
- f"{pad}tot_res = np.asarray([x.T for x in res]) # reshape to (N, S, T)",
- f"{pad}# should not contain timepoints", f"{pad}tot_res = tot_res[:, 1:, :]",
- "", f"{pad}return tot_res", ""]
- sim_strs.extend(simulator)
- sim2_com = "# Wrapper, simulator function to abc should should only take one argument "
- sim2_com += "(the parameter point)"
- simulator2 = [sim2_com, "def simulator2(x):", f"{pad}return simulator(x, model=model)"]
- sim_strs.extend(simulator2)
- return nbf.new_code_cell("\n".join(sim_strs))
-
-
- def create_me_notebook(self):
- '''Create a model exploration jupiter notebook for a StochSS model/workflow
-
- Attributes
- ----------'''
+ sciope_imports = [
+ "from sciope.utilities.gillespy2 import wrapper",
+ "from sciope.designs import latin_hypercube_sampling",
+ "from sciope.utilities.summarystats.auto_tsfresh import SummariesTSFRESH",
+ "from sciope.stochmet.stochmet import StochMET",
+ "from sciope.models.label_propagation import LPModel"
+ ]
+ cells.insert(1, nbf.new_code_cell("\n".join(base_imports)))
+ cells.insert(3, nbf.new_code_cell("\n".join(sciope_imports)))
+
+ def __create_me_header_cells(self, cells):
+ header1 = "***\n## Model Exploration\n***\n" + \
+ "### Define simulator function (using gillespy2 wrapper)"
+ header6 = "***\n## Explore the result\n***\n" + \
+ "First lets add some appropiate information about the model and features"
+ header9 = "Just to vislualize the result we will map the " + \
+ "label distribution to the user_labels " + \
+ "(will enable us to see the LP model output when using method 'explore')"
+ cells.extend([
+ nbf.new_markdown_cell(header1),
+ nbf.new_markdown_cell("### Start local cluster using dask client"),
+ nbf.new_markdown_cell("### Define parameter sampler/design and summary statistics"),
+ nbf.new_markdown_cell("### Initiate StochMET"),
+ nbf.new_markdown_cell("***\n## Run parameter sweep\n***"),
+ nbf.new_markdown_cell(header6),
+ nbf.new_markdown_cell("Here we use UMAP for dimension reduction"),
+ nbf.new_markdown_cell("Here lets use the dimension reduction embedding as input data"),
+ nbf.new_markdown_cell(header9)
+ ])
+
+ def __create_mi_header_cells(self, cells):
+ header1 = "***\n## Model Inference\n***\n### Generate some fixed" + \
+ "(observed) data based on default parameters of the model"
+ header6 = "Wrapper, simulator function to abc should " + \
+ "should only take one argument (the parameter point)"
+ header7 = "### Define summary statistics and distance function" + \
+ "\nFunction to generate summary statistics"
+ cells.extend([
+ nbf.new_markdown_cell(header1),
+ nbf.new_markdown_cell("Reshape the data and remove timepoints array"),
+ nbf.new_markdown_cell(
+ "### Define prior distribution\nTake default from mode 1 as reference"
+ ),
+ nbf.new_markdown_cell("### Define simulator"),
+ nbf.new_markdown_cell("Here we use the GillesPy2 Solver"),
+ nbf.new_markdown_cell(header6),
+ nbf.new_markdown_cell(header7),
+ nbf.new_markdown_cell("### Start local cluster using dask client"),
+ nbf.new_markdown_cell("***\n## Run the abc instance\n***"),
+ nbf.new_markdown_cell("First compute the fixed(observed) mean")
+ ])
+
+ def __create_observed_data(self, cells):
+ cell2 = [
+ "fixed_data = fixed_data.to_array()",
+ "fixed_data = numpy.asarray([x.T for x in fixed_data])",
+ "fixed_data = fixed_data[:, 1:, :]"
+ ]
+ cells.insert(11, nbf.new_code_cell(
+ "kwargs = configure_simulation()\nfixed_data = model.run(**kwargs)"
+ ))
+ cells.insert(13, nbf.new_code_cell("\n".join(cell2)))
+
+ def __create_prior(self):
+ nb_prior = [
+ "default_param = numpy.array(list(model.listOfParameters.items()))[:, 1]", "",
+ "bound = []", "for exp in default_param:", " bound.append(float(exp.expression))",
+ "", "# Set the bounds", "bound = numpy.array(bound)", "dmin = bound * 0.1",
+ "dmax = bound * 2.0", "", "# Here we use uniform prior",
+ "uni_prior = uniform_prior.UniformPrior(dmin, dmax)"
+ ]
+ return nbf.new_code_cell("\n".join(nb_prior))
+
+ def __create_simulator_cells(self, cells):
+ pad = ' '
+ cell1 = [
+ "def get_variables(params, model):",
+ f"{pad}# params - array, need to have the same order as model.listOfParameters",
+ f"{pad}variables = " + r"{}",
+ f"{pad}for e, pname in enumerate(model.listOfParameters.keys()):",
+ f"{pad*2}variables[pname] = params[e]", f"{pad}return variables"
+ ]
+ cell2 = [
+ "def simulator(params, model):", f"{pad}variables = get_variables(params, model)", "",
+ f"{pad}res = model.run(**kwargs, variables=variables)", f"{pad}res = res.to_array()",
+ f"{pad}tot_res = numpy.asarray([x.T for x in res]) # reshape to (N, S, T)",
+ f"{pad}# should not contain timepoints", f"{pad}tot_res = tot_res[:, 1:, :]", "",
+ f"{pad}return tot_res"
+ ]
+ cells.insert(16, nbf.new_code_cell("\n".join(cell1)))
+ cells.insert(18, nbf.new_code_cell("\n".join(cell2)))
+ cells.insert(20, nbf.new_code_cell(
+ f"def simulator2(x):\n{pad}return simulator(x, model=model)"
+ ))
+
+ def __create_simulator_func(self):
+ nb_sim_func = [
+ "settings = configure_simulation()", "simulator = wrapper.get_simulator(",
+ " gillespy_model=model, run_settings=settings, species_of_interest=['U', 'V']",
+ ")", "expression_array = wrapper.get_parameter_expression_array(model)"
+ ]
+ return nbf.new_code_cell("\n".join(nb_sim_func))
+
+ def __create_summary_stats(self):
+ nb_sum_stats = [
+ "lhc = latin_hypercube_sampling.LatinHypercube(",
+ " xmin=expression_array, xmax=expression_array*3", ")",
+ "lhc.generate_array(1000) # creates a LHD of size 1000", "",
+ "# will use default minimal set of features", "summary_stats = SummariesTSFRESH()"
+ ]
+ return nbf.new_code_cell("\n".join(nb_sum_stats))
+
+ def create_me_notebook(self, results=None, compute="StochSS"):
+ '''Create a model exploration jupiter notebook for a StochSS model/workflow.'''
self.nb_type = self.MODEL_EXPLORATION
+ cells = self.create_common_cells()
+ self.__create_import_cells(cells)
+ self.__create_me_header_cells(cells)
+
self.settings['solver'] = self.get_gillespy2_solver_name()
- cells = [nbf.new_code_cell("%matplotlib notebook")]
- cells.extend(self.create_common_cells())
- cells.append(nbf.new_markdown_cell("# Model Exploration"))
- cells.extend(self.__create_me_setup_cells())
- cells.extend([nbf.new_markdown_cell("## Run parameter sweep"),
- nbf.new_code_cell("met.compute(n_points=500, chunk_size=10)")])
- cells.extend(self.__create_me_expres_cells())
+ cells.insert(11, self.__create_simulator_func())
+ cells.insert(13, nbf.new_code_cell("c = Client()\nc"))
+ cells.insert(15, self.__create_summary_stats())
+ cells.insert(17, nbf.new_code_cell("met = StochMET(simulator, lhc, summary_stats)"))
+ cells.insert(19, nbf.new_code_cell("met.compute(n_points=500, chunk_size=10)"))
+ self.__create_explore_cells(cells)
+ if compute != "StochSS":
+ self.log(
+ "warning",
+ "AWS Cloud compute environment is not supported by SCIOPE model exploration workflows."
+ )
message = self.write_notebook_file(cells=cells)
return {"Message":message, "FilePath":self.get_path(), "File":self.get_file()}
-
- def create_mi_notebook(self):
- '''Create a model inference jupiter notebook for a StochSS model/workflow
-
- Attributes
- ----------'''
+ def create_mi_notebook(self, results=None, compute="StochSS"):
+ '''Create a model inference jupiter notebook for a StochSS model/workflow.'''
self.nb_type = self.MODEL_INFERENCE
+ cells = self.create_common_cells()
+ self.__create_import_cells(cells)
+ self.__create_mi_header_cells(cells)
+
self.settings['solver'] = self.get_gillespy2_solver_name()
- cells = [nbf.new_code_cell("%load_ext autoreload\n%autoreload 2")]
- cells.extend(self.create_common_cells())
- imports = "from tsfresh.feature_extraction.settings import MinimalFCParameters"
- fd_header = "## Generate some fixed(observed) data based on default parameters of the model"
- fd_str = "kwargs = configure_simulation()\nfixed_data = model.run(**kwargs)"
- rshp_strs = ["# Reshape the data and remove timepoints array",
- "fixed_data = fixed_data.to_array()",
- "fixed_data = np.asarray([x.T for x in fixed_data])",
- "fixed_data = fixed_data[:, 1:, :]"]
- cells.extend([nbf.new_markdown_cell("# Model Inference"),
- nbf.new_code_cell(imports), nbf.new_markdown_cell(fd_header),
- nbf.new_code_cell(fd_str), nbf.new_code_cell("\n".join(rshp_strs))])
- cells.extend(self.__create_mi_setup_cells())
- # abc cell
- abc_str = "from sciope.inference.abc_inference import ABC\n\n"
- abc_str += "abc = ABC(fixed_data, sim=simulator2, prior_function=uni_prior, "
- abc_str += "summaries_function=summ_func.compute, distance_function=ns)"
- # compute fixed mean cell
- fm_str = "# First compute the fixed(observed) mean\nabc.compute_fixed_mean(chunk_size=2)"
- # run model inference cell
- rmi_str = "res = abc.infer(num_samples=100, batch_size=10, chunk_size=2)"
- # absolute error cell
- abse_str = "from sklearn.metrics import mean_absolute_error\n\n"
- abse_str += "mae_inference = mean_absolute_error(bound, abc.results['inferred_parameters'])"
- cells.extend([nbf.new_markdown_cell("## Start local cluster using dask client"),
- nbf.new_code_cell("from dask.distributed import Client\n\nc = Client()"),
- nbf.new_markdown_cell("## Start abc instance"),
- nbf.new_code_cell(abc_str), nbf.new_code_cell(fm_str),
- nbf.new_code_cell(rmi_str), nbf.new_code_cell(abse_str)])
+ self.__create_observed_data(cells)
+ self.__create_simulator_cells(cells)
+ cells.insert(15, self.__create_prior())
+ cells.insert(23, nbf.new_code_cell(
+ "summ_func = auto_tsfresh.SummariesTSFRESH()\n\n" + \
+ "# Distance\nns = naive_squared.NaiveSquaredDistance()"
+ ))
+ cells.insert(25, nbf.new_code_cell("c = Client()\nc"))
+ self.__create_abc_cells(cells)
+ if compute != "StochSS":
+ self.log(
+ "warning",
+ "AWS Cloud compute environment is not supported by SCIOPE model inference workflows."
+ )
message = self.write_notebook_file(cells=cells)
return {"Message":message, "FilePath":self.get_path(), "File":self.get_file()}
diff --git a/stochss/handlers/util/scripts/aws_compute.py b/stochss/handlers/util/scripts/aws_compute.py
new file mode 100755
index 0000000000..063958dde5
--- /dev/null
+++ b/stochss/handlers/util/scripts/aws_compute.py
@@ -0,0 +1,119 @@
+#!/usr/bin/env python3
+
+'''
+StochSS is a platform for simulating biochemical systems
+Copyright (C) 2019-2022 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
+import sys
+import logging
+import argparse
+
+import fcntl
+
+sys.path.append("/stochss/stochss/") # pylint: disable=wrong-import-position
+sys.path.append("/stochss/stochss/handlers/") # pylint: disable=wrong-import-position
+from util.stochss_base import StochSSBase
+from handlers.log import init_log
+
+init_log()
+log = logging.getLogger("stochss")
+
+class OpenAndLock:
+ '''
+ Wrapper for open that controls the file lock.
+ '''
+ def __init__(self, path, *pargs, **kwargs):
+ self.file = open(path,*pargs, **kwargs)
+ if self.file.writable():
+ fcntl.lockf(self.file, fcntl.LOCK_EX | fcntl.LOCK_NB)
+ print("File is locked")
+ self.file.write(str(os.getpid()))
+
+ def __enter__(self, *pargs, **kwargs):
+ return self.file
+
+ def __exit__(self, exc_type=None, exc_value=None, traceback=None):
+ self.file.flush()
+ os.fsync(self.file.fileno())
+ if self.file.writable():
+ fcntl.lockf(self.file, fcntl.LOCK_UN)
+ print("File is un-locked")
+ self.file.close()
+ return exc_type is None
+
+ def close(self, **kwargs):
+ '''
+ Un-lock and close the file.
+ '''
+ self.__exit__(**kwargs)
+
+def get_parsed_args():
+ '''
+ Initializes an argpaser to document this script and returns a dict of
+ the arguments that were passed to the script from the command line.
+ '''
+ description = "Launch, terminate, or get the status of an AWS instance."
+ parser = argparse.ArgumentParser(description=description)
+ parser.add_argument('-l', '--launch', action="store_true", help="Launch an AWS instance.")
+ parser.add_argument(
+ '-s', '--status', action="store_true", help="Get the status of an AWS instance."
+ )
+ parser.add_argument(
+ '-v', '--verbose', action="store_true", help="Print results as they are stored."
+ )
+ parser.add_argument('-t', '--terminate', action="store_true", help="Terminate an AWS instance.")
+ return parser.parse_args()
+
+def interact(launch=False, status=False, terminate=False):
+ '''
+ Interact with the aws instance.
+
+ Attributes
+ ----------
+ launch : bool
+ Indicates a launch interaction.
+ status : bool
+ Indicates a status update interaction.
+ termimate : bool
+ Indicates a terminate interaction.
+ '''
+ base = StochSSBase(path="")
+ l_path = os.path.join(base.user_dir, '.aws/awsec2.lock')
+
+ try:
+ lock_fd = OpenAndLock(l_path, 'w', encoding="utf-8")
+ except BlockingIOError:
+ print("The file is already locked")
+ return
+
+ if launch:
+ base.launch_aws_cluster()
+ elif status:
+ _ = base.get_aws_cluster()
+ elif terminate:
+ base.terminate_aws_cluster()
+ else:
+ raise Exception("No operation provided, please set -l, -s, or -t flags.")
+ lock_fd.close()
+
+if __name__ == "__main__":
+ pid = os.fork()
+ if pid > 0:
+ sys.exit()
+
+ args = get_parsed_args()
+ interact(launch=args.launch, status=args.status, terminate=args.terminate)
diff --git a/stochss/handlers/util/scripts/run_preview.py b/stochss/handlers/util/scripts/run_preview.py
index a5d4d97237..f6eb1d6560 100755
--- a/stochss/handlers/util/scripts/run_preview.py
+++ b/stochss/handlers/util/scripts/run_preview.py
@@ -2,7 +2,7 @@
'''
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -90,7 +90,7 @@ def run_spatialpy_preview(args):
'''
model = StochSSSpatialModel(path=args.path)
job = SpatialSimulation(path="", preview=True, target=args.target)
- job.s_py_model = model.convert_to_spatialpy()
+ job.s_py_model = model.convert_to_spatialpy(include_model_settings=True)
job.s_model = model.model
return job.run(preview=True)
diff --git a/stochss/handlers/util/scripts/start_job.py b/stochss/handlers/util/scripts/start_job.py
index 1c11f9ff26..008f942f09 100755
--- a/stochss/handlers/util/scripts/start_job.py
+++ b/stochss/handlers/util/scripts/start_job.py
@@ -2,7 +2,7 @@
'''
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/stochss/handlers/util/scripts/upload_remote_file.py b/stochss/handlers/util/scripts/upload_remote_file.py
index c7c3e2aa5a..ce8d33c6c4 100755
--- a/stochss/handlers/util/scripts/upload_remote_file.py
+++ b/stochss/handlers/util/scripts/upload_remote_file.py
@@ -2,7 +2,7 @@
'''
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/stochss/handlers/util/spatial_simulation.py b/stochss/handlers/util/spatial_simulation.py
index 299b76a960..cff26bb072 100644
--- a/stochss/handlers/util/spatial_simulation.py
+++ b/stochss/handlers/util/spatial_simulation.py
@@ -1,6 +1,6 @@
'''
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/stochss/handlers/util/stochss_base.py b/stochss/handlers/util/stochss_base.py
index 6b09a3adee..eea23a2103 100644
--- a/stochss/handlers/util/stochss_base.py
+++ b/stochss/handlers/util/stochss_base.py
@@ -1,6 +1,6 @@
'''
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -22,9 +22,14 @@
import shutil
import datetime
import traceback
+import subprocess
+import dotenv
import requests
+from stochss_compute.cloud import EC2Cluster, EC2LocalConfig
+from stochss_compute.cloud.exceptions import EC2Exception
+
from .stochss_errors import StochSSFileNotFoundError, StochSSPermissionsError, \
FileNotJSONFormatError
@@ -36,6 +41,7 @@ class StochSSBase():
'''
user_dir = os.path.expanduser("~") # returns the path to the users home directory
TEMPLATE_VERSION = 1
+ DOMAIN_TEMPLATE_VERSION = 2
def __init__(self, path):
'''
@@ -50,37 +56,63 @@ def __init__(self, path):
self.logs = []
def __build_example_html(self, exm_data, home):
- row = "__CONTENTS__
"
- entry = "__ALERT__' href='__OPEN_LINK__' role='button' style='width: 100%'>__NAME__"
- entry_a = f"__COLUMNS__
"
# Well Mixed Examples
wm_list = []
for entry in exm_data['Well-Mixed']:
- exm_a = entry_a.replace("__ALERT__", entry['alert'])
- exm_a = exm_a.replace("__OPEN_LINK__", f"{home}?open={entry['open_link']}")
- exm_a = exm_a.replace("__NAME__", entry['name'])
- wm_list.append(f"{exm_a}
")
- well_mixed = row.replace("__CONTENTS__", ''.join(wm_list))
+ columns = self.__build_html_entry(entry, home)
+ wm_list.append(row.replace("__COLUMNS__", columns))
+ well_mixed = section.replace("__ROWS__", "
".join(wm_list))
# Spatial Examples
s_list = []
for entry in exm_data['Spatial']:
- exm_a = entry_a.replace("__ALERT__", entry['alert'])
- exm_a = exm_a.replace("__OPEN_LINK__", f"{home}?open={entry['open_link']}")
- exm_a = exm_a.replace("__NAME__", entry['name'])
- s_list.append(f"{exm_a}
")
- spatial = row.replace("__CONTENTS__", ''.join(s_list))
+ columns = self.__build_html_entry(entry, home)
+ s_list.append(row.replace("__COLUMNS__", columns))
+ spatial = section.replace("__ROWS__", "
".join(s_list))
return {"wellMixed": well_mixed, "spatial": spatial}
- def __get_entry(self, entries, name):
+ @classmethod
+ def __build_html_entry(cls, entry, home):
+ desc = entry['description'] if 'description' in entry else "TODO"
+
+ nav_alert = "secondary" if entry['alert'] == "success" else entry['alert']
+ nav_status = "" if entry['alert'] == "primary" else " disabled"
+ nav_link = f"/stochss/project/manager?path=Examples/{entry['name']}.proj"
+ nav_title = "Error!" if entry['alert'] == "danger" else "Open"
+ nav_classes = f"class='btn full-btn btn-outline-{nav_alert} box-shadow{nav_status}'"
+ nav_a = f"{nav_title}"
+
+ imp_alert = "success" if entry['alert'] == "danger" else entry['alert']
+ imp_link = f"{home}?open={entry['open_link']}"
+ imp_title = "Update" if entry['alert'] == "primary" else "Import"
+ imp_classes = f"class='btn full-btn btn-outline-{imp_alert} box-shadow'"
+ imp_a = f"{imp_title}"
+
+ mod_label = "Added" if entry['alert'] == "success" else "Last Updated"
+ mod_date = entry['mod_time'] if entry['mod_time'] is not None else "TODO"
+
+ return "".join([
+ f"{entry['name']}
"
+ f"",
+ "",
+ f"
{nav_a}
",
+ f"
{imp_a}
",
+ f"
{mod_label}: {mod_date}
"
+ ])
+
+ @classmethod
+ def __get_entry(cls, entries, name):
for entry in entries:
if entry['name'] == name:
return entry
return None
- def __get_from_remote(self):
+ @classmethod
+ def __get_from_remote(cls):
p_path = "/stochss/.proxies.txt"
rel_path = "https://raw.githubusercontent.com/StochSS/StochSS_Example_Library/main/example_data.json"
if os.path.exists(p_path):
@@ -103,12 +135,16 @@ def __update_exm_data(self, exm_data, old_exm_data=None):
entry['alert'] = "success"
if old_exm_data is not None:
old_entry = self.__get_entry(old_exm_data['Well-Mixed'], entry['name'])
+ if old_entry is None:
+ continue
entry['mod_time'] = old_entry['mod_time']
entry['umd5_sum'] = old_entry['umd5_sum']
for entry in exm_data['Spatial']:
entry['alert'] = "success"
if old_exm_data is not None:
old_entry = self.__get_entry(old_exm_data['Spatial'], entry['name'])
+ if old_entry is None:
+ continue
entry['mod_time'] = old_entry['mod_time']
entry['umd5_sum'] = old_entry['umd5_sum']
@@ -137,7 +173,12 @@ def __update_umd5_sums(self, exm_data, files=None):
entry = self.__get_entry(
exm_data['Spatial'], exm_data['Name-Mappings'][example]
)
- entry['alert'] = "primary"
+ if entry is None:
+ continue
+ if example == "Example SIR Epidemic Project.proj":
+ entry['alert'] = "danger"
+ else:
+ entry['alert'] = "primary"
def add_presentation_name(self, file, name):
'''
@@ -219,6 +260,29 @@ def delete_presentation_name(self, file):
json.dump(names, names_file)
+ def get_aws_cluster(self, instance=None):
+ '''
+ Get the AWS cluster.
+
+ Attributes
+ ----------
+ instance : str
+ AWS EC2 instance.
+ '''
+ key_dir = os.path.join(self.user_dir, ".aws")
+ if instance is None:
+ path = os.path.join(self.user_dir, ".user-settings.json")
+ settings = self.load_user_settings(path=path)
+ instance = settings['headNode']
+ s_path = os.path.join(key_dir, f"{instance.replace('.', '-')}-status.txt")
+ # Setup the AWS environment
+ env_path = os.path.join(key_dir, "awsec2.env")
+ dotenv.load_dotenv(dotenv_path=env_path)
+ # Configure the AWS cluster
+ local_config = EC2LocalConfig(key_dir=key_dir, status_file=s_path)
+ cluster = EC2Cluster(local_config=local_config)
+ return cluster
+
@classmethod
def get_new_path(cls, dst_path):
'''
@@ -262,7 +326,7 @@ def get_model_template(self, as_string=False):
as_string : bool
Indicates whether or not to return the template in string format
'''
- path = '/stochss/stochss_templates/nonSpatialModelTemplate.json'
+ path = '/stochss/stochss_templates/modelTemplate.json'
self.log("debug", f"Using model template at: {path}")
try:
with open(path, 'r', encoding="utf-8") as template:
@@ -440,6 +504,21 @@ def get_unique_copy_path(self, path=None):
return os.path.join(dirname, cp_file)
+ def launch_aws_cluster(self):
+ '''
+ Launch an AWS instance.
+ '''
+ settings = self.load_user_settings(path='.user-settings.json')
+ instance = settings['headNode']
+
+ try:
+ cluster = self.get_aws_cluster(instance=instance)
+ cluster.launch_single_node_instance(instance)
+ except EC2Exception:
+ pass
+ except Exception:
+ cluster.clean_up()
+
def load_example_library(self, home):
'''
Load the example library dropdown list.
@@ -463,6 +542,33 @@ def load_example_library(self, home):
return self.__build_example_html(exm_data, home)
+ def load_user_settings(self, path=None):
+ '''
+ Load the user settings from file.
+
+ Attributes
+ ----------
+ path : str
+ Absolute path to the user settings file.
+ '''
+ if path is None and os.path.exists(self.path):
+ path = self.path
+ elif path is None or not os.path.exists(path):
+ path = "/stochss/stochss_templates/userSettingTemplate.json"
+ with open(path, "r", encoding="utf-8") as usrs_fd:
+ settings = json.load(usrs_fd)
+ settings['awsHeadNodeStatus'] = "not launched"
+ if os.path.exists(os.path.join(self.user_dir, ".aws/awsec2.env")):
+ settings['awsSecretKey'] = "*"*20
+ i_id = settings['headNode'].replace('.', '-')
+ s_path = os.path.join(self.user_dir, f".aws/{i_id}-status.txt")
+ if os.path.exists(s_path):
+ with open(s_path, 'r', encoding="utf-8") as aws_s_fd:
+ settings['awsHeadNodeStatus'] = aws_s_fd.read().strip()
+ else:
+ settings['awsSecretKey'] = None
+ return settings
+
def log(self, level, message):
'''
Add a log to the objects internal logs
@@ -532,5 +638,30 @@ def rename(self, name):
message = f"Could not find the file or directory: {str(err)}"
raise StochSSFileNotFoundError(message, traceback.format_exc()) from err
except PermissionError as err:
- message = f"You don not have permission to rename this file or directory: {str(err)}"
+ message = f"You do not have permission to rename this file or directory: {str(err)}"
raise StochSSPermissionsError(message, traceback.format_exc()) from err
+
+ def terminate_aws_cluster(self):
+ '''
+ Terminate an AWS instance.
+ '''
+ cluster = self.get_aws_cluster()
+ cluster.clean_up()
+
+ def update_aws_status(self, instance):
+ '''
+ Updated the status of the aws instance.
+
+ Attributes
+ ----------
+ instance : str
+ The AWS instance.
+ '''
+ s_path = os.path.join(self.user_dir, f".aws/{instance.replace('.', '-')}-status.txt")
+ if not os.path.exists(s_path):
+ return
+
+ script = "/stochss/stochss/handlers/util/scripts/aws_compute.py"
+ exec_cmd = [f"{script}", "-sv"]
+ print("Updating the status of AWS")
+ process = subprocess.Popen(exec_cmd)
diff --git a/stochss/handlers/util/stochss_errors.py b/stochss/handlers/util/stochss_errors.py
index cd2c1fb36c..48679844d8 100644
--- a/stochss/handlers/util/stochss_errors.py
+++ b/stochss/handlers/util/stochss_errors.py
@@ -1,6 +1,6 @@
'''
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -39,6 +39,23 @@ def report_error(handler, log, err):
error['Traceback'] = trace
handler.write(error)
+def report_critical_error(handler, log, err):
+ '''
+ Report a critical stochss error to the front end.
+
+ Attributes
+ ----------
+ handler : obj
+ Jupyter Notebook API Handler
+ log : obj
+ StochSS log
+ '''
+ handler.set_status(500)
+ error = {"Reason":"Uncaught Critical Error", "Message":str(err)}
+ trace = traceback.format_exc()
+ log.error("Exception information: %s\n%s", error, trace)
+ error['Traceback'] = trace
+ handler.write(error)
class StochSSAPIError(Exception):
'''
@@ -228,16 +245,38 @@ def __init__(self, msg, trace=None):
super().__init__(406, "Domain File Not In Proper Format", msg, trace)
-class DomainGeometryError(StochSSAPIError):
+class DomainUpdateError(StochSSAPIError):
+ '''
+ ################################################################################################
+ Domain File Can't Be Updated
+ ################################################################################################
+ '''
+
+ def __init__(self, msg, trace=None):
+ '''
+ Indicates that the domain file can't be updated as it may be a
+ dependency of another doamin or a spatial model.
+
+ Attributes
+ ----------
+ msg : str
+ Details on what caused the error
+ trace : str
+ Error traceback for the error
+ '''
+ super().__init__(405, "Domain File Can't Be Updated.", msg, trace)
+
+
+class DomainActionError(StochSSAPIError):
'''
################################################################################################
- Domain Geometry Action Failed
+ Domain Action Failed to Initialize
################################################################################################
'''
def __init__(self, msg, trace=None):
'''
- Indicates that a geometry action for the a domain failed
+ Indicates that an action for the a domain failed.
Attributes
----------
@@ -246,7 +285,49 @@ def __init__(self, msg, trace=None):
trace : str
Error traceback for the error
'''
- super().__init__(406, "Domain Geometry Action Failed", msg, trace)
+ super().__init__(406, "Domain Action Failed", msg, trace)
+
+
+class DomainShapeError(StochSSAPIError):
+ '''
+ ################################################################################################
+ Domain Shape Failed to Initialize
+ ################################################################################################
+ '''
+
+ def __init__(self, msg, trace=None):
+ '''
+ Indicates that a shape failed to initialize.
+
+ Attributes
+ ----------
+ msg : str
+ Details on what caused the error
+ trace : str
+ Error traceback for the error
+ '''
+ super().__init__(406, "Domain Shape Failed", msg, trace)
+
+
+class DomainTransformationError(StochSSAPIError):
+ '''
+ ################################################################################################
+ Domain Transformation Failed to Initialize
+ ################################################################################################
+ '''
+
+ def __init__(self, msg, trace=None):
+ '''
+ Indicates that a transformation failed to initialize.
+
+ Attributes
+ ----------
+ msg : str
+ Details on what caused the error
+ trace : str
+ Error traceback for the error
+ '''
+ super().__init__(406, "Domain Transformations Failed", msg, trace)
####################################################################################################
# Job Errors
@@ -334,3 +415,68 @@ def __init__(self, msg, trace=None):
Error traceback for the error
'''
super().__init__(500, "Job Results Error", msg, trace)
+
+
+####################################################################################################
+# AWS Errors
+####################################################################################################
+
+class AWSConfigurationError(StochSSAPIError):
+ '''
+ ################################################################################################
+ AWS Configuration Error
+ ################################################################################################
+ '''
+
+ def __init__(self, msg, trace=None):
+ '''
+ Indicates that StochSS Compute configured to run jobs.
+
+ Attributes
+ ----------
+ msg : str
+ Details on what caused the error
+ trace : str
+ Error traceback for the error
+ '''
+ super().__init__(403, "AWS Configuration Error", msg, trace)
+
+class AWSLauncherError(StochSSAPIError):
+ '''
+ ################################################################################################
+ AWS Launcher Error
+ ################################################################################################
+ '''
+
+ def __init__(self, msg, trace=None):
+ '''
+ Indicates that the StochSS Compute launcher single node instance failed.
+
+ Attributes
+ ----------
+ msg : str
+ Details on what caused the error
+ trace : str
+ Error traceback for the error
+ '''
+ super().__init__(403, "AWS Launch Error", msg, trace)
+
+class AWSTerminatorError(StochSSAPIError):
+ '''
+ ################################################################################################
+ AWS Terminator Error
+ ################################################################################################
+ '''
+
+ def __init__(self, msg, trace=None):
+ '''
+ Indicates that the StochSS Compute clean up failed.
+
+ Attributes
+ ----------
+ msg : str
+ Details on what caused the error
+ trace : str
+ Error traceback for the error
+ '''
+ super().__init__(403, "AWS Terminate Error", msg, trace)
diff --git a/stochss/handlers/util/stochss_file.py b/stochss/handlers/util/stochss_file.py
index f96fe377b9..b57594c8f3 100644
--- a/stochss/handlers/util/stochss_file.py
+++ b/stochss/handlers/util/stochss_file.py
@@ -1,6 +1,6 @@
'''
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/stochss/handlers/util/stochss_folder.py b/stochss/handlers/util/stochss_folder.py
index 65275c76f3..c56d4bee52 100644
--- a/stochss/handlers/util/stochss_folder.py
+++ b/stochss/handlers/util/stochss_folder.py
@@ -1,6 +1,6 @@
'''
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -81,8 +81,10 @@ def __get_file_from_link(self, remote_path):
response = requests.get(remote_path, allow_redirects=True, proxies=proxies)
body = response.content
if "download_presentation" in remote_path:
- if ext in ("mdl", "smdl"):
+ if ext =="mdl":
file = f"{json.loads(body)['name']}.{ext}"
+ elif ext == "smdl":
+ file = f"{json.loads(body)['model']['name']}.{ext}"
elif ext == "ipynb":
file = json.loads(body)['file']
body = json.dumps(json.loads(body)['notebook'])
@@ -199,7 +201,7 @@ def __overwrite(self, path, ext, body):
zip_file.write(body)
try:
with zipfile.ZipFile(ext_path, 'r') as zip_file:
- members = set([name.split('/')[0] for name in zip_file.namelist()])
+ members = {name.split('/')[0] for name in zip_file.namelist()}
for name in members:
if "Examples" in path:
m_path = self.get_new_path(dst_path=os.path.join("Examples", name))
@@ -228,9 +230,24 @@ def __upload_file(self, file, body, new_name=None):
def __upload_model(self, file, body, new_name=None):
- is_valid, error = self.__validate_model(body, file)
+ try:
+ model = json.loads(body)
+ if "files" in model:
+ for entry in model['files'].values():
+ if not entry['dwn_path'].startswith(self.user_dir):
+ entry['dwn_path'] = os.path.join(self.user_dir, entry['dwn_path'])
+ if not os.path.exists(os.path.dirname(entry['dwn_path'])):
+ os.makedirs(os.path.dirname(entry['dwn_path']))
+ with open(entry['dwn_path'], "w", encoding="utf-8") as entry_fd:
+ entry_fd.write(entry['body'])
+ model = model['model']
+ is_valid, error = self.__validate_model(model, file)
+ except json.decoder.JSONDecodeError:
+ error = [f"The file {file} is not in JSON format."]
+ is_valid = False
+
if is_valid:
- ext = "smdl" if json.loads(body)['is_spatial'] else "mdl"
+ ext = "smdl" if model['is_spatial'] else "mdl"
else:
ext = "json"
if new_name is not None:
@@ -247,7 +264,9 @@ def __upload_model(self, file, body, new_name=None):
path = os.path.join(wkgp_path, file)
else:
path = os.path.join(self.path, file)
- new_file = StochSSFile(path=path, new=True, body=body)
+ if not isinstance(model, str):
+ model = json.dumps(model, sort_keys=True, indent=4)
+ new_file = StochSSFile(path=path, new=True, body=model)
file = new_file.get_file()
dirname = new_file.get_dir_name()
if is_valid:
@@ -286,12 +305,6 @@ def __upload_sbml(self, file, body, new_name=None):
@classmethod
def __validate_model(cls, body, file):
- try:
- body = json.loads(body)
- except json.decoder.JSONDecodeError:
- message = [f"The file {file} is not in JSON format."]
- return False, message
-
keys = ["species", "parameters", "reactions", "eventsCollection",
"rules", "functionDefinitions"]
for key in body.keys():
@@ -633,7 +646,7 @@ def validate_upload_link(self, remote_path):
zip_file.write(body)
try:
with zipfile.ZipFile(ext_path, 'r') as zip_file:
- members = set([name.split('/')[0] for name in zip_file.namelist()])
+ members = {name.split('/')[0] for name in zip_file.namelist()}
for name in members:
if "github.com/StochSS/StochSS_Example_Library/raw/" in remote_path:
mem_path = self.get_new_path(dst_path=os.path.join("Examples", name))
diff --git a/stochss/handlers/util/stochss_job.py b/stochss/handlers/util/stochss_job.py
index 0792138c0d..de96be2b05 100644
--- a/stochss/handlers/util/stochss_job.py
+++ b/stochss/handlers/util/stochss_job.py
@@ -1,6 +1,6 @@
'''
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -76,15 +76,20 @@ def __init__(self, path, new=False, data=None):
self.time_stamp = None
if new:
self.type = data['type']
- self.__create_new_job(mdl_path=data['mdl_path'], settings=data['settings'])
+ self.__create_new_job(
+ mdl_path=data['mdl_path'], settings=data['settings'], compute=data['compute_env']
+ )
- def __create_new_job(self, mdl_path, settings=None):
+ def __create_new_job(self, mdl_path, settings=None, compute="Local"):
path = self.get_path(full=True)
try:
os.mkdir(path)
os.mkdir(self.__get_results_path(full=True))
- info = {"source_model":mdl_path, "wkfl_model":None, "type":self.type, "start_time":None}
+ info = {
+ "source_model": mdl_path, "wkfl_model": None, "type": self.type,
+ "start_time": None, "compute_env": compute
+ }
self.update_info(new_info=info, new=True)
with open(os.path.join(path, "logs.txt"), "w", encoding="utf-8") as log_file:
log_file.close()
@@ -461,7 +466,10 @@ def get_notebook_data(self):
wkfl_type = "2d_parameter_sweep"
kwargs = {"path":path, "new":True, "settings":settings,
"models":{"s_model":s_model, "model":g_model}}
- return {"kwargs":kwargs, "type":wkfl_type}
+ results_path = os.path.join(self.__get_results_path(), "results.p")
+ return {
+ "kwargs":kwargs, "type":wkfl_type, "results": results_path, "compute_env": info['compute_env']
+ }
def get_plot_from_results(self, data_keys, plt_key, add_config=False):
@@ -517,19 +525,19 @@ def get_plot_from_spatial_results(self, data_keys, add_config=False):
result = self.__get_filtered_ensemble_results(None)
self.log("info", "Generating the plot...")
if data_keys['target'] in ("type", "nu", "rho", "mass"):
- fig = result.plot_property(
+ fig = result[data_keys['trajectory']].plot_property(
data_keys['target'], width="auto", height="auto", animated=True,
return_plotly_figure=True
)
elif data_keys['target'] == "v":
- fig = result.plot_property(
+ fig = result[data_keys['trajectory']].plot_property(
data_keys['target'], p_ndx=data_keys['index'], width="auto", height="auto",
animated=True, return_plotly_figure=True
)
else:
concentration = data_keys['mode'] == "discrete-concentration"
deterministic = data_keys['mode'] == "continuous"
- fig = result.plot_species(
+ fig = result[data_keys['trajectory']].plot_species(
data_keys['target'], concentration=concentration, deterministic=deterministic,
width="auto", height="auto", animated=True, return_plotly_figure=True
)
@@ -697,10 +705,10 @@ def load(self, new=False):
self.log("error", f"Exception information: {error}")
finally:
settings = self.load_settings(model=model)
+ info = self.load_info()
+ logs = f"Compute Environment: {info['compute_env']}"
if os.path.exists(os.path.join(self.path, "logs.txt")):
- logs = self.__get_run_logs()
- else:
- logs = ""
+ logs = f"{logs}\n{self.__get_run_logs()}"
self.job = {"mdlPath":mdl_path, "model":model, "settings":settings,
"startTime":info['start_time'], "status":status,
"timeStamp":self.time_stamp, "titleType":self.TITLES[info['type']],
@@ -725,6 +733,8 @@ def load_info(self):
info = json.load(info_file)
if "annotation" not in info:
info['annotation'] = ""
+ if "compute_env" not in info:
+ info['compute_env'] = "Local"
return info
except FileNotFoundError as err:
message = f"Could not find the info file: {str(err)}"
@@ -908,16 +918,18 @@ def update_info(self, new_info, new=False):
info = new_info
else:
info = self.load_info()
- if "source_model" in new_info.keys():
+ if "source_model" in new_info:
info['source_model'] = new_info['source_model']
- if "wkfl_model" in new_info.keys():
+ if "wkfl_model" in new_info:
info['wkfl_model'] = new_info['wkfl_model']
- if "type" in new_info.keys():
+ if "type" in new_info:
info['type'] = new_info['type']
- if "start_time" in new_info.keys():
+ if "start_time" in new_info:
info['start_time'] = new_info['start_time']
- if "annotation" in new_info.keys():
+ if "annotation" in new_info:
info['annotation'] = new_info['annotation']
+ if "compute_env" in new_info:
+ info['compute_env'] = new_info['compute_env']
self.log("debug", f"New info: {info}")
with open(self.__get_info_path(full=True), "w", encoding="utf-8") as file:
json.dump(info, file, indent=4, sort_keys=True)
diff --git a/stochss/handlers/util/stochss_model.py b/stochss/handlers/util/stochss_model.py
index 237992ee38..001f5ba58d 100644
--- a/stochss/handlers/util/stochss_model.py
+++ b/stochss/handlers/util/stochss_model.py
@@ -1,6 +1,6 @@
'''
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/stochss/handlers/util/stochss_notebook.py b/stochss/handlers/util/stochss_notebook.py
index 3bc1486976..7bd96dda32 100644
--- a/stochss/handlers/util/stochss_notebook.py
+++ b/stochss/handlers/util/stochss_notebook.py
@@ -1,6 +1,6 @@
'''
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -15,7 +15,6 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
'''
-
import os
import json
import string
@@ -25,17 +24,18 @@
from escapism import escape
from nbformat import v4 as nbf
+import numpy
+
from gillespy2.solvers.utilities.cpp_support_test import check_cpp_support
from .stochss_base import StochSSBase
-from .stochss_errors import StochSSFileNotFoundError, StochSSModelFormatError, \
- StochSSPermissionsError
+from .stochss_errors import StochSSFileNotFoundError, StochSSModelFormatError, StochSSPermissionsError
class StochSSNotebook(StochSSBase):
'''
- ################################################################################################
+ ####################################################################################################################
StochSS notebook object
- ################################################################################################
+ ####################################################################################################################
'''
ENSEMBLE_SIMULATION = 1
SPATIAL_SIMULATION = 2
@@ -44,9 +44,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",
- "ODECSolver":"ODE", "TauLeapingCSolver":"Tau-Leaping",
- "TauHybridCSolver":"Hybrid-Tau-Leaping"}
+ "TauLeapingSolver":"Tau-Leaping", "TauHybridSolver":"Hybrid-Tau-Leaping", "ODECSolver":"ODE",
+ "TauLeapingCSolver":"Tau-Leaping", "TauHybridCSolver":"Hybrid-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
@@ -63,364 +62,642 @@ def __init__(self, path, new=False, models=None, settings=None):
self.model = models["model"]
if settings is None:
self.settings = self.get_settings_template()
+ if self.s_model['is_spatial']:
+ self.settings['simulationSettings']['realizations'] = 1
else:
self.settings = settings
if "timespanSettings" in settings.keys():
keys = settings['timespanSettings'].keys()
if "endSim" in keys and "timeStep" in keys:
- end = settings['timespanSettings']['endSim']
- step_size = settings['timespanSettings']['timeStep']
- self.s_model['modelSettings']['endSim'] = end
- self.s_model['modelSettings']['timeStep'] = step_size
+ self.s_model['modelSettings']['endSim'] = settings['timespanSettings']['endSim']
+ self.s_model['modelSettings']['timeStep'] = settings['timespanSettings']['timeStep']
self.make_parent_dirs()
n_path, changed = self.get_unique_path(name=self.get_file())
if changed:
self.path = n_path.replace(self.user_dir + '/', "")
+ @classmethod
+ def __add_name(cls, names, names_length, name, max_len=100):
+ if names_length + len(name) > max_len:
+ names = [f"{', '.join(names)},\n{' ' * (8 + 100 - max_len)}{name}"]
+ return 8 + len(name), names
+ names.append(name)
+ return names_length + 2 + len(name), None
- def __create_boundary_condition_cells(self):
- pad = " "
- bc_cells = []
- try:
- for boundary_condition in self.s_model['boundaryConditions']:
- bc_cell = [f'class {boundary_condition["name"]}(BoundaryCondition):',
- f'{pad}def expression(self):',
- f'{pad*2}return """{boundary_condition["expression"]}"""']
- bc_cells.append(nbf.new_code_cell("\n".join(bc_cell)))
- return bc_cells
- except KeyError as err:
- message = "Boundary conditions are not properly formatted or "
- message += f"are referenced incorrectly for notebooks: {str(err)}"
- raise StochSSModelFormatError(message, traceback.format_exc()) from err
-
-
- def __create_boundary_condition_string(self, model, pad):
- if self.s_model['boundaryConditions']:
- bound_conds = ["", f"{pad}# Boundary Conditions"]
- try:
- for bound_cond in self.s_model['boundaryConditions']:
- bc_str = f"{pad}self.add_boundary_condition({bound_cond['name']}())"
- bound_conds.append(bc_str)
- model.extend(bound_conds)
- except KeyError as err:
- message = "Boundary conditions are not properly formatted or "
- message += f"are referenced incorrectly for notebooks: {str(err)}"
- raise StochSSModelFormatError(message, traceback.format_exc()) from err
+ @classmethod
+ def __build_geometry(cls, formula, name=None, c_name=None):
+ pad = ' '
+ if c_name is None:
+ c_name = name.title().replace("_", "")
+ cell_str = "\n".join([
+ f"class {c_name}(spatialpy.Geometry):", f"{pad}def __init__(self):",
+ f"{pad * 2}pass", "", f"{pad}def inside(self, point, on_boundary):",
+ f"{pad * 2}namespace = " + "{'x': point[0], 'y': point[1], 'z': point[2]}",
+ f"{pad * 2}formula = '{formula}'", f"{pad * 2}return eval(formula, " + "{}, namespace)"
+ ])
+ return nbf.new_code_cell(cell_str)
+ @classmethod
+ def __check_reflect_mathod(cls, transformation):
+ point1 = numpy.array([
+ transformation['point1']['x'], transformation['point1']['y'], transformation['point1']['z']
+ ])
+ point2 = numpy.array([
+ transformation['point2']['x'], transformation['point2']['y'], transformation['point2']['z']
+ ])
+ point3 = numpy.array([
+ transformation['point3']['x'], transformation['point3']['y'], transformation['point3']['z']
+ ])
+ if numpy.count_nonzero(point3 - point1) == 0:
+ return "Point-Normal"
+ if numpy.count_nonzero(point2 - point1) == 0:
+ return "Point-Normal"
+ return "3-Point"
+
+ def __create_common_header_cells(self):
+ name = self.s_model['name'].replace('_', ' ').replace('-', ' ').title()
+ nb_title = f"# {name}\n***"
+ if 'annotation' in self.s_model and self.s_model['annotation'] != '':
+ nb_title = f"{nb_title}\n{self.s_model['annotation']}\n***"
+ nb_title = f"{nb_title}\n## Setup the Environment\n***"
+ return [
+ nbf.new_markdown_cell(nb_title), nbf.new_markdown_cell(f"***\n## Create the {name} Model\n***"),
+ nbf.new_markdown_cell("### Instantiate the model"),
+ nbf.new_markdown_cell("***\n## Simulation Parameters\n***")
+ ]
+
+ def __create_compute_cells(self, cells, compute):
+ self.__create_compute_headers(cells, compute)
+ self.__create_compute_imports(cells, compute)
+ self.__create_compute_credentials(cells, compute)
+ self.__create_compute_launch(cells, compute)
+ self.__create_compute_cleanup(cells, compute)
+
+ def __create_compute_cleanup(self, cells, compute):
+ cells.append(nbf.new_code_cell("# cluster.clean_up()"))
+
+ def __create_compute_credentials(self, cells, compute):
+ nb_creds = [
+ "key_dir = os.path.join(os.environ.get('HOME'), '.aws')",
+ "_ = dotenv.load_dotenv(dotenv_path=os.path.join(key_dir, 'awsec2.env'))"
+ ]
+ cells.insert(5, nbf.new_code_cell("\n".join(nb_creds)))
+
+ def __create_compute_headers(self, cells, compute):
+ cells.insert(2, nbf.new_markdown_cell(
+ "***\n## AWS Credentials\n***\nAWS Credentials for StochSS can be set [here](/stochss/settings)"
+ ))
+ cells.insert(3, nbf.new_markdown_cell("***\n## Launch AWS Cluster\n***"))
+ cells.append(nbf.new_markdown_cell('***\n## Clean Up AWS Cluster\n***'))
+
+ def __create_compute_imports(self, cells, compute):
+ cells.insert(1, nbf.new_code_cell("import os\nimport dotenv"))
+ cells.insert(3, nbf.new_code_cell(
+ "import stochss_compute\nfrom stochss_compute.cloud import EC2Cluster, EC2LocalConfig"
+ ))
+
+ def __create_compute_launch(self, cells, compute):
+ instance = self.load_user_settings(path='.user-settings.json')['headNode']
+ path = f'{instance.replace(".", "-")}-status.txt'
+ nb_cluster = [
+ "local_config = EC2LocalConfig(",
+ f" key_dir=key_dir, status_file=os.path.join(key_dir, '{path}')", ")",
+ "cluster = EC2Cluster(local_config=local_config)",
+ f"# cluster = EC2Cluster(status_file=os.path.join(key_dir, '{path}'))",
+ "if cluster._server is None:",
+ f" cluster.launch_single_node_instance('{instance}')", "cluster.status"
+ ]
+ cells.insert(7, nbf.new_code_cell("\n".join(nb_cluster)))
def __create_configuration_cell(self):
- pad = " "
- config = ["def configure_simulation():"]
- # Add solver instantiation line if the c solver are available
+ use_solver = self.nb_type not in (self.ENSEMBLE_SIMULATION, self.SPATIAL_SIMULATION)
+ algorithm = self.settings['simulationSettings']['algorithm']
is_automatic = self.settings['simulationSettings']['isAutomatic']
- if "CSolver" in self.settings['solver']:
- if is_automatic and self.nb_type <= self.ENSEMBLE_SIMULATION:
- commented = True
- elif is_automatic and "CSolver" not in self.settings['solver']:
- commented = True
- else:
- commented = False
- start = f"{pad}# " if commented else pad
- if self.nb_type in (self.MODEL_EXPLORATION, self.MODEL_INFERENCE):
- del_dir = ", delete_directory=False"
- else:
- del_dir = ""
- config.append(f"{start}solver = {self.settings['solver']}(model=model{del_dir})")
- config.append(pad + "kwargs = {")
if self.s_model['is_spatial']:
settings = self.__get_spatialpy_run_setting()
+ settings_lists = {"SSA": ["number_of_trajectories", "seed"]}
else:
- settings = self.__get_gillespy2_run_settings()
- settings_lists = {"ODE":['"solver"', '"integrator_options"'],
- "SSA":['"solver"', '"seed"', '"number_of_trajectories"'],
- "CLE":['"solver"', '"seed"', '"number_of_trajectories"', '"tau_tol"'],
- "Tau-Leaping":['"solver"', '"seed"', '"number_of_trajectories"',
- '"tau_tol"'],
- "Hybrid-Tau-Leaping":['"solver"', '"seed"', '"number_of_trajectories"',
- '"tau_tol"', '"integrator_options"']}
- algorithm = self.settings['simulationSettings']['algorithm']
- is_spatial = self.s_model['is_spatial']
- for setting in settings:
- key = setting.split(':')[0]
- if self.nb_type > self.ENSEMBLE_SIMULATION and key == '"solver"' and \
- "CSolver" in self.settings['solver']:
- start = pad*2
- elif key not in settings_lists[algorithm]:
- start = f"{pad*2}# "
- elif is_automatic and not is_spatial and key == '"number_of_trajectories"':
- start = pad*2
- elif is_automatic:
- start = f"{pad*2}# "
+ settings = self.__get_gillespy2_run_settings(use_solver=use_solver)
+ settings_lists = {
+ "ODE": self.model.get_best_solver_algo("ODE").get_solver_settings(),
+ "SSA": self.model.get_best_solver_algo("SSA").get_solver_settings(),
+ "CLE": self.model.get_best_solver_algo("CLE").get_solver_settings(),
+ "Tau-Leaping": self.model.get_best_solver_algo("Tau-Leaping").get_solver_settings(),
+ "Hybrid-Tau-Leaping": self.model.get_best_solver_algo("Tau-Hybrid").get_solver_settings()
+ }
+ pad = " "
+ config = ["def configure_simulation():"]
+ if use_solver:
+ if self.s_model['is_spatial']:
+ nb_solver = "spatialpy.solver(model=model)"
else:
- start = pad*2
- config.append(f"{start}{setting},")
+ solver = self.get_gillespy2_solver_name()
+ del_dir = ", delete_directory=False" if "CSolver" in solver else ""
+ nb_solver = f"gillespy2.{solver}(model=model{del_dir})"
+ config.append(f"{pad}solver = {nb_solver}")
+ config.append(pad + "kwargs = {")
+ for name, val in settings.items():
+ if name == "solver" and \
+ self.nb_type not in (self.ENSEMBLE_SIMULATION, self.SPATIAL_SIMULATION):
+ config.append(f"{pad*2}'{name}': {val},")
+ elif is_automatic or name not in settings_lists[algorithm]:
+ config.append(f"{pad*2}# '{name}': {val},")
+ else:
+ config.append(f"{pad*2}'{name}': {val},")
config.extend([pad + "}", f"{pad}return kwargs"])
return nbf.new_code_cell("\n".join(config))
-
- def __create_event_strings(self, model, pad):
- if self.s_model['eventsCollection']:
- triggers = ["", f"{pad}# Event Triggers"]
- assignments = ["", f"{pad}# Event Assignments"]
- events = ["", f"{pad}# Events"]
+ def __create_parameters(self, nb_model, index):
+ if len(self.s_model['parameters']) > 0:
+ pad = ' '
+ package = "spatialpy" if self.s_model['is_spatial'] else "gillespy2"
+ args_tmp = "name='__NAME__', expression='__EXPRESSION__'"
+ tmp = f"{pad}__NAME__ = {package}.Parameter({args_tmp})"
+ names = []
+ n_len = 8
+ parameters = ["", f"{pad}# Parameters"]
try:
- for event in self.s_model['eventsCollection']:
- t_name = self.__create_event_trigger_string(triggers=triggers, event=event,
- pad=pad)
- a_names = self.__create_event_assignment_strings(assignments=assignments,
- event=event, pad=pad)
- delay = f'"{event["delay"]}"' if event['delay'] else None
- ev_str = f'{pad}self.add_event(Event(name="{event["name"]}", '
- ev_str += f'trigger={t_name}, assignments=[{a_names}], '
- ev_str += f'delay={delay}, priority="{event["priority"]}", '
- ev_str += f'use_values_from_trigger_time={event["useValuesFromTriggerTime"]}))'
- events.append(ev_str)
- model.extend(triggers)
- model.extend(assignments)
- model.extend(events)
+ for s_parameter in self.s_model['parameters']:
+ n_len, n_names = self.__add_name(names, n_len, s_parameter['name'])
+ if n_names is not None:
+ names = n_names
+ nb_param = tmp.replace("__NAME__", s_parameter['name'])
+ nb_param = nb_param.replace("__EXPRESSION__", str(s_parameter['expression']))
+ parameters.append(nb_param)
+ parameters.append(f"{pad}model.add_parameter([\n{pad*2}{', '.join(names)}\n{pad}])")
+ nb_model.insert(index, '\n'.join(parameters))
+ index += 1
except KeyError as err:
- message = "Events are not properly formatted or "
+ message = "Parameters are not properly formatted or "
message += f"are referenced incorrectly for notebooks: {str(err)}"
raise StochSSModelFormatError(message, traceback.format_exc()) from err
-
-
- @classmethod
- def __create_event_assignment_strings(cls, assignments, event, pad):
- names = []
- for i, assignment in enumerate(event['eventAssignments']):
- name = f'{event["name"]}_assign_{i+1}'
- names.append(name)
- assign_str = f'{pad}{name} = EventAssignment(variable="{assignment["variable"]["name"]}'
- assign_str += f'", expression="{assignment["expression"]}")'
- assignments.append(assign_str)
- return ', '.join(names)
-
-
- @classmethod
- def __create_event_trigger_string(cls, triggers, event, pad):
- name = f'{event["name"]}_trig'
- trig_str = f'{pad}{name} = EventTrigger(expression="{event["triggerExpression"]}", '
- trig_str += f'initial_value={event["initialValue"]}, persistent={event["persistent"]})'
- triggers.append(trig_str)
- return name
-
-
- def __create_function_definition_strings(self, model, pad):
- if self.s_model['functionDefinitions']:
- func_defs = ["", f"{pad}# Function Definitions"]
+ return index
+
+ def __create_reactions(self, nb_model, index, type_refs=None):
+ if len(self.s_model['reactions']) > 0:
+ pad = ' '
+ package = "spatialpy" if self.s_model['is_spatial'] else "gillespy2"
+ l1_tmp = f"{pad*2}name='__NAME__',__RATE____RESTRICT_TO__"
+ l2_tmp = f"{pad*2}reactants=__REACTANTS__, products=__PRODUCTS__"
+ tmp = f"{pad}__NAME__ = {package}.Reaction(\n{l1_tmp}\n{l2_tmp}__L3____L4__\n{pad})"
+ names = []
+ n_len = 8
+ reactions = ["", f"{pad}# Reactions"]
try:
- for func_def in self.s_model['functionDefinitions']:
- fd_str = f'{pad}self.add_function_definition(FunctionDefinition('
- fd_str += f'name="{func_def["name"]}", function="{func_def["expression"]}", '
- fd_str += f'args={func_def["variables"].split(", ")}))'
- func_defs.append(fd_str)
- model.extend(func_defs)
+ for s_reaction in self.s_model['reactions']:
+ n_len, n_names = self.__add_name(names, n_len, s_reaction['name'])
+ if n_names is not None:
+ names = n_names
+ reactants = self.__create_stoich_species(stoich_species=s_reaction['reactants'])
+ products = self.__create_stoich_species(stoich_species=s_reaction['products'])
+ if s_reaction['massaction']:
+ nb_reac = tmp.replace("__L3____L4__", "")
+ nb_reac = nb_reac.replace("__RATE__", f" rate='{s_reaction['rate']['name']}',")
+ else:
+ ssa = f",\n{pad*2}propensity_function='{s_reaction['propensity']}',"
+ ode = f"\n{pad*2}ode_propensity_function='{s_reaction['odePropensity']}'"
+ nb_reac = tmp.replace("__RATE__", "").replace("__L3__", ssa).replace("__L4__", ode)
+ if type_refs is None or len(s_reaction['types']) == len(type_refs):
+ nb_reac = nb_reac.replace("__RESTRICT_TO__", "")
+ else:
+ types = [type_refs[d_type] for d_type in s_reaction['types']]
+ restrict_to = f" restrict_to=[{', '.join(types)}],"
+ nb_reac = nb_reac.replace("__RESTRICT_TO__", restrict_to)
+ nb_reac = nb_reac.replace("__NAME__", s_reaction['name'])
+ nb_reac = nb_reac.replace("__REACTANTS__", reactants)
+ reactions.append(nb_reac.replace("__PRODUCTS__", products))
+ reactions.append(f"{pad}model.add_reaction([\n{pad*2}{', '.join(names)}\n{pad}])")
+ nb_model.insert(index, '\n'.join(reactions))
+ index += 1
except KeyError as err:
- message = "Function definitions are not properly formatted or "
+ message = "Reactions are not properly formatted or "
message += f"are referenced incorrectly for notebooks: {str(err)}"
raise StochSSModelFormatError(message, traceback.format_exc()) from err
-
-
- def __create_import_cell(self):
- try:
- is_automatic = self.settings['simulationSettings']['isAutomatic']
- imports = ["import numpy as np"]
- if self.s_model['is_spatial']:
- imports.append("import spatialpy")
- imports.append("from spatialpy import Model, Species, Parameter, Reaction,\\")
- imports.append(" Domain, PlaceInitialCondition, \\")
- imports.append(" UniformInitialCondition, \\")
- imports.append(" ScatterInitialCondition")
- return nbf.new_code_cell("\n".join(imports))
- imports.append("import gillespy2")
- imports.append("from gillespy2 import Model, Species, Parameter, Reaction, Event, \\")
- imports.append(" EventTrigger, EventAssignment, RateRule, \\")
- imports.append(" AssignmentRule, FunctionDefinition, TimeSpan")
- tau_leaping_solver = self.model.get_best_solver_algo("Tau-Leaping").name
- tau_hybrid_solver = self.model.get_best_solver_algo("Tau-Hybrid").name
- algorithm_map = {
- 'ODE': f'from gillespy2 import {self.model.get_best_solver_algo("ODE").name}',
- 'SSA': f'from gillespy2 import {self.model.get_best_solver_algo("SSA").name}',
- 'CLE': f'from gillespy2 import {self.model.get_best_solver_algo("CLE").name}',
- 'Tau-Leaping': f'from gillespy2 import {tau_leaping_solver}',
- 'Hybrid-Tau-Leaping': f'from gillespy2 import {tau_hybrid_solver}'
- }
- algorithm = self.settings['simulationSettings']['algorithm']
- for name, alg_import in algorithm_map.items():
- if not is_automatic and name == algorithm:
- imports.append(alg_import)
- elif name == algorithm and self.nb_type > self.ENSEMBLE_SIMULATION and \
- "CSolver" in alg_import:
- imports.append(alg_import)
- else:
- imports.append(f"# {alg_import}")
- return nbf.new_code_cell("\n".join(imports))
- except KeyError as err:
- message = "Workflow settings are not properly formatted or "
- message += f"are referenced incorrectly for notebooks: {str(err)}"
- raise StochSSModelFormatError(message, traceback.format_exc()) from err
-
-
- def __create_initial_condition_strings(self, model, pad, type_refs=None):
- if self.s_model['initialConditions']:
- ic_types = {"Place":"PlaceInitialCondition", "Scatter":"ScatterInitialCondition",
- "Distribute Uniformly per Voxel":"UniformInitialCondition"}
- initial_conditions = ["", f"{pad}# Initial Conditions"]
+ return index
+
+ def __create_run(self, results, compute="StochSS"):
+ nb_run_header = "***\n## Run the Simulation\n***"
+ nb_run = ["kwargs = configure_simulation()"]
+ if compute in ("AWS Cloud", "AWS"):
+ nb_run.append("simulation = stochss_compute.RemoteSimulation(model, server=cluster)")
+ nb_res = "results = simulation.run(**kwargs)"
+ else:
+ nb_res = "results = model.run(**kwargs)"
+ if results is None:
+ nb_run.append(nb_res)
+ else:
+ nb_load_res = [
+ f"# {nb_res}", f"path = '{results}'",
+ "with open(os.path.join(os.path.expanduser('~'), path), 'rb') as results_file:",
+ " results = pickle.load(results_file)"
+ ]
+ nb_run.extend(nb_load_res)
+ return nbf.new_markdown_cell(nb_run_header), nbf.new_code_cell("\n".join(nb_run))
+
+ def __create_spatial_actions(self, nb_domain, index, type_refs):
+ if len(self.s_model['domain']['actions']) > 0:
+ func_map = {"Remove Action": "add_remove_action", "Set Action": "add_set_action"}
+ pad = ' '
+ c_ndx = 1
+ l_ndx = 1
+ args_tmp = "__GEOMETRY____ENABLE____PROPS__"
+ tmp = f"{pad}domain.__FUNCTION__(\n{pad*2}__ARGS__\n{pad})"
+ actions = ["", f"{pad}# Domain Actions"]
try:
- for init_cond in self.s_model['initialConditions']:
- ic_str = f'{pad}self.add_initial_condition({ic_types[init_cond["icType"]]}('
- ic_str += f'species="{init_cond["specie"]["name"]}", count={init_cond["count"]}'
- if init_cond["icType"] == "Place":
- place = f'{init_cond["x"]}, {init_cond["y"]}, {init_cond["z"]}'
- ic_str += f', location=[{place}]))'
+ s_actions = sorted(self.s_model['domain']['actions'], key=lambda action: action['priority'])
+ for s_act in s_actions:
+ # Build props arg
+ if s_act['type'] in ('Fill Action', 'Set Action', 'XML Mesh', 'Mesh IO'):
+ props = [
+ f"mass={s_act['mass']}, vol={s_act['vol']}, rho={s_act['rho']}, ",
+ f"nu={s_act['nu']}, c={s_act['c']}, fixed={s_act['fixed']}",
+ ]
+ if s_act['type'] in ('Fill Action', 'Set Action') and s_act['typeID'] > 0:
+ props.insert(0, f"type_id={type_refs[s_act['typeID']]}, ")
+ args = args_tmp.replace("__PROPS__", f",\n{pad*2}{''.join(props)}")
else:
- types = [type_refs[d_type] for d_type in init_cond["types"]]
- ic_str += f', types={types}))'
- initial_conditions.append(ic_str)
- model.extend(initial_conditions)
+ args = args_tmp.replace("__PROPS__", "")
+ if s_act['scope'] == 'Multi Particle' or s_act['type'] != "Fill Action":
+ args = args.replace("__ENABLE__", f"enable={s_act['enable']}, apply_action={s_act['enable']}")
+ # Apply actions
+ if s_act['type'] == "Fill Action":
+ if s_act['scope'] == 'Multi Particle':
+ if s_act['transformation'] == "":
+ lattice = f"{s_act['shape']}_latt"
+ else:
+ lattice = s_act['transformation']
+ actions.append(f"{pad}{lattice}.lattice = {s_act['shape']}_latt")
+ args = args.replace("__GEOMETRY__", f"lattice={lattice}, geometry={s_act['shape']}_geom, ")
+ nb_act = tmp.replace("__FUNCTION__", "add_fill_action").replace("__ARGS__", args)
+ else:
+ p_x = s_act['point']['x']
+ p_y = s_act['point']['y']
+ p_z = s_act['point']['z']
+ args = args.replace("__GEOMETRY____ENABLE__", f"[{p_x}, {p_y}, {p_z}]")
+ nb_act = tmp.replace("__FUNCTION__", "add_point").replace("__ARGS__", args)
+ elif s_act['type'] in ('XML Mesh', 'Mesh IO', 'StochSS Domain'):
+ args = args.replace("__GEOMETRY__", f"lattice={f'ipa_lattice{l_ndx}'}, ")
+ nb_act = tmp.replace("__FUNCTION__", "add_fill_action").replace("__ARGS__", args)
+ l_ndx += 1
+ else:
+ if s_act['scope'] == "Single Particle":
+ geometry = f"geometry=SPAGeometry{c_ndx}(), "
+ c_ndx += 1
+ elif s_act['transformation'] == "":
+ geometry = f"geometry={s_act['shape']}_geom, "
+ else:
+ geometry = f"geometry={s_act['transformation']}, "
+ actions.append(f"{pad}{s_act['transformation']}.geometry = {s_act['shape']}_geom")
+ args = args.replace("__GEOMETRY__", geometry)
+ nb_act = tmp.replace("__FUNCTION__", func_map[s_act['type']]).replace("__ARGS__", args)
+ actions.append(nb_act)
+ nb_domain.insert(index, '\n'.join(actions))
+ index += 1
except KeyError as err:
- message = "Initial conditions are not properly formatted or "
- message += f"are referenced incorrectly for notebooks: {str(err)}"
+ message = "The domain actions is not properly formatted or "
+ message += f"is referenced incorrectly for notebooks: {str(err)}"
raise StochSSModelFormatError(message, traceback.format_exc()) from err
+ return index
-
- def __create_domain_string(self, model, pad):
- domain = ["", f"{pad}# Domain",
- f"{pad}domain = Domain.read_stochss_domain('{self.s_model['path']}')",
- f"{pad}self.add_domain(domain)",
- "", f"{pad}self.staticDomain = {self.s_model['domain']['static']}"]
- model.extend(domain)
-
-
- def __create_model_cell(self):
- pad = ' '
- if self.s_model['is_spatial']:
- types = sorted(self.s_model['domain']['types'], key=lambda d_type: d_type['typeID'])
- type_ids = {d_type['typeID']:d_type['name'] for d_type in types if d_type['typeID'] > 0}
- model = [f"class {self.get_class_name()}(Model):"]
- model.extend([f" {type_id.upper()} = '{type_id}'" for type_id in type_ids.values()])
- model.extend(["", " def __init__(self):",
- f'{pad}Model.__init__(self, name="{self.get_name()}")'])
- type_refs = {index: type_id for index, type_id in type_ids.items()}
- self.__create_domain_string(model=model, pad=pad)
- self.__create_boundary_condition_string(model=model, pad=pad)
- self.__create_species_strings(model=model, pad=pad, type_refs=type_refs)
- self.__create_initial_condition_strings(model=model, pad=pad, type_refs=type_refs)
- self.__create_parameter_strings(model=model, pad=pad)
- self.__create_reaction_strings(model=model, pad=pad, type_refs=type_refs)
- else:
- model = [f"class {self.get_class_name()}(Model):",
- " def __init__(self, parameter_values=None):",
- f'{pad}Model.__init__(self, name="{self.get_name()}")',
- f"{pad}self.volume = {self.s_model['volume']}"]
- self.__create_parameter_strings(model=model, pad=pad)
- self.__create_species_strings(model=model, pad=pad)
- self.__create_reaction_strings(model=model, pad=pad)
- self.__create_event_strings(model=model, pad=pad)
- self.__create_rules_strings(model=model, pad=pad)
- self.__create_function_definition_strings(model=model, pad=pad)
- self.__create_tspan_string(model=model, pad=pad)
- return nbf.new_code_cell("\n".join(model))
-
-
- def __create_parameter_strings(self, model, pad):
- if self.s_model['parameters']:
- parameters = ["", f"{pad}# Parameters"]
+ def __create_spatial_boundary_conditions(self, nb_model, index):
+ if len(self.s_model['boundaryConditions']) > 0:
+ pad = ' '
+ tmp = f"{pad}__NAME__ = __OBJECT__()"
names = []
+ n_len = 8
+ bound_conds = ["", f"{pad}# Boundary Conditions"]
try:
- for param in self.s_model['parameters']:
- names.append(param['name'])
- param_str = f'{pad}{param["name"]} = Parameter(name="{param["name"]}", '
- param_str += f'expression="{param["expression"]}")'
- parameters.append(param_str)
- parameters.append(f"{pad}self.add_parameter([{', '.join(names)}])")
- model.extend(parameters)
+ for s_bound_cond in self.s_model['boundaryConditions']:
+ name = f"{s_bound_cond['name'].lower()}_bc"
+ n_len, n_names = self.__add_name(names, n_len, name)
+ if n_names is not None:
+ names = n_names
+ nb_bound_cond = tmp.replace("__NAME__", name)
+ nb_bound_cond = nb_bound_cond.replace("__OBJECT__", s_bound_cond['name'])
+ bound_conds.append(nb_bound_cond)
+ bound_conds.append(f"{pad}model.add_boundary_condition([\n{pad*2}{', '.join(names)}\n{pad}])")
+ nb_model.insert(index, '\n'.join(bound_conds))
+ index += 1
except KeyError as err:
- message = "Parameters are not properly formatted or "
+ message = "Boundary conditions are not properly formatted or "
message += f"are referenced incorrectly for notebooks: {str(err)}"
raise StochSSModelFormatError(message, traceback.format_exc()) from err
+ return index
+
+ def __create_spatial_boundary_condition_cells(self, cells):
+ index = 2
+ if len(self.s_model['boundaryConditions']) > 0:
+ pad = " "
+ tmp_body = f"{pad}def expression(self):\n{pad*2}return '''__EXPRESSION__'''"
+ tmp = f"class __NAME__(spatialpy.BoundaryCondition):\n{tmp_body}"
+ try:
+ bc_header = "***\n## Creating the Boundary Conditions for the System\n***"
+ cells.insert(index, nbf.new_markdown_cell(bc_header))
+ index += 1
+ for s_bound_cond in self.s_model['boundaryConditions']:
+ bc_cell = tmp.replace("__NAME__", s_bound_cond['name'])
+ bc_cell = bc_cell.replace("__EXPRESSION__", s_bound_cond['expression'])
+ cells.insert(index, nbf.new_code_cell(bc_cell))
+ index += 1
+ return index + 1
+ except KeyError as err:
+ message = "Boundary conditions are not properly formatted or "
+ message += f"are referenced incorrectly for notebooks: {str(err)}"
+ raise StochSSModelFormatError(message, traceback.format_exc()) from err
+ return index
+ def __create_spatial_domain(self, nb_model, index, type_refs):
+ try:
+ pad = ' '
+ tmp = f"{pad}model.__TYPE__ = '__ID__'"
+ xlim = tuple(self.s_model['domain']['x_lim'])
+ ylim = tuple(self.s_model['domain']['y_lim'])
+ zlim = tuple(self.s_model['domain']['z_lim'])
+ rho0 = self.s_model['domain']['rho_0']
+ c_0 = self.s_model['domain']['c_0']
+ p_0 = self.s_model['domain']['p_0']
+ gravity = None if self.s_model['domain']['gravity'] == [0, 0, 0] else self.s_model['domain']['gravity']
+ nb_domain = [
+ "", f"{pad}# Define Domain Type IDs as constants of the Model", "", f"{pad}# Domain",
+ f"{pad}domain = spatialpy.Domain(", f"{pad*2}0, {xlim}, {ylim},",
+ f"{pad*2}{zlim}, rho0={rho0}, c0={c_0}, P0={p_0}, gravity={gravity}", f"{pad})", "",
+ f"{pad}model.add_domain(domain, allow_all_types=True)", "",
+ f"{pad}model.staticDomain = {self.s_model['domain']['static']}"
+ ]
+ d_index = self.__create_spatial_shapes(nb_domain, 8)
+ d_index = self.__create_spatial_transformations(nb_domain, d_index)
+ d_index = self.__create_spatial_actions(nb_domain, d_index, type_refs)
+ d_index = 2
+ for d_type in self.s_model['domain']['types']:
+ if d_type['typeID'] > 0:
+ name = f"N_{d_type['name']}" if d_type['name'].isdigit() else d_type['name'].upper()
+ nb_type = tmp.replace("__TYPE__", name)
+ nb_domain.insert(d_index, nb_type.replace("__ID__", d_type['name']))
+ d_index += 1
+ nb_model.insert(index, '\n'.join(nb_domain))
+ except KeyError as err:
+ message = f"The domain is not properly formatted or is referenced incorrectly for notebooks: {str(err)}"
+ raise StochSSModelFormatError(message, traceback.format_exc()) from err
+ return index + 1
+
+ def __create_spatial_geometry_cells(self, cells, index):
+ exempt_formula = ("", "True", "on_boundary", "not on_boundary")
+ shapes = list(filter(
+ lambda shape, exempt=exempt_formula: shape['type'] == "Standard" and shape['formula'] not in exempt,
+ self.s_model['domain']['shapes']
+ ))
+ actions = list(filter(
+ lambda action: action['scope'] == "Single Particle" and action['type'] in ("Set Action", "Remove Action"),
+ self.s_model['domain']['actions']
+ ))
+ if len(shapes) > 0 or len(actions) > 0:
+ try:
+ cells.insert(index, nbf.new_markdown_cell("***\n## Geometries\n***"))
+ index += 1
+ for s_shape in shapes:
+ cells.insert(index, self.__build_geometry(s_shape['formula'], name=s_shape['name']))
+ index += 1
+ for i, s_act in enumerate(actions):
+ p_x = s_act['point']['x']
+ p_y = s_act['point']['y']
+ p_z = s_act['point']['z']
+ formula = f"x == {p_x} and y == {p_y} and z == {p_z}"
+ cells.insert(index, self.__build_geometry(formula, c_name=f"SPAGeometry{i + 1}"))
+ index += 1
+ except KeyError as err:
+ message = "Shapes or actions are not properly formatted or "
+ message += f"are referenced incorrectly for notebooks: {str(err)}"
+ raise StochSSModelFormatError(message, traceback.format_exc()) from err
+ return index
- def __create_reaction_strings(self, model, pad, type_refs=None):
- if self.s_model['reactions']:
- reactions = ["", f"{pad}# Reactions"]
+ def __create_spatial_initial_condition(self, nb_model, index, type_refs):
+ if len(self.s_model['initialConditions']) > 0:
+ pad = ' '
+ args_tmp = "species='__SPECIES__', count=__COUNT__, __DATA__"
+ tmp = f"{pad}__NAME__ = spatialpy.__OBJECT__({args_tmp})"
names = []
+ n_len = 8
+ ic_types = {
+ "Place":"PlaceInitialCondition",
+ "Scatter":"ScatterInitialCondition",
+ "Distribute Uniformly per Voxel":"UniformInitialCondition"
+ }
+ init_cond = ["", f"{pad}# Initial Conditions"]
try:
- for reac in self.s_model['reactions']:
- names.append(reac['name'])
- react_str = self.__create_stoich_spec_string(stoich_species=reac['reactants'])
- prod_str = self.__create_stoich_spec_string(stoich_species=reac['products'])
-
- reac_str = f'{pad}{reac["name"]} = Reaction(name="{reac["name"]}", '
- reac_str += f'reactants={react_str}, products={prod_str}, '
- if not reac['massaction']:
- reac_str += f'propensity_function="{reac["propensity"]}", '
- reac_str += f'ode_propensity_function="{reac["odePropensity"]}"'
+ for s_init_cond in self.s_model['initialConditions']:
+ name = f"{s_init_cond['specie']['name']}_ic"
+ n_len, n_names = self.__add_name(names, n_len, name)
+ if n_names is not None:
+ names = n_names
+ nb_init_cond = tmp.replace("__OBJECT__", ic_types[s_init_cond['icType']])
+ nb_init_cond = nb_init_cond.replace("__NAME__", name)
+ nb_init_cond = nb_init_cond.replace("__SPECIES__", s_init_cond['specie']['name'])
+ nb_init_cond = nb_init_cond.replace("__COUNT__", str(s_init_cond['count']))
+ if s_init_cond['icType'] == "Place":
+ location = f"location=[{s_init_cond['x']}, {s_init_cond['y']}, {s_init_cond['z']}]"
+ nb_init_cond = nb_init_cond.replace("__DATA__", location)
else:
- reac_str += f'rate="{reac["rate"]["name"]}"'
- if self.s_model['is_spatial']:
- types = [f"self.{type_refs[d_type].upper()}" for d_type in reac["types"]]
- if len(types) < len(type_refs):
- reac_str += f', restrict_to=[{", ".join(types)}]'
- reac_str += ")"
- reactions.append(reac_str)
- reactions.append(f"{pad}self.add_reaction([{', '.join(names)}])")
- model.extend(reactions)
+ types = [type_refs[d_type] for d_type in s_init_cond['types']]
+ nb_init_cond = nb_init_cond.replace("__DATA__", f"types=[{', '.join(types)}]")
+ init_cond.append(nb_init_cond)
+ init_cond.append(f"{pad}model.add_initial_condition([\n{pad*2}{', '.join(names)}\n{pad}])")
+ nb_model.insert(index, '\n'.join(init_cond))
+ index += 1
except KeyError as err:
- message = "Reactions are not properly formatted or "
+ message = "Initial Conditions are not properly formatted or "
message += f"are referenced incorrectly for notebooks: {str(err)}"
raise StochSSModelFormatError(message, traceback.format_exc()) from err
+ return index
-
- def __create_rules_strings(self, model, pad):
- if self.s_model['rules']:
- rate_rules = ["", f"{pad}# Rate Rules"]
- assignment_rules = ["", f"{pad}# Assignment Rules"]
- rr_start = "self.add_rate_rule(RateRule"
- ar_start = "self.add_assignment_rule(AssignmentRule"
+ def __create_spatial_model_cell(self, func_name):
+ pad = ' '
+ frequency = self.s_model['modelSettings']['timeStep']
+ end = self.s_model['modelSettings']['endSim']
+ timestep_size = self.s_model['modelSettings']['timestepSize']
+ nb_model = [
+ f"def {func_name}(parameter_values=None):",
+ f"{pad}model = spatialpy.Model(name='{self.s_model['name']}')", "", f"{pad}# Timespan",
+ f"{pad}tspan = spatialpy.TimeSpan.arange({frequency}, t={end}, timestep_size={timestep_size})",
+ f"{pad}model.timespan(tspan)", f"{pad}return model"
+ ]
+ type_refs = self.__get_object_types()
+ index = self.__create_spatial_domain(nb_model, 2, type_refs)
+ index = self.__create_spatial_species(nb_model, index, type_refs)
+ index = self.__create_spatial_initial_condition(nb_model, index, type_refs)
+ index = self.__create_parameters(nb_model, index)
+ index = self.__create_reactions(nb_model, index, type_refs=type_refs)
+ index = self.__create_spatial_boundary_conditions(nb_model, index)
+ return nbf.new_code_cell("\n".join(nb_model))
+
+ def __create_spatial_shapes(self, nb_domain, index):
+ actions = list(filter(
+ lambda act: act['type'] in ('XML Mesh', 'Mesh IO', 'StochSS Domain'),
+ self.s_model['domain']['actions']
+ ))
+ if len(self.s_model['domain']['shapes']) > 0 or len(actions) > 0:
+ class_map = {
+ 'Cartesian Lattice': 'CartesianLattice', 'Spherical Lattice': 'SphericalLattice',
+ 'Cylindrical Lattice': 'CylindricalLattice', 'XML Mesh': 'XMLMeshLattice',
+ 'Mesh IO': 'MeshIOLattice', 'StochSS Domain': 'StochSSLattice'
+ }
+ pad = ' '
+ comb_geoms = {}
+ geo_tmp = f"{pad}__NAME__ = __CLASS_NAME__(__ARGS__)"
+ lat_tmp = f"{pad}__NAME__ = spatialpy.__CLASS__(\n{pad*2}__ARGS__\n{pad})"
+ geometries = ["", f"{pad}# Domain Geometries"]
+ lattices = ["", f"{pad}# Domain Lattices"]
+ if len(actions) > 0:
+ lattices.insert(1, f"{pad}import os\n{pad}home = os.path.expanduser('~')")
try:
- for rule in self.s_model['rules']:
- start = rr_start if rule['type'] == "Rate Rule" else ar_start
- rule_str = f'{pad}{start}(name="{rule["name"]}", formula="{rule["expression"]}"'
- rule_str += f', variable="{rule["variable"]["name"]}"))'
- if rule['type'] == "Rate Rule":
- rate_rules.append(rule_str)
+ for s_shape in self.s_model['domain']['shapes']:
+ # Create geometry from shape
+ geo_name = f"{s_shape['name']}_geom"
+ if s_shape['type'] == "Standard":
+ if s_shape['formula'] in ("", "True"):
+ geometries.append(f"{pad}{geo_name} = spatialpy.GeometryAll()")
+ elif s_shape['formula'] == "on_boundary":
+ geometries.append(f"{pad}{geo_name} = spatialpy.GeometryExterior()")
+ elif s_shape['formula'] == "not on_boundary":
+ geometries.append(f"{pad}{geo_name} = spatialpy.GeometryInterior()")
+ else:
+ c_name = s_shape['name'].title().replace("_", "")
+ nb_geom = geo_tmp.replace("__ARGS__", "").replace("__NAME__", geo_name)
+ geometries.append(nb_geom.replace("__CLASS_NAME__", c_name))
else:
- assignment_rules.append(rule_str)
- if len(rate_rules) > 2:
- model.extend(rate_rules)
- if len(assignment_rules) > 2:
- model.extend(assignment_rules)
+ comb_geoms[geo_name] = s_shape['formula']
+ nb_geom = geo_tmp.replace("__ARGS__", "'', {}").replace("__NAME__", geo_name)
+ geometries.append(nb_geom.replace("__CLASS_NAME__", "spatialpy.CombinatoryGeometry"))
+ geometries.append(f"{pad}{geo_name}.formula = '{s_shape['formula']}'")
+ # Create lattice from shape if fillable
+ if s_shape['fillable']:
+ l_class = class_map[s_shape['lattice']]
+ lat_name = f"{s_shape['name']}_latt"
+ nb_latt = lat_tmp.replace("__NAME__", lat_name).replace("__CLASS__", l_class)
+ if l_class == "CartesianLattice":
+ half_length = s_shape['length'] / 2
+ half_height = s_shape['height'] / 2
+ half_depth = s_shape['depth'] / 2
+ args = ''.join([
+ f"-{half_length}, {half_length}, {s_shape['deltax']},\n{pad*2}",
+ f"ymin=-{half_height}, ymax={half_height}, deltay={s_shape['deltay']}, ",
+ f"zmin=-{half_depth}, zmax={half_depth}, deltaz={s_shape['deltaz']}"
+ ])
+ else:
+ slen = "" if l_class == "SphericalLattice" else f", {s_shape['length']}"
+ args = f"{s_shape['radius']}{slen}, {s_shape['deltas']}, deltar={s_shape['deltar']}"
+ lattices.append(nb_latt.replace("__ARGS__", args))
+ if len(comb_geoms) > 0:
+ geometries.append("")
+ items = [' and ', ' or ', ' not ', '(', ')']
+ for name, formula in comb_geoms.items():
+ if formula.startswith("not "):
+ formula = formula.replace("not ", "")
+ for item in items:
+ formula = formula.replace(item, " ")
+ deps = formula.split(" ")
+ geo_namespace = [f"'{dep}': {dep}_geom" for dep in deps if dep != '']
+ nb_geo_ns = f"{pad}{name}.geo_namespace = " + "{" + f"{', '.join(geo_namespace)}" + "}"
+ geometries.append(nb_geo_ns)
+ if len(geometries) > 2:
+ nb_domain.insert(index, '\n'.join(geometries))
+ index += 1
+ for i, s_act in enumerate(actions):
+ args = f"os.path.join(home, '{s_act['filename']}')"
+ if s_act['type'] != "StochSS Domain" and s_act['subdomainFile'] != "":
+ args = f"{args},\n{pad*2}subdomain_file=os.path.join(home, '{s_act['subdomainFile']}')"
+ nb_latt = lat_tmp.replace("__NAME__", f"ipa_lattice{i+1}")
+ nb_latt = nb_latt.replace("__CLASS__", class_map[s_act['type']]).replace("__ARGS__", args)
+ lattices.append(nb_latt)
+ if len(lattices) > 2:
+ nb_domain.insert(index, '\n'.join(lattices))
+ index += 1
except KeyError as err:
- message = "Rules are not properly formatted or "
- message += f"are referenced incorrectly for notebooks: {str(err)}"
+ message = "The domain shape is not properly formatted or "
+ message += f"is referenced incorrectly for notebooks: {str(err)}"
raise StochSSModelFormatError(message, traceback.format_exc()) from err
+ return index
-
- def __create_species_strings(self, model, pad, type_refs=None):
- if self.s_model['species']:
- species = ["", f"{pad}# Variables"]
+ def __create_spatial_species(self, nb_model, index, type_refs):
+ if len(self.s_model['species']) > 0:
+ pad = ' '
+ args_tmp = "name='__NAME__', diffusion_coefficient=__VALUE____RESTRICT_TO__"
+ tmp = f"{pad}__NAME__ = spatialpy.Species({args_tmp})"
names = []
+ n_len = 8
+ species = ["", f"{pad}# Variables"]
try:
- for spec in self.s_model['species']:
- names.append(spec["name"])
- spec_str = f'{pad}{spec["name"]} = Species(name="{spec["name"]}", '
- if self.s_model['is_spatial']:
- types = [f"self.{type_refs[d_type].upper()}" for d_type in spec['types']]
- spec_str += f"diffusion_coefficient={spec['diffusionConst']}, "
- spec_str += f"restrict_to=[{', '.join(types)}])"
+ for s_species in self.s_model['species']:
+ n_len, n_names = self.__add_name(names, n_len, s_species['name'])
+ if n_names is not None:
+ names = n_names
+ nb_spec = tmp.replace("__NAME__", s_species['name'])
+ nb_spec = nb_spec.replace("__VALUE__", str(s_species['diffusionConst']))
+ if len(s_species['types']) == len(type_refs):
+ nb_spec = nb_spec.replace("__RESTRICT_TO__", "")
else:
- spec_str += f'initial_value={spec["value"]}, mode="{spec["mode"]}")'
- species.append(spec_str)
- species.append(f"{pad}self.add_species([{', '.join(names)}])")
- model.extend(species)
+ types = [type_refs[d_type] for d_type in s_species['types']]
+ restrict_to = f", restrict_to=[{', '.join(types)}]"
+ nb_spec = nb_spec.replace("__RESTRICT_TO__", restrict_to)
+ species.append(nb_spec)
+ species.append(f"{pad}model.add_species([\n{pad*2}{', '.join(names)}\n{pad}])")
+ nb_model.insert(index, '\n'.join(species))
+ index += 1
except KeyError as err:
- message = "Species are not properly formatted or "
- message += f"are referenced incorrectly for notebooks: {str(err)}"
+ message = f"Species are not properly formatted or are referenced incorrectly for notebooks: {str(err)}"
raise StochSSModelFormatError(message, traceback.format_exc()) from err
+ return index
+
+ def __create_spatial_transformations(self, nb_domain, index):
+ if len(self.s_model['domain']['transformations']) > 0:
+ class_map = {
+ 'Translate Transformation': 'TranslationTransformation',
+ 'Rotate Transformation': 'RotationTransformation',
+ 'Reflect Transformation': 'ReflectionTransformation',
+ 'Scale Transformation': 'ScalingTransformation'
+ }
+ pad = ' '
+ tmp = f"{pad}__NAME__ = spatialpy.__CLASS__(\n{pad*2}__ARGS__\n{pad})"
+ nested_trans = {}
+ transformations = ["", f"{pad}# Domain Transformations"]
+ try:
+ for s_tran in self.s_model['domain']['transformations']:
+ t_class = class_map[s_tran['type']]
+ nb_tran = tmp.replace("__NAME__", s_tran['name']).replace("__CLASS__", t_class)
+ if s_tran['transformation'] != "":
+ nested_trans[s_tran['name']] = s_tran['transformation']
+ if t_class in ("TranslationTransformation", "RotationTransformation"):
+ point1 = f"[{s_tran['vector'][0]['x']}, {s_tran['vector'][0]['y']}, {s_tran['vector'][0]['z']}]"
+ point2 = f"[{s_tran['vector'][1]['x']}, {s_tran['vector'][1]['y']}, {s_tran['vector'][1]['z']}]"
+ angle = "" if t_class == "TranslationTransformation" else f", {s_tran['angle']}"
+ args = f"({point1}, {point2}){angle}"
+ elif t_class == "ScalingTransformation":
+ center = f"[{s_tran['center']['x']}, {s_tran['center']['y']}, {s_tran['center']['z']}]"
+ args = f"{s_tran['factor']}, center={center}"
+ else:
+ point1 = f"[{s_tran['point1']['x']}, {s_tran['point1']['y']}, {s_tran['point1']['z']}]"
+ if self.__check_reflect_mathod(s_tran) == "Point-Normal":
+ normal = f"[{s_tran['normal']['x']}, {s_tran['normal']['y']}, {s_tran['normal']['z']}]"
+ args = f"{point1}, normal={normal}"
+ else:
+ point2 = f"[{s_tran['point2']['x']}, {s_tran['point2']['y']}, {s_tran['point2']['z']}]"
+ point3 = f"[{s_tran['point3']['x']}, {s_tran['point3']['y']}, {s_tran['point3']['z']}]"
+ args = f"{point1}, point2={point2}, point3={point3}"
+ nb_tran = nb_tran.replace("__ARGS__", args)
+ transformations.append(nb_tran)
+ if len(nested_trans) > 0:
+ transformations.append("")
+ for tran, nested_tran in nested_trans.items():
+ transformations.append(f"{pad}{tran}.transformation = {nested_tran}")
+ nb_domain.insert(index, '\n'.join(transformations))
+ index += 1
+ except KeyError as err:
+ message = "The domain transformation is not properly formatted or "
+ message += f"is referenced incorrectly for notebooks: {str(err)}"
+ raise StochSSModelFormatError(message, traceback.format_exc()) from err
+ return index
-
- def __create_stoich_spec_string(self, stoich_species):
+ @classmethod
+ def __create_stoich_species(cls, stoich_species):
species = {}
for stoich_spec in stoich_species:
name = stoich_spec['specie']['name']
@@ -428,64 +705,213 @@ def __create_stoich_spec_string(self, stoich_species):
species[name] += stoich_spec['ratio']
else:
species[name] = stoich_spec['ratio']
- spec_list = []
- for name, ratio in species.items():
- spec_list.append(f"'{name}': {ratio}")
- return "{" + ", ".join(spec_list) + "}"
+ return str(species)
+ def __create_well_mixed_events(self, nb_model, index):
+ if self.s_model['eventsCollection']:
+ pad = ' '
+ l1_args = f"{pad*2}name='__NAME__', trigger=__TRIGGER__,"
+ l2_args = f"{pad*2}assignments=__ASSIGNMENTS__,"
+ l3_args = f"{pad*2}delay=__DELAY__, priority='__PRIORITY__',"
+ l4_args = f"{pad*2}use_values_from_trigger_time=__UVFTT__"
+ tmp = f"{pad}__NAME__ = gillespy2.Event(\n{l1_args}\n{l2_args}\n{l3_args}\n{l4_args}\n{pad})"
+ names = []
+ n_len = 8
+ triggers = ["", f"{pad}# Event Triggers"]
+ assignments = ["", f"{pad}# Event Assignments"]
+ events = ["", f"{pad}# Events"]
+ try:
+ for s_event in self.s_model['eventsCollection']:
+ t_name, nb_trigger = self.__create_well_mixed_event_trigger(s_event)
+ triggers.append(nb_trigger)
+ a_names, nb_assignments = self.__create_well_mixed_event_assignments(s_event)
+ assignments.extend(nb_assignments)
+ delay = "None" if s_event['delay'] in (None, "") else f"'{s_event['delay']}'"
+ n_len, n_names = self.__add_name(names, n_len, s_event['name'])
+ if n_names is not None:
+ names = n_names
+ nb_event = tmp.replace("__NAME__", s_event['name']).replace("__TRIGGER__", t_name)
+ nb_event = nb_event.replace("__ASSIGNMENTS__", a_names).replace("__DELAY__", delay)
+ nb_event = nb_event.replace("__PRIORITY__", s_event['priority'])
+ nb_event = nb_event.replace("__UVFTT__", str(s_event['useValuesFromTriggerTime']))
+ events.append(nb_event)
+ events.append(f"{pad}model.add_event([\n{pad*2}{', '.join(names)}\n{pad}])")
+ nb_model.insert(index, '\n'.join(triggers))
+ index += 1
+ nb_model.insert(index, '\n'.join(assignments))
+ index += 1
+ nb_model.insert(index, '\n'.join(events))
+ index += 1
+ except KeyError as err:
+ message = f"Events are not properly formatted or are referenced incorrectly for notebooks: {str(err)}"
+ raise StochSSModelFormatError(message, traceback.format_exc()) from err
+ return index
- def __create_tspan_string(self, model, pad):
- end = self.s_model['modelSettings']['endSim']
- output_freq = self.s_model['modelSettings']['timeStep']
- tspan = ["", f"{pad}# Timespan"]
- if self.s_model['is_spatial']:
- step_size = self.s_model['modelSettings']['timestepSize']
- ts_str = f'{pad}self.timespan(np.arange(0, {end + step_size}, {output_freq})'
- ts_str += f", timestep_size={step_size})"
- tspan.append(ts_str)
- else:
- tspan.append(f'{pad}tspan = TimeSpan(np.arange(0, {end + output_freq}, {output_freq}))')
- tspan.append(f'{pad}self.timespan(tspan)')
- model.extend(tspan)
+ def __create_well_mixed_event_assignments(self, s_event):
+ pad = ' '
+ args = f"{pad*2}variable='__VAR__',\n{pad*2}expression='__EXPRESSION__'"
+ tmp = f"{pad}__NAME__ = gillespy2.EventAssignment(\n{args}\n{pad})"
+ names = []
+ n_len = 8
+ assignments = []
+ for i, s_assignment in enumerate(s_event['eventAssignments']):
+ name = f"{s_event['name']}_assign_{i + 1}"
+ n_len, n_names = self.__add_name(names, n_len, name, max_len=87)
+ if n_names is not None:
+ names = n_names
+ nb_assignment = tmp.replace("__NAME__", name)
+ nb_assignment = nb_assignment.replace("__VAR__", s_assignment['variable']['name'])
+ nb_assignment = nb_assignment.replace("__EXPRESSION__", s_assignment['expression'])
+ assignments.append(nb_assignment)
+ return f"[{', '.join(names)}]", assignments
+ @classmethod
+ def __create_well_mixed_event_trigger(cls, s_event):
+ pad = ' '
+ name = f"{s_event['name']}_trig"
+ persistent = s_event['persistent']
+ l1_args = f"{pad*2}expression='{s_event['triggerExpression']}',"
+ l2_args = f"{pad*2}initial_value={s_event['initialValue']}, persistent={persistent}"
+ nb_trig = f"{pad}{name} = gillespy2.EventTrigger(\n{l1_args}\n{l2_args}\n{pad})"
+ return name, nb_trig
+
+ def __create_well_mixed_function_definition(self, nb_model, index):
+ if self.s_model['functionDefinitions']:
+ pad = ' '
+ args_tmp = f"{pad*2}name='__NAME__', args=__ARGS__,\n{pad*2}function='__FUNCTION__'"
+ tmp = f"{pad}__NAME__ = gillespy2.FunctionDefinition(\n{args_tmp}\n{pad})"
+ names = []
+ n_len = 8
+ func_defs = ["", f"{pad}# Function Definitions"]
+ try:
+ for s_func_def in self.s_model['functionDefinitions']:
+ args = str(s_func_def['variables'].split(','))
+ n_len, n_names = self.__add_name(names, n_len, s_func_def['name'])
+ if n_names is not None:
+ names = n_names
+ nb_func_def = tmp.replace("__NAME__", s_func_def['name']).replace("__ARGS__", args)
+ nb_func_def = nb_func_def.replace("__FUNCTION__", s_func_def['expression'])
+ func_defs.append(nb_func_def)
+ func_defs.append(f"{pad}model.add_function_definition([\n{pad*2}{', '.join(names)}\n{pad}])")
+ nb_model.insert(index, '\n'.join(func_defs))
+ index += 1
+ except KeyError as err:
+ message = "Function definitions are not properly formatted or "
+ message += f"are referenced incorrectly for notebooks: {str(err)}"
+ raise StochSSModelFormatError(message, traceback.format_exc()) from err
+ return index
- def __get_gillespy2_run_settings(self):
- is_automatic = self.settings['simulationSettings']['isAutomatic']
- if self.nb_type in (self.PARAMETER_SWEEP_1D, self.PARAMETER_SWEEP_2D) and is_automatic:
- self.settings['simulationSettings']['realizations'] = 20
- settings = self.settings['simulationSettings']
- if settings['algorithm'] == "ODE":
- self.settings['simulationSettings']['realizations'] = 1
- settings['realizations'] = 1
- # Map algorithm for GillesPy2
- tau_hybrid_solver = self.model.get_best_solver_algo("Tau-Hybrid").name
- solver_map = {
- "ODE":f'"solver":{"solver" if self.is_ssa_c else "ODESolver"}',
- "SSA":f'"solver":{"solver" if self.is_ssa_c else "NumPySSASolver"}',
- "CLE":'"solver": CLESolver',
- "Tau-Leaping":f'"solver":{"solver" if self.is_ssa_c else "TauLeapingSolver"}',
- "Hybrid-Tau-Leaping":f'"solver":{"solver" if "CSolver" in tau_hybrid_solver else "TauHybridSolver"}'
- }
- # Map algorithm settings for GillesPy2. GillesPy2 requires snake case, remap camelCase
- settings_map = {"number_of_trajectories":settings['realizations'],
- "seed":settings['seed'] if settings['seed'] != -1 else None,
- "tau_tol":settings['tauTol'],
- "integrator_options":str({"rtol":settings['relativeTol'],
- "atol":settings['absoluteTol']})}
- #Parse settings for algorithm
- run_settings = [solver_map[settings['algorithm']]]
- algorithm_settings = [f'"{key}":{val}' for key, val in settings_map.items()]
- run_settings.extend(algorithm_settings)
- return run_settings
+ def __create_well_mixed_model_cell(self, func_name):
+ pad = ' '
+ frequency = self.s_model['modelSettings']['timeStep']
+ end = self.s_model['modelSettings']['endSim']
+ nb_model = [
+ f"def {func_name}(parameter_values=None):",
+ f"{pad}model = gillespy2.Model(name='{self.s_model['name']}')",
+ f"{pad}model.volume = {self.s_model['volume']}", "", f"{pad}# Timespan",
+ f"{pad}tspan = gillespy2.TimeSpan.arange({frequency}, t={end})",
+ f"{pad}model.timespan(tspan)", f"{pad}return model"
+ ]
+ index = self.__create_well_mixed_species(nb_model, 3)
+ index = self.__create_parameters(nb_model, index)
+ index = self.__create_reactions(nb_model, index)
+ index = self.__create_well_mixed_rules(nb_model, index)
+ index = self.__create_well_mixed_events(nb_model, index)
+ index = self.__create_well_mixed_function_definition(nb_model, index)
+ return nbf.new_code_cell("\n".join(nb_model))
+
+ def __create_well_mixed_species(self, nb_model, index):
+ if len(self.s_model['species']) > 0:
+ pad = ' '
+ args_tmp = "name='__NAME__', initial_value=__VALUE__, mode='__MODE__'"
+ tmp = f"{pad}__NAME__ = gillespy2.Species({args_tmp})"
+ names = []
+ n_len = 8
+ species = ["", f"{pad}# Variables"]
+ try:
+ for s_species in self.s_model['species']:
+ n_len, n_names = self.__add_name(names, n_len, s_species['name'])
+ if n_names is not None:
+ names = n_names
+ nb_spec = tmp.replace("__NAME__", s_species['name'])
+ nb_spec = nb_spec.replace("__VALUE__", str(s_species['value']))
+ nb_spec = nb_spec.replace("__MODE__", s_species['mode'])
+ species.append(nb_spec)
+ species.append(f"{pad}model.add_species([\n{pad*2}{', '.join(names)}\n{pad}])")
+ nb_model.insert(index, '\n'.join(species))
+ index += 1
+ except KeyError as err:
+ message = f"Species are not properly formatted or are referenced incorrectly for notebooks: {str(err)}"
+ raise StochSSModelFormatError(message, traceback.format_exc()) from err
+ return index
+ def __create_well_mixed_rules(self, nb_model, index):
+ if self.s_model['rules']:
+ pad = ' '
+ args_tmp = f"{pad*2}name='__NAME__', variable='__VAR__',\n{pad*2}formula='__FORMULA__'"
+ tmp = f"{pad}__NAME__ = gillespy2.__TYPE__(\n{args_tmp}\n{pad})"
+ rr_names = []
+ ar_names = []
+ rr_n_len = 8
+ ar_n_len = 8
+ rate_rules = ["", f"{pad}# Rate Rules"]
+ assignment_rules = ["", f"{pad}# Assignment Rules"]
+ try:
+ for s_rule in self.s_model['rules']:
+ nb_rule = tmp.replace("__NAME__", s_rule["name"])
+ nb_rule = nb_rule.replace("__FORMULA__", s_rule["expression"])
+ nb_rule = nb_rule.replace("__VAR__", s_rule["variable"]["name"])
+ if s_rule['type'] == "Rate Rule":
+ rr_n_len, n_names = self.__add_name(rr_names, rr_n_len, s_rule['name'])
+ if n_names is not None:
+ rr_names = n_names
+ nb_rule = nb_rule.replace("__TYPE__", "RateRule")
+ rate_rules.append(nb_rule)
+ else:
+ ar_n_len, n_names = self.__add_name(ar_names, ar_n_len, s_rule['name'])
+ if n_names is not None:
+ ar_names = n_names
+ nb_rule = nb_rule.replace("__TYPE__", "AssignmentRule")
+ assignment_rules.append(nb_rule)
+ if len(rate_rules) > 2:
+ rate_rules.append(f"{pad}model.add_rate_rule([\n{pad*2}{', '.join(rr_names)}\n{pad}])")
+ nb_model.insert(index, '\n'.join(rate_rules))
+ index += 1
+ if len(assignment_rules) > 2:
+ assignment_rules.append(
+ f"{pad}model.add_assignment_rule([\n{pad*2}{', '.join(ar_names)}\n{pad}])"
+ )
+ nb_model.insert(index, '\n'.join(assignment_rules))
+ index += 1
+ except KeyError as err:
+ message = f"Rules are not properly formatted or are referenced incorrectly for notebooks: {str(err)}"
+ raise StochSSModelFormatError(message, traceback.format_exc()) from err
+ return index
- def __get_spatialpy_run_setting(self):
- self.settings['simulationSettings']['realizations'] = 1
+ def __get_gillespy2_run_settings(self, use_solver=False):
settings = self.settings['simulationSettings']
- settings_map = {"number_of_trajectories":settings['realizations'],
- "seed":settings['seed'] if settings['seed'] != -1 else None}
- return [f'"{key}":{val}' for key, val in settings_map.items()]
+ # Map algorithm settings for GillesPy2. GillesPy2 requires snake case, remap camelCase
+ settings_map = {
+ "number_of_trajectories": settings['realizations'],
+ "seed": settings['seed'] if settings['seed'] != -1 else None,
+ "tau_tol": settings['tauTol'],
+ "integrator_options": str({"rtol":settings['relativeTol'], "atol":settings['absoluteTol']})
+ }
+ if use_solver:
+ settings_map['solver'] = "solver"
+ elif settings['algorithm'] == "Hybrid-Tau-Leaping":
+ settings_map['algorithm'] = "'Tau-Hybrid'"
+ else:
+ settings_map['algorithm'] = f"'{settings['algorithm']}'"
+ return settings_map
+ def __get_object_types(self):
+ types = sorted(self.s_model['domain']['types'], key=lambda d_type: d_type['typeID'])
+ type_map = {}
+ for d_type in types:
+ name = f"N_{d_type['name']}" if d_type['name'].isdigit() else d_type['name'].upper()
+ type_map[d_type['typeID']] = f"model.{name}"
+ return type_map
@classmethod
def __get_presentation_links(cls, hostname, file):
@@ -496,74 +922,80 @@ def __get_presentation_links(cls, hostname, file):
open_link = f"https://open.stochss.org?open={download_link}"
return {"presentation": present_link, "download": download_link, "open": open_link}
+ def __get_spatialpy_run_setting(self):
+ settings = self.settings['simulationSettings']
+ return {
+ "number_of_trajectories":settings['realizations'],
+ "seed":settings['seed'] if settings['seed'] != -1 else None
+ }
def create_common_cells(self):
''' Create the cells common to all notebook types. '''
- cells = [self.__create_import_cell(),
- nbf.new_markdown_cell(f"# {self.get_name()}"),
- self.__create_model_cell(),
- nbf.new_code_cell(f'model = {self.get_class_name()}()'),
- nbf.new_markdown_cell("# Simulation Parameters"),
- self.__create_configuration_cell()]
+ func_name = self.get_function_name()
+ cells = self.__create_common_header_cells()
+ cells.insert(3, nbf.new_code_cell(f"model = {func_name}()"))
+ if self.nb_type == self.SPATIAL_SIMULATION:
+ cells.insert(1, nbf.new_code_cell("import spatialpy"))
+ index = self.__create_spatial_boundary_condition_cells(cells)
+ index = self.__create_spatial_geometry_cells(cells, index)
+ cells.insert(index + 1, self.__create_spatial_model_cell(func_name))
+ else:
+ cells.insert(1, nbf.new_code_cell("import gillespy2"))
+ cells.insert(3, self.__create_well_mixed_model_cell(func_name))
+ cells.append(self.__create_configuration_cell())
return cells
-
- def create_es_notebook(self):
- '''Create an ensemble simulation jupiter notebook for a StochSS model/workflow
-
- Attributes
- ----------'''
+ def create_es_notebook(self, results=None, compute="StochSS"):
+ '''Create an ensemble simulation jupiter notebook for a StochSS model/workflow '''
self.nb_type = self.ENSEMBLE_SIMULATION
- self.settings['solver'] = self.get_gillespy2_solver_name()
- run_str = "kwargs = configure_simulation()\nresults = model.run(**kwargs)"
cells = self.create_common_cells()
- cells.extend([nbf.new_code_cell(run_str),
- nbf.new_markdown_cell("# Visualization"),
- nbf.new_code_cell("results.plotplotly()")])
+
+ self.settings['solver'] = self.get_gillespy2_solver_name()
+ run_header, run_code = self.__create_run(results, compute=compute)
+ vis_header = nbf.new_markdown_cell("***\n## Visualization\n***")
+ vis_code = nbf.new_code_cell("results.plotplotly()")
+ cells.extend([run_header, run_code, vis_header, vis_code])
+ if compute != "StochSS":
+ self.__create_compute_cells(cells, compute)
+ if results is not None:
+ cells.insert(1, nbf.new_code_cell("import os\nimport pickle"))
message = self.write_notebook_file(cells=cells)
return {"Message":message, "FilePath":self.get_path(), "File":self.get_file()}
-
- def create_ses_notebook(self):
- '''Create a spetial ensemble simulation jupiter notebook for a StochSS model/workflow
-
- Attributes
- ----------'''
+ def create_ses_notebook(self, results=None, compute="StochSS"):
+ '''Create a spetial ensemble simulation jupiter notebook for a StochSS model/workflow '''
self.nb_type = self.SPATIAL_SIMULATION
+ cells = self.create_common_cells()
+
self.settings['solver'] = "Solver"
- run_str = "kwargs = configure_simulation()\nresults = model.run(**kwargs)"
- if self.s_model['species']:
- species = self.s_model['species'][0]['name']
- plot_str = f"results.plot_species('{species}', animated=True, width=None, height=None)"
+ if results is not None:
+ cells.insert(1, nbf.new_code_cell("import os\nimport pickle"))
+ run_header, run_code = self.__create_run(results)
+ vis_header = nbf.new_markdown_cell("***\n## Visualization\n***")
+ plt_args = "animated=True, width='auto', height='auto'"
+ if len(self.s_model['species']) > 0:
+ plot_str = f"results.plot_species('{self.s_model['species'][0]['name']}', {plt_args})"
else:
- plot_str = "results.plot_property('type', animated=True, width=None, height=None)"
- cells = [nbf.new_code_cell("%load_ext autoreload\n%autoreload 2")]
- cells.extend(self.create_common_cells())
- if 'boundaryConditions' in self.s_model.keys():
- bc_cells = self.__create_boundary_condition_cells()
- for i, bc_cell in enumerate(bc_cells):
- cells.insert(2 + i, bc_cell)
- cells.extend([nbf.new_code_cell(run_str),
- nbf.new_markdown_cell("# Visualization"),
- nbf.new_code_cell(plot_str)])
+ plot_str = f"results.plot_property('type', {plt_args})"
+ vis_code = nbf.new_code_cell(plot_str)
+ cells.extend([run_header, run_code, vis_header, vis_code])
+ if compute != "StochSS":
+ self.log(
+ "warning",
+ "AWS Cloud compute environment is not supported by spatial ensemble simulation workflows."
+ )
message = self.write_notebook_file(cells=cells)
return {"Message":message, "FilePath":self.get_path(), "File":self.get_file()}
-
- def get_class_name(self):
+ def get_function_name(self):
''' Get the python style class name, '''
- name = self.get_name()
+ name = self.s_model['name'].lower()
for char in string.punctuation:
- name = name.replace(char, "")
- l_char = name[0]
- if l_char in string.digits:
- return f"M{name}"
- if l_char in string.ascii_lowercase:
- return name.replace(l_char, l_char.upper(), 1)
- return name.replace(" ", "")
-
+ if char != "_":
+ name = name.replace(char, "")
+ return f"create_{name.replace(' ', '_')}"
def get_gillespy2_solver_name(self):
''' Get the name of the gillespy2 solver. '''
@@ -578,10 +1010,8 @@ def get_gillespy2_solver_name(self):
'Tau-Leaping': self.model.get_best_solver_algo("Tau-Leaping").name,
'Hybrid-Tau-Leaping': self.model.get_best_solver_algo("Tau-Hybrid").name
}
-
return algorithm_map[self.settings['simulationSettings']['algorithm']]
-
def load(self):
'''Read the notebook file and return as a dict'''
try:
@@ -610,8 +1040,7 @@ def publish_presentation(self):
exists = False
with open(dst, "w", encoding="utf-8") as presentation_file:
json.dump(notebook_pres, presentation_file)
- links = self.__get_presentation_links(hostname, file)
- return links, exists
+ return self.__get_presentation_links(hostname, file), exists
except PermissionError as err:
message = f"You do not have permission to publish this file: {str(err)}"
raise StochSSPermissionsError(message, traceback.format_exc()) from err
diff --git a/stochss/handlers/util/stochss_project.py b/stochss/handlers/util/stochss_project.py
index 9c11dd4c2b..67d2e7fb29 100644
--- a/stochss/handlers/util/stochss_project.py
+++ b/stochss/handlers/util/stochss_project.py
@@ -1,6 +1,6 @@
'''
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/stochss/handlers/util/stochss_sbml.py b/stochss/handlers/util/stochss_sbml.py
index abe62155f4..0331d0ea08 100644
--- a/stochss/handlers/util/stochss_sbml.py
+++ b/stochss/handlers/util/stochss_sbml.py
@@ -1,6 +1,6 @@
'''
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/stochss/handlers/util/stochss_spatial_model.py b/stochss/handlers/util/stochss_spatial_model.py
index 8594597f7d..dc0bb35e2c 100644
--- a/stochss/handlers/util/stochss_spatial_model.py
+++ b/stochss/handlers/util/stochss_spatial_model.py
@@ -1,6 +1,6 @@
'''
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -15,24 +15,28 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
'''
-
import os
import json
import copy
import string
import hashlib
-import tempfile
import traceback
+import numpy
import plotly
from escapism import escape
from spatialpy import Model, Species, Parameter, Reaction, Domain, DomainError, BoundaryCondition, \
PlaceInitialCondition, UniformInitialCondition, ScatterInitialCondition, \
- Geometry, ModelError, TimeSpan
+ TimeSpan, Geometry, GeometryAll, GeometryExterior, GeometryInterior, \
+ CombinatoryGeometry, CartesianLattice, SphericalLattice, CylindricalLattice, \
+ XMLMeshLattice, MeshIOLattice, StochSSLattice, TranslationTransformation, \
+ RotationTransformation, ReflectionTransformation, ScalingTransformation, \
+ ModelError
from .stochss_base import StochSSBase
from .stochss_errors import StochSSFileNotFoundError, FileNotJSONFormatError, DomainFormatError, \
- StochSSModelFormatError, StochSSPermissionsError, DomainGeometryError
+ StochSSModelFormatError, StochSSPermissionsError, DomainUpdateError, \
+ DomainActionError, DomainShapeError, DomainTransformationError
class StochSSSpatialModel(StochSSBase):
'''
@@ -40,7 +44,6 @@ class StochSSSpatialModel(StochSSBase):
StochSS spatial model object
################################################################################################
'''
-
def __init__(self, path, new=False, model=None):
'''
Intitialize a spatial model object and if its new create it on the users file system
@@ -73,7 +76,6 @@ def __init__(self, path, new=False, model=None):
else:
self.model = None
-
@classmethod
def __build_boundary_condition(cls, boundary_condition):
class NewBC(BoundaryCondition): # pylint: disable=too-few-public-methods
@@ -87,37 +89,32 @@ def __init__(self):
pass
def expression(self): # pylint: disable=no-self-use
- '''
- Custom expression for boundary condition
- '''
+ ''' Custom expression for boundary condition. '''
return boundary_condition['expression']
return NewBC()
-
@classmethod
- def __build_geometry(cls, d_type, center):
- name = d_type['name'].title()
- class NewG(Geometry): # pylint: disable=too-few-public-methods
+ def __build_geometry(cls, geometry, name=None, formula=None):
+ if formula is None:
+ formula = geometry['formula']
+ if name is None:
+ name = geometry['name']
+
+ class NewGeometry(Geometry): # pylint: disable=too-few-public-methods
'''
########################################################################################
Custom SpatialPy Geometry
########################################################################################
'''
__class__ = f"__main__.{name}"
- def __init__(self, center):
- self.center = center
+ def __init__(self):
+ pass
def inside(self, point, on_boundary): # pylint: disable=no-self-use
- '''
- Custom inside for geometry
- '''
- namespace = {
- 'cx': self.center[0], 'cy': self.center[1], 'cz': self.center[2],
- 'x': point[0], 'y': point[1], 'z': point[2]
- }
- return eval(d_type['geometry'], {}, namespace)
- return NewG(center)
-
+ ''' Custom inside function for geometry. '''
+ namespace = {'x': point[0], 'y': point[1], 'z': point[2], 'on_boundary': on_boundary}
+ return eval(formula, {}, namespace)
+ return NewGeometry()
@classmethod
def __build_stochss_domain(cls, s_domain, data=None):
@@ -131,20 +128,16 @@ def __build_stochss_domain(cls, s_domain, data=None):
"x_lim":s_domain.xlim,
"y_lim":s_domain.ylim,
"z_lim":s_domain.zlim,
- "types":[{"fixed":False,
- "mass":1.0,
- "name":"Un-Assigned",
- "nu":0.0,
- "typeID":0,
- "volume":1.0}],
+ "types":[{
+ "fixed":False, "mass":1.0, "name":"Un-Assigned",
+ "nu":0.0, "typeID":0, "volume":1.0
+ }],
"boundary_condition": {
- "reflect_x": True,
- "reflect_y": True,
- "reflect_z": True},
+ "reflect_x": True, "reflect_y": True, "reflect_z": True
+ },
"particles":particles}
return domain
-
@classmethod
def __build_stochss_domain_particles(cls, s_domain, data=None):
particles = []
@@ -172,6 +165,96 @@ def __build_stochss_domain_particles(cls, s_domain, data=None):
particles.append(particle)
return particles
+ def __convert_actions(self, domain, s_domain, type_ids):
+ geometries, lattices = self.__convert_shapes(s_domain)
+ transformations = self.__convert_transformations(s_domain)
+ try:
+ actions = list(filter(lambda action: action['enable'], s_domain['actions']))
+ actions = sorted(actions, key=lambda action: action['priority'])
+ for i, action in enumerate(actions):
+ # Build props arg
+ if action['type'] in ('Fill Action', 'Set Action', 'XML Mesh', 'Mesh IO'):
+ kwargs = {
+ 'mass': action['mass'], 'vol': action['vol'], 'rho': action['rho'],
+ 'nu': action['nu'], 'c': action['c'], 'fixed': action['fixed']
+ }
+ if action['type'] in ('Fill Action', 'Set Action'):
+ kwargs['type_id'] = type_ids[action['typeID']].replace("-", "")
+ else:
+ kwargs = {}
+ # Apply actions
+ if action['type'] == "Fill Action":
+ if action['scope'] == 'Multi Particle':
+ geometry = geometries[f"{action['shape']}_geom"]
+ if action['transformation'] == "":
+ lattice = lattices[f"{action['shape']}_latt"]
+ else:
+ lattice = transformations[action['transformation']]
+ lattice.lattice = lattices[f"{action['shape']}_latt"]
+ _ = domain.add_fill_action(
+ lattice=lattice, geometry=geometry, enable=action['enable'],
+ apply_action=action['enable'], **kwargs
+ )
+ else:
+ point = [action['point']['x'], action['point']['y'], action['point']['z']]
+ domain.add_point(point, **kwargs)
+ elif action['type'] in ('XML Mesh', 'Mesh IO', 'StochSS Domain'):
+ lattices = {'XML Mesh': XMLMeshLattice, 'Mesh IO': MeshIOLattice, 'StochSS Domain': StochSSLattice}
+ filename = os.path.join(self.user_dir, action['filename'])
+ if action['type'] == "StochSS Domain" or action['subdomainFile'] == "":
+ lattice = lattices[action['type']](filename)
+ else:
+ subdomain_file = os.path.join(self.user_dir, action['subdomainFile'])
+ lattice = lattices[action['type']](filename, subdomain_file=subdomain_file)
+ _ = domain.add_fill_action(
+ lattice=lattice, enable=action['enable'], apply_action=action['enable'], **kwargs
+ )
+ else:
+ # Get proper geometry for scope
+ # 'Single Particle' scope creates a geometry using actions point.
+ if action['scope'] == 'Single Particle':
+ p_x = action['point']['x']
+ p_y = action['point']['y']
+ p_z = action['point']['z']
+ formula = f"x == {p_x} and y == {p_y} and z == {p_z}"
+ geometry = self.__build_geometry(
+ None, name=f"SPAGeometry{i + 1}", formula=formula
+ )
+ elif action['transformation'] == "":
+ geometry = geometries[f"{action['shape']}_geom"]
+ else:
+ geometry = transformations[action['transformation']]
+ geometry.geometry = geometries[f"{action['shape']}_geom"]
+ if action['type'] == "Set Action":
+ domain.add_set_action(
+ geometry=geometry, enable=action['enable'],
+ apply_action=action['enable'], **kwargs
+ )
+ if action['scope'] == "Single Particle":
+ curr_pnt = numpy.array(
+ [action['point']['x'], action['point']['y'], action['point']['z']]
+ )
+ new_pnt = numpy.array([
+ action['newPoint']['x'], action['newPoint']['y'],
+ action['newPoint']['z']
+ ])
+ if numpy.count_nonzero(curr_pnt - new_pnt) > 0:
+ for j, vertex in enumerate(domain.vertices):
+ if numpy.count_nonzero(curr_pnt - vertex) <= 0:
+ domain.vertices[j] = new_pnt
+ break
+ else:
+ domain.add_remove_action(
+ geometry=geometry, enable=action['enable'],
+ apply_action=action['enable']
+ )
+ except KeyError as err:
+ message = "Spatial actions are not properly formatted or "
+ message += f"are referenced incorrectly: {str(err)}"
+ raise StochSSModelFormatError(message, traceback.format_exc()) from err
+ except ModelError as err:
+ message = f"One or more domain actions are invalid: {err}"
+ raise DomainActionError(message, traceback.format_exc()) from err
def __convert_boundary_conditions(self, model):
try:
@@ -183,28 +266,33 @@ def __convert_boundary_conditions(self, model):
message += f"are referenced incorrectly: {str(err)}"
raise StochSSModelFormatError(message, traceback.format_exc()) from err
-
- def __convert_domain(self, model, type_ids):
+ def __convert_domain(self, type_ids, model=None, s_domain=None):
try:
- xlim = tuple(self.model['domain']['x_lim'])
- ylim = tuple(self.model['domain']['y_lim'])
- zlim = tuple(self.model['domain']['z_lim'])
- rho0 = self.model['domain']['rho_0']
- c_0 = self.model['domain']['c_0']
- p_0 = self.model['domain']['p_0']
- gravity = self.model['domain']['gravity']
+ if s_domain is None:
+ s_domain = self.model['domain']
+ xlim = tuple(s_domain['x_lim'])
+ ylim = tuple(s_domain['y_lim'])
+ zlim = tuple(s_domain['z_lim'])
+ rho0 = s_domain['rho_0']
+ c_0 = s_domain['c_0']
+ p_0 = s_domain['p_0']
+ gravity = s_domain['gravity']
if gravity == [0, 0, 0]:
gravity = None
domain = Domain(0, xlim, ylim, zlim, rho0=rho0, c0=c_0, P0=p_0, gravity=gravity)
- self.__convert_particles(domain=domain, type_ids=type_ids)
- model.add_domain(domain)
+ self.__convert_actions(domain, s_domain, type_ids)
+ if model is None:
+ self.__convert_types(domain, type_ids)
+ return domain
+ model.add_domain(domain, allow_all_types=True)
+ self.__convert_types(model.domain, type_ids)
model.staticDomain = self.model['domain']['static']
+ return None
except KeyError as err:
message = "Spatial model domain properties are not properly formatted or "
message += f"are referenced incorrectly: {str(err)}"
raise StochSSModelFormatError(message, traceback.format_exc()) from err
-
def __convert_initial_conditions(self, model, type_ids):
try:
s_species = model.get_all_species()
@@ -215,9 +303,9 @@ def __convert_initial_conditions(self, model, type_ids):
types = [type_ids[d_type] for d_type in initial_condition['types']]
s_ic = ScatterInitialCondition(species, count, types=types)
elif initial_condition['icType'] == "Place":
- location = [initial_condition['x'],
- initial_condition['y'],
- initial_condition['z']]
+ location = [
+ initial_condition['x'], initial_condition['y'], initial_condition['z']
+ ]
s_ic = PlaceInitialCondition(species, count, location)
else:
types = [type_ids[d_type] for d_type in initial_condition['types']]
@@ -228,7 +316,6 @@ def __convert_initial_conditions(self, model, type_ids):
message += f"are referenced incorrectly: {str(err)}"
raise StochSSModelFormatError(message, traceback.format_exc()) from err
-
def __convert_model_settings(self, model):
try:
end = self.model['modelSettings']['endSim']
@@ -241,7 +328,6 @@ def __convert_model_settings(self, model):
message += f"are referenced incorrectly: {str(err)}"
raise StochSSModelFormatError(message, traceback.format_exc()) from err
-
def __convert_parameters(self, model):
try:
for parameter in self.model['parameters']:
@@ -252,21 +338,6 @@ def __convert_parameters(self, model):
message += f"are referenced incorrectly: {str(err)}"
raise StochSSModelFormatError(message, traceback.format_exc()) from err
-
- def __convert_particles(self, domain, type_ids):
- try:
- for particle in self.model['domain']['particles']:
- domain.add_point(
- particle['point'], particle['volume'], particle['mass'],
- type_ids[particle['type']], particle['nu'], particle['fixed'],
- particle['rho'], particle['c']
- )
- except KeyError as err:
- message = "Spatial model domain particle properties are not properly formatted or "
- message += f"are referenced incorrectly: {str(err)}"
- raise StochSSModelFormatError(message, traceback.format_exc()) from err
-
-
def __convert_reactions(self, model, type_ids):
try:
s_params = model.get_all_parameters()
@@ -284,19 +355,81 @@ def __convert_reactions(self, model, type_ids):
types = [type_ids[d_type] for d_type in reaction['types']]
if len(types) == len(type_ids):
types = None
- s_reaction = Reaction(name=reaction['name'],
- reactants=reactants,
- products=products,
- rate=rate,
- propensity_function=propensity,
- ode_propensity_function=ode_propensity,
- restrict_to=types)
+ s_reaction = Reaction(
+ name=reaction['name'], reactants=reactants, products=products,
+ rate=rate, propensity_function=propensity,
+ ode_propensity_function=ode_propensity, restrict_to=types
+ )
model.add_reaction(s_reaction)
except KeyError as err:
message = "Spatial model reactions are not properly formatted or "
message += f"are referenced incorrectly: {str(err)}"
raise StochSSModelFormatError(message, traceback.format_exc()) from err
+ def __convert_shapes(self, s_domain):
+ try:
+ geometries = {}
+ comb_geoms = []
+ lattices = {}
+ for s_shape in s_domain['shapes']:
+ # Create geometry from shape
+ geo_name = f"{s_shape['name']}_geom"
+ if s_shape['type'] == "Standard":
+ if s_shape['formula'] in ("", "True"):
+ geometries[geo_name] = GeometryAll()
+ elif s_shape['formula'] == "on_boundary":
+ geometries[geo_name] = GeometryExterior()
+ elif s_shape['formula'] == "not on_boundary":
+ geometries[geo_name] = GeometryInterior()
+ else:
+ geometries[geo_name] = self.__build_geometry(None, name=geo_name, formula=s_shape['formula'])
+ else:
+ geometry = CombinatoryGeometry("", {})
+ geometry.formula = s_shape['formula']
+ geometries[geo_name] = geometry
+ comb_geoms.append(geo_name)
+ # Create lattice from shape if fillable
+ if s_shape['fillable']:
+ lat_name = f"{s_shape['name']}_latt"
+ if s_shape['lattice'] == "Cartesian Lattice":
+ half_length = s_shape['length'] / 2
+ half_height = s_shape['height'] / 2
+ half_depth = s_shape['depth'] / 2
+ lattice = CartesianLattice(
+ -half_length, half_length, s_shape['deltax'],
+ ymin=-half_height, ymax=half_height, deltay=s_shape['deltay'],
+ zmin=-half_depth, zmax=half_depth, deltaz=s_shape['deltaz']
+ )
+ elif s_shape['lattice'] == "Spherical Lattice":
+ lattice = SphericalLattice(
+ s_shape['radius'], s_shape['deltas'], deltar=s_shape['deltar']
+ )
+ elif s_shape['lattice'] == "Cylindrical Lattice":
+ lattice = CylindricalLattice(
+ s_shape['radius'], s_shape['length'], s_shape['deltas'], deltar=s_shape['deltar']
+ )
+ lattices[lat_name] = lattice
+ items = [' and ', ' or ', ' not ', '(', ')']
+ for name in comb_geoms:
+ formula = geometries[name].formula
+ if formula.startswith("not "):
+ formula = formula.replace("not ", "")
+ for item in items:
+ formula = formula.replace(item, " ")
+ formula = formula.split(" ")
+ geo_namespace = {}
+ for key, geometry in geometries.items():
+ if key != name and key[:-5] in formula:
+ geo_namespace[key[:-5]] = geometry
+ geometries[name].geo_namespace = geo_namespace
+ return geometries, lattices
+ except KeyError as err:
+ message = "Spatial domain shapes are not properly formatted or "
+ message += f"are referenced incorrectly: {str(err)}"
+ raise StochSSModelFormatError(message, traceback.format_exc()) from err
+ except ModelError as err:
+ message = f"One or more domain shapes are invalid: {err}"
+ raise DomainShapeError(message, traceback.format_exc()) from err
def __convert_species(self, model, type_ids):
try:
@@ -315,7 +448,6 @@ def __convert_species(self, model, type_ids):
message += f"are referenced incorrectly: {str(err)}"
raise StochSSModelFormatError(message, traceback.format_exc()) from err
-
@classmethod
def __convert_stoich_species(cls, model, reaction):
species = model.get_all_species()
@@ -342,9 +474,126 @@ def __convert_stoich_species(cls, model, reaction):
message += f"are referenced incorrectly: {str(err)}"
raise StochSSModelFormatError(message, traceback.format_exc()) from err
+ @classmethod
+ def __convert_transformations(cls, s_domain):
+ try:
+ transformations = {}
+ nested_trans = {}
+ for s_transformation in s_domain['transformations']:
+ name = s_transformation['name']
+ if s_transformation['transformation'] != "":
+ nested_trans[name] = s_transformation['transformation']
+ if s_transformation['type'] in ("Translate Transformation", "Rotate Transformation"):
+ vector = [
+ [
+ s_transformation['vector'][0]['x'],
+ s_transformation['vector'][0]['y'],
+ s_transformation['vector'][0]['z']
+ ],
+ [
+ s_transformation['vector'][1]['x'],
+ s_transformation['vector'][1]['y'],
+ s_transformation['vector'][1]['z']
+ ]
+ ]
+
+ if s_transformation['type'] == "Translate Transformation":
+ transformation = TranslationTransformation(vector)
+ else:
+ transformation = RotationTransformation(vector, s_transformation['angle'])
+ elif s_transformation['type'] == "Reflect Transformation":
+ normal = numpy.array([
+ s_transformation['normal']['x'], s_transformation['normal']['y'],
+ s_transformation['normal']['z']
+ ])
+ point1 = numpy.array([
+ s_transformation['point1']['x'], s_transformation['point1']['y'],
+ s_transformation['point1']['z']
+ ])
+ point2 = numpy.array([
+ s_transformation['point2']['x'], s_transformation['point2']['y'],
+ s_transformation['point2']['z']
+ ])
+ point3 = numpy.array([
+ s_transformation['point3']['x'], s_transformation['point3']['y'],
+ s_transformation['point3']['z']
+ ])
+ if numpy.count_nonzero(point3 - point1) <= 0 or \
+ numpy.count_nonzero(point2 - point1) <= 0:
+ point2 = None
+ point3 = None
+ else:
+ normal = None
+ transformation = ReflectionTransformation(point1, normal=normal, point2=point2, point3=point3)
+ else:
+ center = numpy.array([
+ s_transformation['center']['x'], s_transformation['center']['y'],
+ s_transformation['center']['z']
+ ])
+ transformation = ScalingTransformation(s_transformation['factor'], center=center)
+ transformations[name] = transformation
+ for trans, nested_tran in nested_trans.items():
+ transformations[trans].transformation = transformations[nested_tran]
+ return transformations
+ except KeyError as err:
+ message = "Spatial transformations are not properly formatted or "
+ message += f"are referenced incorrectly: {str(err)}"
+ raise StochSSModelFormatError(message, traceback.format_exc()) from err
+ except ModelError as err:
+ message = f"One or more domain transformations are invalid: {err}"
+ raise DomainTransformationError(message, traceback.format_exc()) from err
+
+ @classmethod
+ def __convert_types(cls, domain, type_ids):
+ domain.typeNdxMapping = {"type_UnAssigned": 0}
+ domain.typeNameMapping = {0: "type_UnAssigned"}
+ domain.listOfTypeIDs = [0]
+ for ndx, name in type_ids.items():
+ if ndx not in domain.typeNameMapping:
+ name = f"type_{name}"
+ domain.typeNdxMapping[name] = ndx
+ domain.typeNameMapping[ndx] = name
+ domain.listOfTypeIDs.append(ndx)
+ types = list(set(domain.type_id))
+ for name in types:
+ if name not in domain.typeNdxMapping:
+ ndx = len(domain.typeNdxMapping)
+ domain.typeNdxMapping[name] = ndx
+ domain.typeNameMapping[ndx] = name
+ domain.listOfTypeIDs.append(ndx)
+
+ def __create_presentation(self, file, dst):
+ presentation = {'model': self.model, 'files': {}}
+ # Check if the domain has lattices
+ if len(self.model['domain']['lattices']) == 0:
+ return self.__write_presentation_file(presentation, dst)
+ # Process file based lattices
+ file_based_types = ('XML Mesh Lattice', 'Mesh IO Lattice', 'StochSS Lattice')
+ for lattice in self.model['domain']['lattices']:
+ if lattice['type'] in file_based_types:
+ entry = self.__get_presentation_file_entry(lattice['filename'], file)
+ lattice['filename'] = entry['pres_path']
+ presentation['files'][lattice['name']] = entry
+ if lattice['subdomainFile'] != "":
+ sdf_entry = self.__get_presentation_file_entry(lattice['subdomainFile'], file)
+ lattice['subdomainFile'] = sdf_entry['pres_path']
+ presentation['files'][f"{lattice['name']}_sdf"] = sdf_entry
+ return self.__write_presentation_file(presentation, dst)
+
+ def __get_presentation_file_entry(self, path, presentation_file):
+ entry = {'name': self.get_file(path=path)}
+ entry['dwn_path'] = os.path.join(
+ self.user_dir, f"{self.model['name']}_doamin_files", entry['name']
+ )
+ entry['pres_path'] = os.path.join(
+ "/tmp/presentation_cache", f"{presentation_file}_doamin_files", entry['name']
+ )
+ with open(path, "r", encoding="utf-8") as entry_fd:
+ entry['body'] = entry_fd.read().strip()
+ return entry
@classmethod
- def __get_trace_data(cls, particles, name="", index=None):
+ def __get_trace_data(cls, particles, name="", index=None, dimensions=3):
common_rgb_values = [
'#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd', '#8c564b', '#e377c2', '#7f7f7f',
'#bcbd22', '#17becf', '#ff0000', '#00ff00', '#0000ff', '#ffff00', '#00ffff', '#ff00ff',
@@ -366,9 +615,13 @@ def __get_trace_data(cls, particles, name="", index=None):
marker = {"size":5}
if index is not None:
marker["color"] = common_rgb_values[(index) % len(common_rgb_values)]
- return plotly.graph_objs.Scatter3d(ids=ids, x=x_data, y=y_data, z=z_data,
- name=name, mode="markers", marker=marker)
-
+ if dimensions == 2:
+ return plotly.graph_objs.Scatter(
+ ids=ids, x=x_data, y=y_data, name=name, mode="markers", marker=marker
+ )
+ return plotly.graph_objs.Scatter3d(
+ ids=ids, x=x_data, y=y_data, z=z_data, name=name, mode="markers", marker=marker
+ )
def __load_domain_from_file(self, path):
try:
@@ -376,7 +629,8 @@ def __load_domain_from_file(self, path):
if path.endswith(".domn"):
with open(path, "r", encoding="utf-8") as domain_file:
s_domain = json.load(domain_file)
- self.__update_domain(domain=s_domain)
+ self.path = path
+ self.__update_domain_to_current(domain=s_domain)
return s_domain
s_domain = Domain.read_xml_mesh(filename=path)
return self.__build_stochss_domain(s_domain=s_domain)
@@ -390,7 +644,6 @@ def __load_domain_from_file(self, path):
message = f"The domain file is not in proper format: {str(err)}"
raise DomainFormatError(message, traceback.format_exc()) from err
-
def __read_model_file(self):
try:
with open(self.get_path(full=True), "r", encoding="utf-8") as smdl_file:
@@ -402,77 +655,123 @@ def __read_model_file(self):
message = f"The spatial model is not JSON decobable: {str(err)}"
raise FileNotJSONFormatError(message, traceback.format_exc()) from err
-
- def __update_domain(self, domain=None):
+ @classmethod
+ def __update_domain_to_v1(cls, domain=None):
+ if domain['template_version'] == 1:
+ return
+
+ if "static" not in domain.keys():
+ domain['static'] = True
+ type_changes = {}
+ for i, d_type in enumerate(domain['types']):
+ if d_type['typeID'] != i:
+ type_changes[d_type['typeID']] = i
+ d_type['typeID'] = i
+ if "rho" not in d_type.keys():
+ d_type['rho'] = d_type['mass'] / d_type['volume']
+ if "c" not in d_type.keys():
+ d_type['c'] = 10
+ if "geometry" not in d_type.keys():
+ d_type['geometry'] = ""
+ if domain['particles']:
+ for particle in domain['particles']:
+ if particle['type'] in type_changes:
+ particle['type'] = type_changes[particle['type']]
+ if "rho" not in particle.keys():
+ particle['rho'] = particle['mass'] / particle['volume']
+ if "c" not in particle.keys():
+ particle['c'] = 10
+
+ def __update_domain_to_current(self, domain=None):
if domain is None:
if "domain" not in self.model.keys() or len(self.model['domain'].keys()) < 6:
self.model['domain'] = self.get_model_template()['domain']
domain = self.model['domain']
- if "template_version" not in domain or domain['template_version'] != self.TEMPLATE_VERSION:
- if "static" not in domain.keys():
- domain['static'] = True
- type_changes = {}
- for i, d_type in enumerate(domain['types']):
- if d_type['typeID'] != i:
- type_changes[d_type['typeID']] = i
- d_type['typeID'] = i
- if "rho" not in d_type.keys():
- d_type['rho'] = d_type['mass'] / d_type['volume']
- if "c" not in d_type.keys():
- d_type['c'] = 10
- if "geometry" not in d_type.keys():
- d_type['geometry'] = ""
- if domain['particles']:
- for particle in domain['particles']:
- if particle['type'] in type_changes:
- particle['type'] = type_changes[particle['type']]
- if "rho" not in particle.keys():
- particle['rho'] = particle['mass'] / particle['volume']
- if "c" not in particle.keys():
- particle['c'] = 10
- domain['template_version'] = self.TEMPLATE_VERSION
+ if domain['template_version'] == self.DOMAIN_TEMPLATE_VERSION:
+ return
+
+ self.__update_domain_to_v1(domain)
+ # Create version 1 domain directory if needed.
+ v1_dir = os.path.join(self.user_dir, "Version1-Domains")
+ if not os.path.exists(v1_dir):
+ os.mkdir(v1_dir)
+ # Get the file name for the version 1 domain file
+ v1_domain = None
+ filename = os.path.join(v1_dir, self.get_file().replace(".smdl", ".domn"))
+ if self.path == filename:
+ errmsg = f"{self.get_file()} may be a dependency of another doamin (.domn) "
+ errmsg += "or a spatial model (.smdl) and can't be updated."
+ raise DomainUpdateError(errmsg)
+ if os.path.exists(filename):
+ with open(filename, "r", encoding="utf-8") as v1_domain_fd:
+ v1_domain = json.dumps(json.load(v1_domain_fd), sort_keys=True, indent=4)
+ curr_domain = json.dumps(domain, sort_keys=True, indent=4)
+ if v1_domain != curr_domain:
+ filename, _ = self.get_unique_path(
+ self.get_file().replace(".smdl", ".domn"), dirname=v1_dir
+ )
+ v1_domain = None
+ # Create the version 1 doman file
+ if v1_domain is None:
+ with open(filename, "w", encoding="utf-8") as v1_domain_fd:
+ json.dump(domain, v1_domain_fd, sort_keys=True, indent=4)
+
+ shapes = []
+ for d_type in domain['types']:
+ if 'geometry' in d_type and d_type['geometry']:
+ shapes.append({
+ 'deltar': 0, 'deltas': 0, 'deltax': 0, 'deltay': 0, 'deltaz': 0, 'depth': 0, 'fillable': False,
+ 'formula': d_type['geometry'], 'height': 0, 'lattice': 'Cartesian Lattice',
+ 'length': 0, 'name': f"shape{len(shapes) + 1}", 'radius': 0, 'type': 'Standard'
+ })
+ domain['actions'] = [{
+ 'type': 'StochSS Domain', 'scope': 'Multi Particle', 'priority': 1, 'enable': True, 'shape': '',
+ 'transformation': '', 'filename': filename.replace(f'{self.user_dir}/', ''), 'subdomainFile': '',
+ 'point': {'x': 0, 'y': 0, 'z': 0}, 'newPoint': {'x': 0, 'y': 0, 'z': 0},
+ 'c': 10, 'fixed': False, 'mass': 1.0, 'nu': 0.0, 'rho': 1.0, 'typeID': 0, 'vol': 0.0
+ }]
+ domain['shapes'] = shapes
+ domain['transformations'] = []
+ domain['template_version'] = self.DOMAIN_TEMPLATE_VERSION
+
+ def __update_model_to_current(self):
+ if self.model['template_version'] == self.TEMPLATE_VERSION:
+ return
+
+ if not self.model['defaultMode']:
+ self.model['defaultMode'] = "discrete"
+ elif self.model['defaultMode'] == "dynamic":
+ self.model['defaultMode'] = "discrete-concentration"
+ if "timestepSize" not in self.model['modelSettings'].keys():
+ self.model['modelSettings']['timestepSize'] = 1e-5
+ if "boundaryConditions" not in self.model.keys():
+ self.model['boundaryConditions'] = []
+ for species in self.model['species']:
+ if "types" not in species.keys():
+ species['types'] = list(range(1, len(self.model['domain']['types'])))
+ if "diffusionConst" not in species.keys():
+ if "diffusionCoeff" not in species.keys():
+ diff = 0.0
+ else:
+ diff = species['diffusionCoeff']
+ species['diffusionConst'] = diff
+ for reaction in self.model['reactions']:
+ if "odePropensity" not in reaction.keys():
+ reaction['odePropensity'] = reaction['propensity']
+ if "types" not in reaction.keys():
+ reaction['types'] = list(range(1, len(self.model['domain']['types'])))
- @classmethod
- def apply_geometry(cls, particles, d_type, center):
- '''
- Set the properties of the particles found within the geometry.
+ self.model['template_version'] = self.TEMPLATE_VERSION
- Attributes
- ----------
- particles : list
- List of existing particles.
- d_type : str
- StochSS domain type.
- center : list
- Vector representing the center of the geometry.
- '''
- def inside(point):
- namespace = {
- 'cx': center[0], 'cy': center[1], 'cz': center[2],
- 'x': point[0], 'y': point[1], 'z': point[2]
- }
- return eval(d_type['geometry'], {}, namespace)
- ids = []
- try:
- for particle in particles:
- if inside(particle['point']):
- ids.append(particle['particle_id'])
- return {'particles': ids}
- except SyntaxError as err:
- message = f"Failed to apply geometry. Reason given: {err}"
- raise DomainGeometryError(message, traceback.format_exc()) from err
- except NameError as err:
- message = f"Failed to apply geometry. Reason given: {err}"
- raise DomainGeometryError(message, traceback.format_exc()) from err
+ @classmethod
+ def __write_presentation_file(cls, body, path):
+ with open(path, "w", encoding="utf-8") as presentation_fd:
+ json.dump(body, presentation_fd, sort_keys=True, indent=4)
+ return True
def convert_to_model(self):
- '''
- Convert a spatial model to a non_spatial model
-
- Attributes
- ----------
- '''
+ ''' Convert a spatial model to a non_spatial model. '''
if self.model is None:
s_model = self.load()
s_model['is_spatial'] = False
@@ -490,21 +789,16 @@ def convert_to_model(self):
message = f"{self.get_file()} was successfully convert to {m_file}!"
return {"Message":message, "File":m_file}, {"model":s_model, "path":m_path}
-
- def convert_to_spatialpy(self):
- '''
- Convert a spatial model to a spatialpy model
-
- Attributes
- ----------
- '''
+ def convert_to_spatialpy(self, include_model_settings=False):
+ '''Convert a spatial model to a spatialpy model. '''
if self.model is None:
_ = self.load()
name = self.get_name()
s_model = Model(name=name)
- self.__convert_model_settings(model=s_model)
+ if include_model_settings:
+ self.__convert_model_settings(model=s_model)
types = sorted(self.model['domain']['types'], key=lambda d_type: d_type['typeID'])
- type_ids = {d_type['typeID']: d_type['name'] for d_type in types if d_type['typeID'] > 0}
+ type_ids = {d_type['typeID']: d_type['name'] for d_type in types}
self.__convert_domain(model=s_model, type_ids=type_ids)
if "boundaryConditions" in self.model.keys():
self.__convert_boundary_conditions(model=s_model)
@@ -515,7 +809,6 @@ def convert_to_spatialpy(self):
# self.log("debug", str(s_model))
return s_model
-
def create_boundary_condition(self, kwargs):
'''
Create a new boundary condition using spatialpy.BoundaryCondition
@@ -530,53 +823,6 @@ def create_boundary_condition(self, kwargs):
expression = new_bc.expression()
return {"expression": expression}
-
- @classmethod
- def fill_geometry(cls, kwargs, d_type):
- '''
- Create particles of the given type with the given properties within the geometry.
-
- Attributes
- ----------
- kwargs : dict
- Arguments passed to Domain.fill_with_particles.
- d_type : dict
- StochSS type definition.
- '''
- center = [
- (kwargs['xmax'] + kwargs['xmin']) / 2,
- (kwargs['ymax'] + kwargs['ymin']) / 2,
- (kwargs['zmax'] + kwargs['zmin']) / 2
- ]
- kwargs['geometry_ivar'] = cls.__build_geometry(d_type, center)
- kwargs['type_id'] = d_type['name']
- kwargs['mass'] = d_type['mass']
- kwargs['vol'] = d_type['volume']
- kwargs['rho'] = d_type['rho']
- kwargs['nu'] = d_type['nu']
- kwargs['c'] = d_type['c']
- kwargs['fixed'] = d_type['fixed']
- try:
- domain = Domain(
- 0, (kwargs['xmin'],kwargs['xmax']),
- (kwargs['ymin'],kwargs['ymax']), (kwargs['zmin'],kwargs['zmax'])
- )
- domain.fill_with_particles(**kwargs)
- data = {'type': d_type, 'transformation': None}
- particles = cls.__build_stochss_domain_particles(domain, data=data)
- limits = {'x_lim': domain.xlim, 'y_lim': domain.ylim, 'z_lim': domain.zlim}
- return {'particles': particles, 'limits': limits}
- except SyntaxError as err:
- message = f"Failed to fill geometry. Reason given: {err}"
- raise DomainGeometryError(message, traceback.format_exc()) from err
- except NameError as err:
- message = f"Failed to fill geometry. Reason given: {err}"
- raise DomainGeometryError(message, traceback.format_exc()) from err
- except ModelError as err:
- message = f"Failed to fill geometry. Reason given: {err}"
- raise DomainGeometryError(message, traceback.format_exc()) from err
-
-
def get_domain(self, path=None, new=False):
'''
Get a prospective domain
@@ -594,7 +840,6 @@ def get_domain(self, path=None, new=False):
return self.load()['domain']
return self.__load_domain_from_file(path=path)
-
def get_domain_plot(self, path=None, new=False, domains=None):
'''
Get a plotly plot of the models domain or a prospective domain
@@ -610,7 +855,7 @@ def get_domain_plot(self, path=None, new=False, domains=None):
'''
if domains is None:
if new:
- path = '/stochss/stochss_templates/nonSpatialModelTemplate.json'
+ path = '/stochss/stochss_templates/modelTemplate.json'
s_domain = StochSSSpatialModel(path).load()['domain']
elif path is None:
path = self.path
@@ -643,7 +888,7 @@ def get_domain_plot(self, path=None, new=False, domains=None):
# Case #3: 1 or more particles and one type
if len(s_domain['types']) == 1:
fig['data'][0]['name'] = "Un-Assigned"
- ids = list(filter(lambda particle: particle['particle_id'], s_domain['particles']))
+ ids = list(map(lambda particle: particle['particle_id'], s_domain['particles']))
fig['data'][0]['ids'] = ids
# Case #4: 1 or more particles and multiple types
else:
@@ -655,7 +900,7 @@ def get_domain_plot(self, path=None, new=False, domains=None):
traces = list(filter(t_test, fig['data']))
if len(traces) == 0:
fig['data'].insert(index, self.__get_trace_data(
- particles=[], name=d_type['name'], index=index
+ particles=[], name=d_type['name'], index=index, dimensions=domain.dimensions
))
else:
particles = list(filter(
@@ -670,168 +915,52 @@ def get_domain_plot(self, path=None, new=False, domains=None):
fig['layout']['height'] = None
fig['layout']['autosize'] = True
fig['config'] = {"responsive":True}
- return json.dumps(fig, cls=plotly.utils.PlotlyJSONEncoder), trace_temp
-
+ return json.dumps(fig, cls=plotly.utils.PlotlyJSONEncoder)
def get_notebook_data(self):
- '''
- Get the needed data for converting to notebook
-
- Attributes
- ----------
- '''
+ ''' Get the needed data for converting to notebook. '''
file = f"{self.get_name()}.ipynb"
path = os.path.join(self.get_dir_name(), file)
- s_model = self.convert_to_spatialpy()
+ s_model = self.convert_to_spatialpy(include_model_settings=True)
self.model['path'] = self.get_file()
return {"path":path, "new":True, "models":{"s_model":self.model, "model":s_model}}
-
- def get_particles_from_3d_domain(self, data):
- '''
- Create a new 3D domain and return the particles
-
- Attributes
- ----------
- data : dict
- Data used to create the 3D domain
- '''
- if data['type']['type_id'] == "Un-Assigned":
- data['type']['type_id'] = "UnAssigned"
- if data['transformation'] is None:
- xlim = data['xLim']
- ylim = data['yLim']
- zlim = data['zLim']
- else:
- xlim = [coord + data['transformation'][0] for coord in data['xLim']]
- ylim = [coord + data['transformation'][1] for coord in data['yLim']]
- zlim = [coord + data['transformation'][2] for coord in data['zLim']]
- dimensions = bool(xlim[1] - xlim[0])
- dimensions += bool(ylim[1] - ylim[0])
- dimensions += bool(zlim[1] - zlim[0])
- if dimensions > 2:
- s_domain = Domain.create_3D_domain(xlim=xlim, ylim=ylim, zlim=zlim, numx=data['nx'],
- numy=data['ny'], numz=data['nz'], **data['type'])
- else:
- s_domain = Domain.create_2D_domain(xlim=xlim, ylim=ylim,
- numx=data['nx'], numy=data['ny'], **data['type'])
- domain = self.__build_stochss_domain(s_domain=s_domain, data=data)
- limits = {"x_lim":domain['x_lim'], "y_lim":domain['y_lim'], "z_lim":domain['z_lim']}
- resp = {"particles":domain['particles'], "limits":limits}
- return resp
-
-
- @classmethod
- def get_particles_from_remote(cls, domain, data, types):
- '''
- Get a list of stochss particles from a domain
-
- Attributes
- ----------
- domain : str
- Domain containing particle data
- data : dict
- Property and location data to be applied to each particle
- types : list
- List of type discriptions (lines from an uploaded file)
- '''
- with tempfile.NamedTemporaryFile() as file:
- with open(file.name, "w", encoding="utf-8") as domain_file:
- domain_file.write(domain)
- s_domain = Domain.read_xml_mesh(filename=file.name)
- domain = cls.__build_stochss_domain(s_domain=s_domain, data=data)
- if types is not None:
- type_data = cls.get_types_from_file(lines=types)
- for t_data in type_data['types']:
- if t_data['particle_id'] < len(domain['particles']):
- domain['particles'][t_data['particle_id']]['type'] = t_data['typeID']
- limits = {"x_lim":domain['x_lim'], "y_lim":domain['y_lim'], "z_lim":domain['z_lim']}
- resp = {"particles":domain['particles'], "limits":limits}
- if types is not None:
- resp['types'] = type_data['names']
- return resp
-
-
- @classmethod
- def get_types_from_file(cls, path=None, lines=None):
- '''
- Get the type descriptions from the .txt file
-
- Attributes
- ----------
- path : str
- Path to the types description file
- lines : list
- Lines from an uploaded file
- '''
- if lines is None:
- path = os.path.join(cls.user_dir, path)
- with open(path, "r", encoding="utf-8") as types_file:
- lines = types_file.readlines()
- types = []
- names = []
- for line in lines:
- if line.strip().replace(".0", "").replace(",", "", 1).isdigit():
- part_id, type_id = line.strip().split(",")
- if '.' in type_id:
- type_id = float(type_id)
- type_id = int(type_id)
- if type_id not in names:
- names.append(type_id)
- types.append({"particle_id":int(part_id), "typeID":type_id})
- else:
- message = "The type descriptions are not in the proper format "
- message += "(i.e. particle_id (int),type_id (int))"
- raise DomainFormatError(message, traceback.format_exc())
- return {"types":types, "names":sorted(names)}
-
-
def load(self):
'''
- Reads the spatial model file, updates it to the current format, and stores it in self.model
-
- Attributes
- ----------
+ Reads the spatial model file, updates it to the current format, and stores it in self.model.
'''
if self.model is None:
self.__read_model_file()
- self.model['name'] = self.get_name()
- if "template_version" not in self.model or \
- self.model['template_version'] != self.TEMPLATE_VERSION:
- if not self.model['defaultMode']:
- self.model['defaultMode'] = "discrete"
- elif self.model['defaultMode'] == "dynamic":
- self.model['defaultMode'] = "discrete-concentration"
- if "timestepSize" not in self.model['modelSettings'].keys():
- self.model['modelSettings']['timestepSize'] = 1e-5
- self.__update_domain()
- if "boundaryConditions" not in self.model.keys():
- self.model['boundaryConditions'] = []
- for species in self.model['species']:
- if "types" not in species.keys():
- species['types'] = list(range(1, len(self.model['domain']['types'])))
- if "diffusionConst" not in species.keys():
- if "diffusionCoeff" not in species.keys():
- diff = 0.0
- else:
- diff = species['diffusionCoeff']
- species['diffusionConst'] = diff
- for reaction in self.model['reactions']:
- if "odePropensity" not in reaction.keys():
- reaction['odePropensity'] = reaction['propensity']
- if "types" not in reaction.keys():
- reaction['types'] = list(range(1, len(self.model['domain']['types'])))
- self.model['template_version'] = self.TEMPLATE_VERSION
+
+ if "domain" in self.model:
+ self.model['name'] = self.get_name()
+ if "template_version" not in self.model:
+ self.model['template_version'] = 0
+ self.__update_model_to_current()
+
+ if "template_version" not in self.model['domain']:
+ self.model['domain']['template_version'] = 0
+ self.__update_domain_to_current()
+ else:
+ if "template_version" not in self.model:
+ self.model['template_version'] = 0
+ self.__update_domain_to_current(domain=self.model)
+
return self.model
+ def load_action_preview(self, s_domain):
+ ''' Get a domain preview of all enabled actions. '''
+ types = sorted(s_domain['types'], key=lambda d_type: d_type['typeID'])
+ type_ids = {d_type['typeID']: d_type['name'] for d_type in types}
+ domain = self.__convert_domain(type_ids, s_domain=s_domain)
+ xlim, ylim, zlim = domain.get_bounding_box()
+ limits = [list(xlim), list(ylim), list(zlim)]
+ s_domain['particles'] = self.__build_stochss_domain_particles(domain)
+ plot = self.get_domain_plot(domains=(domain, s_domain))
+ return plot, limits
def publish_presentation(self):
- '''
- Publish a model or spatial model presentation
-
- Attributes
- ----------
- '''
+ ''' Publish a model or spatial model presentation. '''
present_dir = os.path.join(self.user_dir, ".presentations")
if not os.path.exists(present_dir):
os.mkdir(present_dir)
@@ -847,7 +976,7 @@ def publish_presentation(self):
data = None
else:
self.add_presentation_name(file, self.model['name'])
- data = {"path": dst, "new":True, "model":self.model}
+ data = self.__create_presentation(file, dst)
query_str = f"?owner={hostname}&file={file}"
present_link = f"/stochss/present-model{query_str}"
downloadlink = os.path.join("/stochss/download_presentation",
@@ -859,7 +988,6 @@ def publish_presentation(self):
message = f"You do not have permission to publish this file: {str(err)}"
raise StochSSPermissionsError(message, traceback.format_exc()) from err
-
def save_domain(self, domain):
'''
Writes a StochSS Domain to a .domn file
diff --git a/stochss/handlers/util/stochss_workflow.py b/stochss/handlers/util/stochss_workflow.py
index 488ad299e7..87390c5861 100644
--- a/stochss/handlers/util/stochss_workflow.py
+++ b/stochss/handlers/util/stochss_workflow.py
@@ -1,6 +1,6 @@
'''
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -273,7 +273,7 @@ def get_model_path(self):
raise StochSSFileNotFoundError(message, traceback.format_exc()) from err
- def initialize_job(self, settings, mdl_path, time_stamp, wkfl_type):
+ def initialize_job(self, settings, mdl_path, time_stamp, wkfl_type, compute):
'''
Initialize a new job for this workflow.
@@ -287,6 +287,8 @@ def initialize_job(self, settings, mdl_path, time_stamp, wkfl_type):
Datetime stamp for the new job
wkfl_type : str
Type of workflow
+ compute : str
+ The compute environment to use.
'''
self.log("info", f"Saving {self.get_file()}")
self.save(new_settings=settings, mdl_path=mdl_path)
@@ -296,7 +298,10 @@ def initialize_job(self, settings, mdl_path, time_stamp, wkfl_type):
if self.check_workflow_format(path=self.path):
self.log("info", f"Creating job{time_stamp} job")
path = os.path.join(self.path, f"job{time_stamp}")
- data = {"mdl_path": mdl_path, "settings": settings, "type":wkfl_type}
+ data = {
+ "mdl_path": mdl_path, "settings": settings,
+ "type": wkfl_type, "compute_env": compute
+ }
job = StochSSJob(path=path, new=True, data=data)
self.log("info", f"Successfully created {job.get_file()} job")
else:
@@ -332,7 +337,8 @@ def load(self):
self.workflow['type'] = jobdata['titleType']
oldfmtrdy = jobdata['status'] == "ready"
self.__update_settings()
- if not os.path.exists(self.workflow['model']) and (oldfmtrdy or self.workflow['newFormat']):
+ if (self.workflow['model'] is None or not os.path.exists(self.workflow['model'])) \
+ and (oldfmtrdy or self.workflow['newFormat']):
if ".proj" in self.path:
if "WorkflowGroup1.wkgp" in self.path:
proj = StochSSFolder(path=os.path.dirname(self.get_dir_name(full=True)))
diff --git a/stochss/handlers/workflows.py b/stochss/handlers/workflows.py
index af5ce1200a..1c043dd888 100644
--- a/stochss/handlers/workflows.py
+++ b/stochss/handlers/workflows.py
@@ -1,6 +1,6 @@
'''
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -28,7 +28,8 @@
# Use finish() for json, write() for text
from .util import StochSSJob, StochSSModel, StochSSSpatialModel, StochSSNotebook, StochSSWorkflow, \
- StochSSParamSweepNotebook, StochSSSciopeNotebook, StochSSAPIError, report_error
+ StochSSParamSweepNotebook, StochSSSciopeNotebook, StochSSAPIError, report_error, \
+ report_critical_error
log = logging.getLogger('stochss')
@@ -64,6 +65,8 @@ async def get(self):
self.write(resp)
except StochSSAPIError as err:
report_error(self, log, err)
+ except Exception as err:
+ report_critical_error(self, log, err)
self.finish()
@@ -91,6 +94,8 @@ async def get(self):
self.write(resp)
except StochSSAPIError as err:
report_error(self, log, err)
+ except Exception as err:
+ report_critical_error(self, log, err)
self.finish()
@@ -117,6 +122,8 @@ async def post(self):
self.write(resp)
except StochSSAPIError as err:
report_error(self, log, err)
+ except Exception as err:
+ report_critical_error(self, log, err)
self.finish()
@@ -140,13 +147,17 @@ async def get(self):
log.debug(f"Handler query string: {data}")
try:
wkfl = StochSSWorkflow(path=path)
- resp = wkfl.initialize_job(settings=data['settings'], mdl_path=data['mdl_path'],
- wkfl_type=data['type'], time_stamp=data['time_stamp'])
+ resp = wkfl.initialize_job(
+ settings=data['settings'], mdl_path=data['mdl_path'], wkfl_type=data['type'],
+ time_stamp=data['time_stamp'], compute=data['compute']
+ )
wkfl.print_logs(log)
log.debug(f"Response message: {resp}")
self.write(resp)
except StochSSAPIError as err:
report_error(self, log, err)
+ except Exception as err:
+ report_critical_error(self, log, err)
self.finish()
@@ -184,6 +195,8 @@ async def get(self):
log.debug('The workflow has started')
except StochSSAPIError as err:
report_error(self, log, err)
+ except Exception as err:
+ report_critical_error(self, log, err)
self.finish()
@@ -211,6 +224,8 @@ async def get(self):
self.write(status)
except StochSSAPIError as err:
report_error(self, log, err)
+ except Exception as err:
+ report_critical_error(self, log, err)
self.finish()
@@ -253,6 +268,8 @@ async def get(self):
self.write(fig)
except StochSSAPIError as err:
report_error(self, log, err)
+ except Exception as err:
+ report_critical_error(self, log, err)
self.finish()
@@ -274,6 +291,9 @@ async def get(self):
path = self.get_query_argument(name="path")
log.debug(f"Path to the model/workflow: {path}")
wkfl_type = self.get_query_argument(name="type")
+ log.debug(f"Type of workflow: {wkfl_type}")
+ compute = self.get_query_argument(name="compute", default=None)
+ log.debug(f"Compute Environment: {compute}")
try:
if path.endswith(".mdl"):
file_obj = StochSSModel(path=path)
@@ -285,9 +305,12 @@ async def get(self):
kwargs = file_obj.get_notebook_data()
if "type" in kwargs:
wkfl_type = kwargs['type']
+ results = kwargs['results']
+ compute = kwargs['compute_env']
kwargs = kwargs['kwargs']
log.info(f"Converting {file_obj.get_file()} to notebook")
else:
+ results = None
log.info(f"Creating notebook workflow for {file_obj.get_file()}")
log.debug(f"Type of workflow to be run: {wkfl_type}")
if wkfl_type in ("1d_parameter_sweep", "2d_parameter_sweep"):
@@ -302,13 +325,15 @@ async def get(self):
notebook = StochSSNotebook(**kwargs)
notebooks = {"gillespy":notebook.create_es_notebook,
"spatial":notebook.create_ses_notebook}
- resp = notebooks[wkfl_type]()
+ resp = notebooks[wkfl_type](results=results, compute=compute)
notebook.print_logs(log)
log.debug(f"Response: {resp}")
log.info(f"Successfully created the notebook for {file_obj.get_file()}")
self.write(resp)
except StochSSAPIError as err:
report_error(self, log, err)
+ except Exception as err:
+ report_critical_error(self, log, err)
self.finish()
@@ -340,6 +365,8 @@ async def post(self):
self.write(resp)
except StochSSAPIError as err:
report_error(self, log, err)
+ except Exception as err:
+ report_critical_error(self, log, err)
self.finish()
@@ -377,6 +404,8 @@ async def post(self):
self.write(resp)
except StochSSAPIError as err:
report_error(self, log, err)
+ except Exception as err:
+ report_critical_error(self, log, err)
self.finish()
@@ -403,6 +432,8 @@ async def get(self):
self.write(resp)
except StochSSAPIError as err:
report_error(self, log, err)
+ except Exception as err:
+ report_critical_error(self, log, err)
self.finish()
@@ -439,6 +470,8 @@ async def get(self):
self.write(resp)
except StochSSAPIError as err:
report_error(self, log, err)
+ except Exception as err:
+ report_critical_error(self, log, err)
self.finish()
@@ -475,4 +508,6 @@ async def get(self):
self.write(csv_data)
except StochSSAPIError as err:
report_error(self, log, err)
+ except Exception as err:
+ report_critical_error(self, log, err)
self.finish()
diff --git a/stochss/tests/example_models.py b/stochss/tests/example_models.py
index d129665623..267a3c2db7 100644
--- a/stochss/tests/example_models.py
+++ b/stochss/tests/example_models.py
@@ -1,6 +1,6 @@
'''
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/stochss/tests/run_tests.py b/stochss/tests/run_tests.py
index f1ec19d2ac..b794f7c240 100755
--- a/stochss/tests/run_tests.py
+++ b/stochss/tests/run_tests.py
@@ -2,7 +2,7 @@
'''
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/stochss/tests/test_gillespy2.py b/stochss/tests/test_gillespy2.py
index 1a47a1ee79..9741595518 100644
--- a/stochss/tests/test_gillespy2.py
+++ b/stochss/tests/test_gillespy2.py
@@ -1,6 +1,6 @@
'''
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/stochss/tests/test_model_template.py b/stochss/tests/test_model_template.py
index bd86dbd901..e9a5e77ce0 100644
--- a/stochss/tests/test_model_template.py
+++ b/stochss/tests/test_model_template.py
@@ -1,6 +1,6 @@
'''
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
@@ -32,9 +32,9 @@ def setUp(self):
'''
Get the model template prior to each test.
'''
- template_path = "stochss_templates/nonSpatialModelTemplate.json"
+ template_path = "stochss_templates/modelTemplate.json"
- with open(template_path, "r") as template_file:
+ with open(template_path, "r", encoding="utf8") as template_file:
self.template = json.load(template_file)
@@ -45,7 +45,7 @@ def test_model_elements(self):
template_keys = sorted(list(self.template.keys()))
model_path = "client/models/model.js"
- with open(model_path, "r") as model_file:
+ with open(model_path, "r", encoding="utf8") as model_file:
data = model_file.read()
props = data.split("props: {").pop().split('}')[0].split(',')
collections = data.split("collections: {").pop().split('}')[0].split(',')
@@ -66,7 +66,7 @@ def test_timespan_settings_elements(self):
template_keys = sorted(list(self.template['modelSettings'].keys()))
mdl_settings_path = "client/models/timespan-settings.js"
- with open(mdl_settings_path, "r") as mdl_settings_file:
+ with open(mdl_settings_path, "r", encoding="utf8") as mdl_settings_file:
data = mdl_settings_file.read().split("props: {").pop().split('}')[0].split(',')
mdl_settings_keys = sorted(list(map(lambda item: item.strip().split(':')[0], data)))
@@ -81,7 +81,7 @@ def test_model_domain_elements(self):
template_keys = sorted(list(self.template['domain'].keys()))
domain_path = "client/models/domain.js"
- with open(domain_path, "r") as domain_file:
+ with open(domain_path, "r", encoding="utf8") as domain_file:
data = domain_file.read()
props = data.split("props: {").pop().split('}')[0].split(',')
collections = data.split("collections: {").pop().split('}')[0].split(',')
diff --git a/stochss/tests/test_settings_template.py b/stochss/tests/test_settings_template.py
index fce01d01ce..26028a15de 100644
--- a/stochss/tests/test_settings_template.py
+++ b/stochss/tests/test_settings_template.py
@@ -1,6 +1,6 @@
'''
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/stochss/tests/test_stochss_base.py b/stochss/tests/test_stochss_base.py
index 1688fe8ecb..e121259364 100644
--- a/stochss/tests/test_stochss_base.py
+++ b/stochss/tests/test_stochss_base.py
@@ -1,6 +1,6 @@
'''
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/stochss/tests/test_stochss_file.py b/stochss/tests/test_stochss_file.py
index cfc5492cd7..6dd7f0f42f 100644
--- a/stochss/tests/test_stochss_file.py
+++ b/stochss/tests/test_stochss_file.py
@@ -1,6 +1,6 @@
'''
StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
+Copyright (C) 2019-2023 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
diff --git a/stochss_templates/instance_types.txt b/stochss_templates/instance_types.txt
new file mode 100644
index 0000000000..0330e84c14
--- /dev/null
+++ b/stochss_templates/instance_types.txt
@@ -0,0 +1 @@
+a1.medium|a1.large|a1.xlarge|a1.2xlarge|a1.4xlarge|a1.metal|c1.medium|c1.xlarge|c3.large|c3.xlarge|c3.2xlarge|c3.4xlarge|c3.8xlarge|c4.large|c4.xlarge|c4.2xlarge|c4.4xlarge|c4.8xlarge|c5.large|c5.xlarge|c5.2xlarge|c5.4xlarge|c5.9xlarge|c5.12xlarge|c5.18xlarge|c5.24xlarge|c5.metal|c5a.large|c5a.xlarge|c5a.2xlarge|c5a.4xlarge|c5a.8xlarge|c5a.12xlarge|c5a.16xlarge|c5a.24xlarge|c5ad.large|c5ad.xlarge|c5ad.2xlarge|c5ad.4xlarge|c5ad.8xlarge|c5ad.12xlarge|c5ad.16xlarge|c5ad.24xlarge|c5d.large|c5d.xlarge|c5d.2xlarge|c5d.4xlarge|c5d.9xlarge|c5d.12xlarge|c5d.18xlarge|c5d.24xlarge|c5d.metal|c5n.large|c5n.xlarge|c5n.2xlarge|c5n.4xlarge|c5n.9xlarge|c5n.18xlarge|c5n.metal|c6g.medium|c6g.large|c6g.xlarge|c6g.2xlarge|c6g.4xlarge|c6g.8xlarge|c6g.12xlarge|c6g.16xlarge|c6g.metal|c6gd.medium|c6gd.large|c6gd.xlarge|c6gd.2xlarge|c6gd.4xlarge|c6gd.8xlarge|c6gd.12xlarge|c6gd.16xlarge|c6gd.metal|c6gn.medium|c6gn.large|c6gn.xlarge|c6gn.2xlarge|c6gn.4xlarge|c6gn.8xlarge|c6gn.12xlarge|c6gn.16xlarge|c6i.large|c6i.xlarge|c6i.2xlarge|c6i.4xlarge|c6i.8xlarge|c6i.12xlarge|c6i.16xlarge|c6i.24xlarge|c6i.32xlarge|c6i.metal|cc1.4xlarge|cc2.8xlarge|cg1.4xlarge|cr1.8xlarge|d2.xlarge|d2.2xlarge|d2.4xlarge|d2.8xlarge|d3.xlarge|d3.2xlarge|d3.4xlarge|d3.8xlarge|d3en.xlarge|d3en.2xlarge|d3en.4xlarge|d3en.6xlarge|d3en.8xlarge|d3en.12xlarge|dl1.24xlarge|f1.2xlarge|f1.4xlarge|f1.16xlarge|g2.2xlarge|g2.8xlarge|g3.4xlarge|g3.8xlarge|g3.16xlarge|g3s.xlarge|g4ad.xlarge|g4ad.2xlarge|g4ad.4xlarge|g4ad.8xlarge|g4ad.16xlarge|g4dn.xlarge|g4dn.2xlarge|g4dn.4xlarge|g4dn.8xlarge|g4dn.12xlarge|g4dn.16xlarge|g4dn.metal|g5.xlarge|g5.2xlarge|g5.4xlarge|g5.8xlarge|g5.12xlarge|g5.16xlarge|g5.24xlarge|g5.48xlarge|g5g.xlarge|g5g.2xlarge|g5g.4xlarge|g5g.8xlarge|g5g.16xlarge|g5g.metal|hi1.4xlarge|hpc6a.48xlarge|hs1.8xlarge|h1.2xlarge|h1.4xlarge|h1.8xlarge|h1.16xlarge|i2.xlarge|i2.2xlarge|i2.4xlarge|i2.8xlarge|i3.large|i3.xlarge|i3.2xlarge|i3.4xlarge|i3.8xlarge|i3.16xlarge|i3.metal|i3en.large|i3en.xlarge|i3en.2xlarge|i3en.3xlarge|i3en.6xlarge|i3en.12xlarge|i3en.24xlarge|i3en.metal|im4gn.large|im4gn.xlarge|im4gn.2xlarge|im4gn.4xlarge|im4gn.8xlarge|im4gn.16xlarge|inf1.xlarge|inf1.2xlarge|inf1.6xlarge|inf1.24xlarge|is4gen.medium|is4gen.large|is4gen.xlarge|is4gen.2xlarge|is4gen.4xlarge|is4gen.8xlarge|m1.small|m1.medium|m1.large|m1.xlarge|m2.xlarge|m2.2xlarge|m2.4xlarge|m3.medium|m3.large|m3.xlarge|m3.2xlarge|m4.large|m4.xlarge|m4.2xlarge|m4.4xlarge|m4.10xlarge|m4.16xlarge|m5.large|m5.xlarge|m5.2xlarge|m5.4xlarge|m5.8xlarge|m5.12xlarge|m5.16xlarge|m5.24xlarge|m5.metal|m5a.large|m5a.xlarge|m5a.2xlarge|m5a.4xlarge|m5a.8xlarge|m5a.12xlarge|m5a.16xlarge|m5a.24xlarge|m5ad.large|m5ad.xlarge|m5ad.2xlarge|m5ad.4xlarge|m5ad.8xlarge|m5ad.12xlarge|m5ad.16xlarge|m5ad.24xlarge|m5d.large|m5d.xlarge|m5d.2xlarge|m5d.4xlarge|m5d.8xlarge|m5d.12xlarge|m5d.16xlarge|m5d.24xlarge|m5d.metal|m5dn.large|m5dn.xlarge|m5dn.2xlarge|m5dn.4xlarge|m5dn.8xlarge|m5dn.12xlarge|m5dn.16xlarge|m5dn.24xlarge|m5dn.metal|m5n.large|m5n.xlarge|m5n.2xlarge|m5n.4xlarge|m5n.8xlarge|m5n.12xlarge|m5n.16xlarge|m5n.24xlarge|m5n.metal|m5zn.large|m5zn.xlarge|m5zn.2xlarge|m5zn.3xlarge|m5zn.6xlarge|m5zn.12xlarge|m5zn.metal|m6a.large|m6a.xlarge|m6a.2xlarge|m6a.4xlarge|m6a.8xlarge|m6a.12xlarge|m6a.16xlarge|m6a.24xlarge|m6a.32xlarge|m6a.48xlarge|m6g.metal|m6g.medium|m6g.large|m6g.xlarge|m6g.2xlarge|m6g.4xlarge|m6g.8xlarge|m6g.12xlarge|m6g.16xlarge|m6gd.metal|m6gd.medium|m6gd.large|m6gd.xlarge|m6gd.2xlarge|m6gd.4xlarge|m6gd.8xlarge|m6gd.12xlarge|m6gd.16xlarge|m6i.large|m6i.xlarge|m6i.2xlarge|m6i.4xlarge|m6i.8xlarge|m6i.12xlarge|m6i.16xlarge|m6i.24xlarge|m6i.32xlarge|m6i.metal|mac1.metal|p2.xlarge|p2.8xlarge|p2.16xlarge|p3.2xlarge|p3.8xlarge|p3.16xlarge|p3dn.24xlarge|p4d.24xlarge|r3.large|r3.xlarge|r3.2xlarge|r3.4xlarge|r3.8xlarge|r4.large|r4.xlarge|r4.2xlarge|r4.4xlarge|r4.8xlarge|r4.16xlarge|r5.large|r5.xlarge|r5.2xlarge|r5.4xlarge|r5.8xlarge|r5.12xlarge|r5.16xlarge|r5.24xlarge|r5.metal|r5a.large|r5a.xlarge|r5a.2xlarge|r5a.4xlarge|r5a.8xlarge|r5a.12xlarge|r5a.16xlarge|r5a.24xlarge|r5ad.large|r5ad.xlarge|r5ad.2xlarge|r5ad.4xlarge|r5ad.8xlarge|r5ad.12xlarge|r5ad.16xlarge|r5ad.24xlarge|r5b.large|r5b.xlarge|r5b.2xlarge|r5b.4xlarge|r5b.8xlarge|r5b.12xlarge|r5b.16xlarge|r5b.24xlarge|r5b.metal|r5d.large|r5d.xlarge|r5d.2xlarge|r5d.4xlarge|r5d.8xlarge|r5d.12xlarge|r5d.16xlarge|r5d.24xlarge|r5d.metal|r5dn.large|r5dn.xlarge|r5dn.2xlarge|r5dn.4xlarge|r5dn.8xlarge|r5dn.12xlarge|r5dn.16xlarge|r5dn.24xlarge|r5dn.metal|r5n.large|r5n.xlarge|r5n.2xlarge|r5n.4xlarge|r5n.8xlarge|r5n.12xlarge|r5n.16xlarge|r5n.24xlarge|r5n.metal|r6g.medium|r6g.large|r6g.xlarge|r6g.2xlarge|r6g.4xlarge|r6g.8xlarge|r6g.12xlarge|r6g.16xlarge|r6g.metal|r6gd.medium|r6gd.large|r6gd.xlarge|r6gd.2xlarge|r6gd.4xlarge|r6gd.8xlarge|r6gd.12xlarge|r6gd.16xlarge|r6gd.metal|r6i.large|r6i.xlarge|r6i.2xlarge|r6i.4xlarge|r6i.8xlarge|r6i.12xlarge|r6i.16xlarge|r6i.24xlarge|r6i.32xlarge|r6i.metal|t1.micro|t2.nano|t2.micro|t2.small|t2.medium|t2.large|t2.xlarge|t2.2xlarge|t3.nano|t3.micro|t3.small|t3.medium|t3.large|t3.xlarge|t3.2xlarge|t3a.nano|t3a.micro|t3a.small|t3a.medium|t3a.large|t3a.xlarge|t3a.2xlarge|t4g.nano|t4g.micro|t4g.small|t4g.medium|t4g.large|t4g.xlarge|t4g.2xlarge|u-6tb1.56xlarge|u-6tb1.112xlarge|u-9tb1.112xlarge|u-12tb1.112xlarge|u-6tb1.metal|u-9tb1.metal|u-12tb1.metal|u-18tb1.metal|u-24tb1.metal|vt1.3xlarge|vt1.6xlarge|vt1.24xlarge|x1.16xlarge|x1.32xlarge|x1e.xlarge|x1e.2xlarge|x1e.4xlarge|x1e.8xlarge|x1e.16xlarge|x1e.32xlarge|x2iezn.2xlarge|x2iezn.4xlarge|x2iezn.6xlarge|x2iezn.8xlarge|x2iezn.12xlarge|x2iezn.metal|x2gd.medium|x2gd.large|x2gd.xlarge|x2gd.2xlarge|x2gd.4xlarge|x2gd.8xlarge|x2gd.12xlarge|x2gd.16xlarge|x2gd.metal|z1d.large|z1d.xlarge|z1d.2xlarge|z1d.3xlarge|z1d.6xlarge|z1d.12xlarge|z1d.metal|x2idn.16xlarge|x2idn.24xlarge|x2idn.32xlarge|x2iedn.xlarge|x2iedn.2xlarge|x2iedn.4xlarge|x2iedn.8xlarge|x2iedn.16xlarge|x2iedn.24xlarge|x2iedn.32xlarge|c6a.large|c6a.xlarge|c6a.2xlarge|c6a.4xlarge|c6a.8xlarge|c6a.12xlarge|c6a.16xlarge|c6a.24xlarge|c6a.32xlarge|c6a.48xlarge|c6a.metal|m6a.metal|i4i.large|i4i.xlarge|i4i.2xlarge|i4i.4xlarge|i4i.8xlarge|i4i.16xlarge|i4i.32xlarge|i4i.metal|x2idn.metal|x2iedn.metal|c7g.medium|c7g.large|c7g.xlarge|c7g.2xlarge|c7g.4xlarge|c7g.8xlarge|c7g.12xlarge|c7g.16xlarge|mac2.metal|c6id.large|c6id.xlarge|c6id.2xlarge|c6id.4xlarge|c6id.8xlarge|c6id.12xlarge|c6id.16xlarge|c6id.24xlarge|c6id.32xlarge|c6id.metal|m6id.large|m6id.xlarge|m6id.2xlarge|m6id.4xlarge|m6id.8xlarge|m6id.12xlarge|m6id.16xlarge|m6id.24xlarge|m6id.32xlarge|m6id.metal|r6id.large|r6id.xlarge|r6id.2xlarge|r6id.4xlarge|r6id.8xlarge|r6id.12xlarge|r6id.16xlarge|r6id.24xlarge|r6id.32xlarge|r6id.metal|r6a.large|r6a.xlarge|r6a.2xlarge|r6a.4xlarge|r6a.8xlarge|r6a.12xlarge|r6a.16xlarge|r6a.24xlarge|r6a.32xlarge|r6a.48xlarge|r6a.metal|p4de.24xlarge|u-3tb1.56xlarge
\ No newline at end of file
diff --git a/stochss_templates/nonSpatialModelTemplate.json b/stochss_templates/modelTemplate.json
similarity index 83%
rename from stochss_templates/nonSpatialModelTemplate.json
rename to stochss_templates/modelTemplate.json
index d422dfd9d6..579d0acfe0 100644
--- a/stochss_templates/nonSpatialModelTemplate.json
+++ b/stochss_templates/modelTemplate.json
@@ -10,19 +10,20 @@
"timestepSize": 1e-5
},
"domain": {
- "size": null,
- "rho_0": 1.0,
- "c_0": 10,
- "p_0": 100.0,
- "gravity": [0, 0, 0],
- "x_lim": [0, 0],
- "y_lim": [0, 0],
- "z_lim": [0, 0],
- "static": true,
- "boundary_condition": {
+ "actions": [],
+ "boundary_condition": {
"reflect_x": true,
"reflect_y": true,
"reflect_z": true},
+ "c_0": 10,
+ "gravity": [0, 0, 0],
+ "p_0": 100.0,
+ "rho_0": 1.0,
+ "shapes": [],
+ "size": null,
+ "static": true,
+ "template_version": 2,
+ "transformations": [],
"types": [
{
"c":10,
@@ -35,8 +36,9 @@
"volume":1.0
}
],
- "particles": [],
- "template_version": 1
+ "x_lim": [0, 0],
+ "y_lim": [0, 0],
+ "z_lim": [0, 0]
},
"species": [],
"initialConditions": [],
diff --git a/stochss_templates/userSettingTemplate.json b/stochss_templates/userSettingTemplate.json
new file mode 100644
index 0000000000..0b97ea18d1
--- /dev/null
+++ b/stochss_templates/userSettingTemplate.json
@@ -0,0 +1,6 @@
+{
+ "awsAccessKeyID": "",
+ "awsRegion": "",
+ "headNode": "",
+ "userLogs": true
+}
diff --git a/webpack.config.js b/webpack.config.js
index 22f1f93036..a2ca6255b1 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -5,6 +5,7 @@ module.exports = {
mode: 'development',
entry: {
home: './client/pages/users-home.js',
+ userSettings: './client/pages/user-settings.js',
quickstart: './client/pages/quickstart.js',
exampleLibrary: './client/pages/example-library.js',
browser: './client/pages/browser.js',
@@ -29,6 +30,13 @@ module.exports = {
name: 'home',
inject: false
}),
+ new HtmlWebpackPlugin({
+ title: 'StochSS | User Settings',
+ filename: 'stochss-user-settings.html',
+ template: 'page_template.pug',
+ name: 'userSettings',
+ inject: false
+ }),
new HtmlWebpackPlugin({
title: 'StochSS | Quickstart',
filename: 'stochss-quickstart.html',