diff --git a/EXAMPLE_DIRECTORY.md b/EXAMPLE_DIRECTORY.md
new file mode 100644
index 0000000000..03713a95fc
--- /dev/null
+++ b/EXAMPLE_DIRECTORY.md
@@ -0,0 +1,90 @@
+# Example Directory
+
+This file contains a directory to all GillesPy2 and SpatialPy example notebooks.
+
+## GillesPy2
+
+["Start Here" Model](https://github.com/StochSS/GillesPy2/blob/main/examples/StartHere.ipynb)
+
+### Starting Models
+
+- [Michaelis Menten](https://github.com/StochSS/GillesPy2/tree/main/examples/StartingModels/MichaelisMenten)
+ - [SSA C Solver](https://github.com/StochSS/GillesPy2/blob/main/examples/StartingModels/MichaelisMenten/Michaelis-Menten_SSA_C.ipynb)
+ - [SSA NumPy Solver](https://github.com/StochSS/GillesPy2/blob/main/examples/StartingModels/MichaelisMenten/Michaelis-Menten_NumPy_SSA.ipynb)
+ - [Tau Leaping NumPy](https://github.com/StochSS/GillesPy2/blob/main/examples/StartingModels/MichaelisMenten/Michaelis-Menten_Basic_Tau_Leaping.ipynb)
+ - [Tau Hybrid NumPy](https://github.com/StochSS/GillesPy2/blob/main/examples/StartingModels/MichaelisMenten/Michaelis-Menten_Basic_Tau_Hybrid.ipynb)
+
+- [Vilar Oscillator](https://github.com/StochSS/GillesPy2/tree/main/examples/StartingModels/VilarOscillator)
+ - [SSA](https://github.com/StochSS/GillesPy2/blob/main/examples/StartingModels/VilarOscillator/VilarOscillator_SSA.ipynb)
+ - [Tau Leaping](https://github.com/StochSS/GillesPy2/blob/main/examples/StartingModels/VilarOscillator/VilarOscillator_Tau_Leaping.ipynb)
+ - [Tau Hybrid](https://github.com/StochSS/GillesPy2/blob/main/examples/StartingModels/VilarOscillator/VilarOscillator_Tau_Hybrid.ipynb)
+
+
+### Results Management
+
+- [Basic Results Management](https://github.com/StochSS/GillesPy2/blob/main/examples/ResultsManagement/basic-results-management.ipynb)
+- [Single Trajectory to CSV](https://github.com/StochSS/GillesPy2/blob/main/examples/ResultsManagement/to_csv-single-trajectory.ipynb)
+- [Multiple Trajectories to CSV](https://github.com/StochSS/GillesPy2/blob/main/examples/ResultsManagement/to_csv-multi-trajectory.ipynb)
+- [Using Pickle](https://github.com/StochSS/GillesPy2/blob/main/examples/ResultsManagement/using-pickle.ipynb)
+
+
+### Data Visualization
+
+- [Data Visualization](https://github.com/StochSS/GillesPy2/blob/main/examples/DataVisualization/DataVisualization.ipynb)
+- [Live Output](https://github.com/StochSS/GillesPy2/blob/main/examples/DataVisualization/LiveOutput.ipynb)
+
+
+### Advanced Features
+
+- [Events](https://github.com/StochSS/GillesPy2/blob/main/examples/AdvancedFeatures/Events.ipynb)
+- [Kinetic Model for Styrene Polymerization](https://github.com/StochSS/GillesPy2/blob/main/examples/AdvancedFeatures/KineticModelForStyrenePolymerization.ipynb)
+- [Photosynthesis](https://github.com/StochSS/GillesPy2/blob/main/examples/AdvancedFeatures/Photosynthesis.ipynb)
+- [GillesPy2 in Parallel](https://github.com/StochSS/GillesPy2/blob/main/examples/AdvancedFeatures/Run%20GillesPy2%20Simulations%20in%20Parallel.ipynb)
+- [SBML Import](https://github.com/StochSS/GillesPy2/blob/main/examples/AdvancedFeatures/SBML_Import_Test.ipynb)
+- [Variable Inputs (SSA)](https://github.com/StochSS/GillesPy2/blob/main/examples/AdvancedFeatures/Variable_SSA_C_Example.ipynb)
+- [Volume Test](https://github.com/StochSS/GillesPy2/blob/main/examples/AdvancedFeatures/Volume_test.py)
+- [Hybrid Continuous Species](https://github.com/StochSS/GillesPy2/blob/main/examples/AdvancedFeatures/hybrid_continuous_species.ipynb)
+- [Hybrid Switching](https://github.com/StochSS/GillesPy2/blob/main/examples/AdvancedFeatures/hybrid_switching_example.ipynb)
+- [Parameter Changing](https://github.com/StochSS/GillesPy2/blob/main/examples/AdvancedFeatures/parameter_changing.py)
+
+
+### Extra Models
+
+- [Brusselator](https://github.com/StochSS/GillesPy2/blob/main/examples/ExtraModels/Brusselator.ipynb)
+- [Genetic Toggle Switch](https://github.com/StochSS/GillesPy2/blob/main/examples/ExtraModels/GeneticToggleSwitch.ipynb)
+- [Opioid Model](https://github.com/StochSS/GillesPy2/blob/main/examples/ExtraModels/Opioid_Model.ipynb)
+- [Oregonator](https://github.com/StochSS/GillesPy2/blob/main/examples/ExtraModels/Oregonator.ipynb)
+- [Tyson Oscillator](https://github.com/StochSS/GillesPy2/blob/main/examples/ExtraModels/Tyson%20Oscillator.ipynb)
+- [Degradation Model](https://github.com/StochSS/GillesPy2/blob/main/examples/ExtraModels/degradation_example.py)
+- [Dimer Model](https://github.com/StochSS/GillesPy2/blob/main/examples/ExtraModels/dimer_example.py)
+- [Tyson Oscillator](https://github.com/StochSS/GillesPy2/blob/main/examples/ExtraModels/tyson_oscillator.py)
+
+
+
+## SpatialPy
+
+### General Examples
+
+- [Bistable Elf Ehrenberg](https://github.com/StochSS/SpatialPy/blob/develop/examples/Bistable_Biochem_Elf_Ehrenberg/Bistable_Biochem_Elf_Ehrenberg.ipynb)
+- [Boundary Conditions](https://github.com/StochSS/SpatialPy/blob/develop/examples/BoundaryCondition/BoundaryCondition.ipynb)
+- [SpatialPy Gravity](https://github.com/StochSS/SpatialPy/blob/develop/examples/Gravity/Spatialpy_gravity.ipynb)
+- [Coral Reef](https://github.com/StochSS/SpatialPy/blob/develop/examples/coral_reef/CoralModel.ipynb)
+- [3D Cylinder Diffusion](https://github.com/StochSS/SpatialPy/blob/develop/examples/cylinderDemo/SpatialPy_cylinderDemo3D.ipynb)
+- [hes1](https://github.com/StochSS/SpatialPy/blob/develop/examples/hes1/hes1.ipynb)
+- [Lid Driven Cavity](https://github.com/StochSS/SpatialPy/blob/develop/examples/lid_driven_cavity/Lid%20driven%20cavity.ipynb)
+- [MinCDE](https://github.com/StochSS/SpatialPy/blob/develop/examples/mincde/mincde.ipynb)
+- [Turing Pattern (Old)](https://github.com/StochSS/SpatialPy/blob/develop/examples/turing_pattern/turing_pattern.ipynb)
+- [Turing Pattern (New)](https://github.com/StochSS/SpatialPy/blob/develop/examples/turing_pattern/new_turing_pattern.ipynb)
+- [Weir](https://github.com/StochSS/SpatialPy/blob/develop/examples/weir/weir.ipynb)
+- [Yeast Polarization](https://github.com/StochSS/SpatialPy/tree/develop/examples/yeast_polarization)
+ - [G-Protein 1D](https://github.com/StochSS/SpatialPy/blob/develop/examples/yeast_polarization/G-Protein_1D.ipynb)
+ - [Polarisome 1D](https://github.com/StochSS/SpatialPy/blob/develop/examples/yeast_polarization/Polarisome_1D.ipynb)
+ - [cdc42](https://github.com/StochSS/SpatialPy/blob/develop/examples/yeast_polarization/cdc42.ipynb)
+
+### Tests
+
+- [Diffusion Validation](https://github.com/StochSS/SpatialPy/blob/develop/examples/tests/Diffusion_validation.ipynb)
+- [Spatial Birth-Death](https://github.com/StochSS/SpatialPy/blob/develop/examples/tests/Spatial_Birth_Death.ipynb)
+- [Compile Time Test](https://github.com/StochSS/SpatialPy/blob/develop/examples/tests/compile_time_comparison.ipynb)
+- [Read Meshes](https://github.com/StochSS/SpatialPy/blob/develop/examples/tests/read_meshes.ipynb)
+
diff --git a/__version__.py b/__version__.py
index 93d8b3f6da..06125f64be 100644
--- a/__version__.py
+++ b/__version__.py
@@ -5,7 +5,7 @@
# @website https://github.com/stochss/stochss
# =============================================================================
-__version__ = '2.4.1'
+__version__ = '2.4.2'
__title__ = 'StochSS'
__description__ = 'StochSS is an integrated development environment (IDE) \
for simulation of biochemical networks.'
diff --git a/client/models/presentation.js b/client/models/presentation.js
index 17a07f2295..3e027ade20 100644
--- a/client/models/presentation.js
+++ b/client/models/presentation.js
@@ -20,8 +20,10 @@ let State = require('ampersand-state');
module.exports = State.extend({
session: {
+ ctime: 'string',
file: 'string',
link: 'string',
+ name: 'string',
size: 'number',
tag: 'string'
},
diff --git a/client/pages/loading-page.js b/client/pages/loading-page.js
index 1b553faef3..be496cdced 100644
--- a/client/pages/loading-page.js
+++ b/client/pages/loading-page.js
@@ -91,7 +91,7 @@ let LoadingPage = PageView.extend({
$(self.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 = this.homeLink;
+ window.location.href = self.homeLink;
});
}
app.getXHR(endpoint, {
diff --git a/client/pages/model-editor.js b/client/pages/model-editor.js
index 850b8a9946..78a13d5d68 100644
--- a/client/pages/model-editor.js
+++ b/client/pages/model-editor.js
@@ -197,17 +197,22 @@ let ModelEditor = PageView.extend({
errorCB(err, response, body);
}
else if(!body.Running){
+ Plotly.purge(this.queryByHook('preview-plot-container'));
if(body.Results.timeout){
$(this.queryByHook('model-timeout-message')).collapse('show');
}
this.plotResults(body.Results.results);
}else{
+ if(body.Results) {
+ Plotly.purge(this.queryByHook('preview-plot-container'));
+ this.plotResults(body.Results.results);
+ }
this.getResults();
}
},
error: errorCB
});
- }, 2000);
+ }, 1000);
},
handlePresentationClick: function (e) {
let errorMsg = $(this.queryByHook("error-detected-msg"));
diff --git a/client/pages/project-manager.js b/client/pages/project-manager.js
index ccae2200b1..976c7706ed 100644
--- a/client/pages/project-manager.js
+++ b/client/pages/project-manager.js
@@ -92,8 +92,8 @@ let ProjectManager = PageView.extend({
always: function (err, response, body) {
let modal = $(modals.importModelHtml(body.files)).modal();
let okBtn = document.querySelector('#importModelModal .ok-model-btn');
- let select = document.querySelector('#importModelModal #modelFileInput');
- let location = document.querySelector('#importModelModal #modelPathInput');
+ let select = document.querySelector('#importModelModal #modelFileSelect');
+ let location = document.querySelector('#importModelModal #modelPathSelect');
select.addEventListener("change", function (e) {
okBtn.disabled = e.target.value && body.paths[e.target.value].length >= 2;
if(body.paths[e.target.value].length >= 2) {
@@ -314,7 +314,7 @@ let ProjectManager = PageView.extend({
}
},
handleUploadModelClick: function (e) {
- this.projectFileBrowser.uploadFile(undefined, "model")
+ this.projectFileBrowser.uploadFile(null, this.model.directory, "model", true)
},
renderArchiveCollection: function () {
if(this.archiveCollectionView) {
diff --git a/client/project-config.js b/client/project-config.js
index edf1cef1b0..9c0629e0ff 100644
--- a/client/project-config.js
+++ b/client/project-config.js
@@ -151,7 +151,7 @@ let getNotebookContext = (view, node) => {
open: open,
publish: view.getPublishNotebookContext(node),
download: download, rename: rename,
- duplicate: duplicate, delete: deleteFile
+ duplicate: duplicate, moveToTrash: moveToTrash
}
}
diff --git a/client/templates/body.pug b/client/templates/body.pug
index 7cab6d1365..a2ab6c0467 100644
--- a/client/templates/body.pug
+++ b/client/templates/body.pug
@@ -64,6 +64,8 @@ body
button.my-0.btn.btn-outline-collapse.inline(data-hook="user-logs-collapse") +
+ button.my-0.btn.btn-light.inline(data-hook="clear-user-logs" style="float: right;") clear
+
div.pl-1.overflow-auto(id="user-logs")
main.col-sm-12.col-md-10.col-xl-9.col-xxl-6.body(role="main" data-hook="page-main")
diff --git a/client/templates/includes/presentationView.pug b/client/templates/includes/presentationView.pug
index 113ec43540..9b14ffa957 100644
--- a/client/templates/includes/presentationView.pug
+++ b/client/templates/includes/presentationView.pug
@@ -5,14 +5,18 @@ div.mx-1
div.row
- div.col-sm-6
+ div.col-sm-4
- a.pl-2(href=this.model.link)=this.model.file
+ a.pl-2(href=this.model.link)=this.model.name
div.col-sm-2
button.btn.btn-outline-secondary.box-shadow(data-hook="copy-link") Copy Link
+ div.col-sm-2
+
+ div=this.model.ctime
+
div.col-sm-2
div=this.model.size + " " + this.model.tag
diff --git a/client/templates/pages/browser.pug b/client/templates/pages/browser.pug
index a634614fe5..a0b9dc16eb 100644
--- a/client/templates/pages/browser.pug
+++ b/client/templates/pages/browser.pug
@@ -36,7 +36,9 @@ section.page
div.mx-1.row.head.align-items-baseline
- div.col-sm-8: h6 File
+ div.col-sm-6: h6 File
+
+ div.col-sm-2: h6 Date
div.col-sm-2: h6 Size
diff --git a/client/views/jstree-view.js b/client/views/jstree-view.js
index 5bed5b9f8b..1fe55f6ab8 100644
--- a/client/views/jstree-view.js
+++ b/client/views/jstree-view.js
@@ -900,7 +900,7 @@ module.exports = View.extend({
msg.html(reason);
msg.css("display", "inline-block");
}
- app.copyToClipboard(links.presentation, onFulfilled, onReject);
+ app.copyToClipboard(body.links.presentation, onFulfilled, onReject);
});
},
error: (err, response, body) => {
diff --git a/client/views/main.js b/client/views/main.js
index c77d0ce9ce..22a5f4a9cd 100644
--- a/client/views/main.js
+++ b/client/views/main.js
@@ -38,67 +38,6 @@ String.prototype.toHtmlEntities = function() {
});
};
-let operationInfoModalHtml = (infoKey) => {
- let fileBrowserInfo = `
-
In StochSS we use custom file extensions for a number of files we work with. Here is a list of our extentions with the files they are associated with:
-
-
- StochSS Model |
- .mdl |
-
-
- StochSS Spatial Model |
- .smdl |
-
-
- SBML Model |
- .sbml |
-
-
- Workflows |
- .wkfl |
-
-
-
- Other useful file extensions include the following:
-
-
- Jupyter Notebook |
- .ipynb |
-
-
- `;
- let modelInfo = `
- Model Information
- `;
- let workflowInfo = `
- Workflow Information
- `;
-
- let infoList = {"File Browser":fileBrowserInfo, "Models":modelInfo, "Workflows":workflowInfo}
-
- return `
-
- `
-}
-
module.exports = View.extend({
template: bodyTemplate,
autoRender: true,
@@ -109,8 +48,8 @@ module.exports = View.extend({
},
events: {
'click [data-hook=registration-link-button]' : 'handleRegistrationLinkClick',
- 'click [data-hook=user-logs-collapse]' : 'collapseExpandLogs'
- //'click a[href]': 'handleLinkClick'
+ 'click [data-hook=user-logs-collapse]' : 'collapseExpandLogs',
+ 'click [data-hook=clear-user-logs]' : 'clearUserLogs'
},
render: function () {
@@ -132,18 +71,7 @@ module.exports = View.extend({
if(app.getBasePath() === "/") {
$("#presentation-nav-link").css("display", "none");
}
- let self = this;
- let message = app.getBasePath() === "/" ? "Welcome to StochSS!" : "Welcome to StochSS Live!";
- $("#user-logs").html(message)
- this.logBlock = [];
- this.logs = [];
- this.getUserLogs();
- this.scrolled = false;
- this.scrollCount = 0;
- $("#user-logs").on("mousewheel", function(e) {
- self.scrolled = true;
- self.scrollCount = 0;
- });
+ this.setupUserLogs();
return this;
},
addNewLogBlock: function () {
@@ -172,6 +100,14 @@ module.exports = View.extend({
});
this.addNewLogBlock();
},
+ clearUserLogs: function (e) {
+ let endpoint = path.join(app.getApiPath(), "clear-user-logs");
+ app.getXHR(endpoint, {
+ success: (err, response, body) => {
+ this.setupUserLogs({getLogs: false});
+ }
+ });
+ },
collapseExpandLogs: function (e) {
let logs = $("#user-logs");
let classes = logs.attr("class").split(/\s+/);
@@ -249,6 +185,21 @@ module.exports = View.extend({
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)
+ this.logBlock = [];
+ this.logs = [];
+ this.scrolled = false;
+ this.scrollCount = 0;
+ if(getLogs) {
+ this.getUserLogs();
+ $("#user-logs").on("mousewheel", (e) => {
+ this.scrolled = true;
+ this.scrollCount = 0;
+ });
+ }
+ },
updateUserLogs: function () {
setTimeout(_.bind(this.getUserLogs, this), 1000);
}
diff --git a/launch_webbrowser.py b/launch_webbrowser.py
index e9f892e27b..9524b9c75e 100755
--- a/launch_webbrowser.py
+++ b/launch_webbrowser.py
@@ -4,6 +4,9 @@
import sys
import time
import webbrowser
+
+MAX_WAIT_TIME = 60
+
try:
import docker
except ImportError:
@@ -25,11 +28,15 @@
#time.sleep(10)
print("Checking for running StochSS container: stochss-lab")
container_started = False
+poll_start_time = time.time()
while not container_started:
+ if (time.time() - MAX_WAIT_TIME) > poll_start_time:
+ print(f"Stopped checking for running StochSS container after {MAX_WAIT_TIME}s")
+ sys.exit(1)
time.sleep(1)
try:
stochss_container=docker_client.containers.get("stochss-lab")
- print("Generating StochSS webpage...")
+ print("Checking to see if the server is active.")
jupyter_url_generator=stochss_container.exec_run("jupyter notebook list", demux=False)
url_regex = r"(?i)\b((?:https?://|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'\".,<>?«»“”‘’]))"
jupyter_url_bytes = jupyter_url_generator.output
@@ -40,6 +47,7 @@
jupyter_url=jupyter_url_sequence[0]
jupyter_url=jupyter_url.replace('0.0.0.0','127.0.0.1')
print(f"Opening StochSS webpage...\n")
+ time.sleep(1)
webbrowser.open_new_tab(jupyter_url)
print("Welcome to StochSS!\n\nYou can access your local StochSS service with this URL:\n\n")
print(jupyter_url+"\n")
diff --git a/requirements.txt b/requirements.txt
index a5b7759e21..ef68e8a1a7 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,8 +2,8 @@ python-libsbml==5.18.0
python-libsedml==2.0.9
python-libcombine==0.2.7
escapism==1.0.1
-gillespy2==1.6.3
-sciope==0.4
+gillespy2==1.6.5
+git+https://github.com/StochSS/sciope.git@master
pygmsh==5.0.2
meshio==2.3.10
-git+https://github.com/spatialpy/SpatialPy.git@main
+git+https://github.com/StochSS/SpatialPy.git@develop
diff --git a/stochss/handlers/__init__.py b/stochss/handlers/__init__.py
index 30adcddd42..512ece0016 100644
--- a/stochss/handlers/__init__.py
+++ b/stochss/handlers/__init__.py
@@ -53,6 +53,7 @@ def get_page_handlers(route_start):
## API Handlers
#
(r'/stochss/api/user-logs\/?', UserLogsAPIHandler),
+ (r'/stochss/api/clear-user-logs\/?', ClearUserLogsAPIHandler),
(r"/stochss/api/file/browser-list\/?", ModelBrowserFileList),
(r"/stochss/api/file/upload\/?", UploadFileAPIHandler),
(r"/stochss/api/file/upload-from-link\/?", UploadFileFromLinkAPIHandler),
diff --git a/stochss/handlers/log.py b/stochss/handlers/log.py
index b8717443c2..c082d97c90 100644
--- a/stochss/handlers/log.py
+++ b/stochss/handlers/log.py
@@ -16,6 +16,8 @@
along with this program. If not, see .
'''
+import os
+import sys
import logging
from tornado.log import LogFormatter
@@ -28,12 +30,45 @@ def init_log():
Attributes
----------
'''
+ relocate_old_logs()
setup_stream_handler()
setup_file_handler()
log.setLevel(logging.DEBUG)
log.propagate = False
+def relocate_old_logs():
+ '''
+ Move the user log file to its new location (/var/log).
+ '''
+ user_dir = os.path.expanduser("~")
+ src = os.path.join(user_dir, ".user-logs.txt")
+ if not os.path.exists(src):
+ return
+
+ src_size = os.path.getsize(src)
+ if src_size < 500000:
+ return
+
+ with open(src, "r") as log_file:
+ logs = log_file.read().rstrip().split('\n')
+
+ mlog_size = src_size % 500000
+ mlogs = [logs.pop()]
+ while sys.getsizeof("\n".join(mlogs)) < mlog_size:
+ mlogs.insert(0, logs.pop())
+ with open(os.path.join(user_dir, ".user-logs.txt"), "w") as main_log_file:
+ main_log_file.write("\n".join(mlogs))
+
+ blogs = [logs.pop()]
+ nlog_size = sys.getsizeof(f"\n{logs[-1]}")
+ while logs and sys.getsizeof("\n".join(blogs)) + nlog_size < 500000:
+ blogs.insert(0, logs.pop())
+ nlog_size = sys.getsizeof(f"\n{logs[-1]}")
+ with open(os.path.join(user_dir, ".user-logs.txt.bak"), "w") as backup_log_file:
+ backup_log_file.write("\n".join(blogs))
+
+
def setup_stream_handler():
'''
Initialize the StochSS stream handler
@@ -57,7 +92,37 @@ def setup_file_handler():
Attributes
----------
'''
- handler = logging.FileHandler(".user-logs.txt")
+ def namer(name):
+ '''
+ Namer function for the RotatingFileHandler
+
+ Attributes
+ ----------
+ name : str
+ Default name of the log file.
+ '''
+ return f"{name}.bak"
+
+ def rotator(src, dst):
+ '''
+ Rotator function for the RotatingFileHandler
+
+ Attributes
+ ----------
+ src : str
+ Path to the main log file.
+ dst : str
+ Path to the backup log file.
+ '''
+ if os.path.exists(dst):
+ os.remove(dst)
+ os.rename(src, dst)
+ os.remove(src)
+
+ 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
fmt = '%(asctime)s$ %(message)s'
formatter = LogFormatter(fmt=fmt, datefmt="%b %d, %Y %I:%M %p UTC")
handler.setFormatter(formatter)
diff --git a/stochss/handlers/models.py b/stochss/handlers/models.py
index 5c4152c8f7..406595e0c3 100644
--- a/stochss/handlers/models.py
+++ b/stochss/handlers/models.py
@@ -212,6 +212,9 @@ 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:
@@ -229,6 +232,7 @@ async def get(self):
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
@@ -265,7 +269,7 @@ async def get(self):
class ImportMeshAPIHandler(APIHandler):
'''
################################################################################################
- Handler for importing mesh particles from remote file.
+ Handler for importing domain particles from remote file.
################################################################################################
'''
@web.authenticated
diff --git a/stochss/handlers/pages.py b/stochss/handlers/pages.py
index 7ca148a5c0..ba03c27060 100644
--- a/stochss/handlers/pages.py
+++ b/stochss/handlers/pages.py
@@ -264,13 +264,39 @@ async def get(self):
'''
self.set_header('Content-Type', 'application/json')
log_num = self.get_query_arguments(name="logNum")[0]
- user_dir = os.path.expanduser("~")
- path = os.path.join(user_dir, ".user-logs.txt")
+ path = os.path.join(os.path.expanduser("~"), ".user-logs.txt")
try:
+ if os.path.exists(f"{path}.bak"):
+ with open(path, "r") as log_file:
+ logs = log_file.read().strip().split("\n")
+ else:
+ logs = []
with open(path, "r") as log_file:
- logs = log_file.read().strip().split("\n")[int(log_num):]
+ logs.extend(log_file.read().strip().split("\n"))
+ logs = logs[int(log_num):]
except FileNotFoundError:
open(path, "w").close()
logs = []
self.write({"logs":logs})
self.finish()
+
+
+class ClearUserLogsAPIHandler(APIHandler):
+ '''
+ ################################################################################################
+ Handler for clearing the user logs
+ ################################################################################################
+ '''
+ @web.authenticated
+ async def get(self):
+ '''
+ Clear contents of the user log file.
+
+ Attributes
+ ----------
+ '''
+ path = os.path.join(os.path.expanduser("~"), ".user-logs.txt")
+ if os.path.exists(f'{path}.bak'):
+ os.remove(f'{path}.bak')
+ open(path, "w").close()
+ self.finish()
diff --git a/stochss/handlers/project.py b/stochss/handlers/project.py
index db3d3d44e0..ccb04bb69f 100644
--- a/stochss/handlers/project.py
+++ b/stochss/handlers/project.py
@@ -171,7 +171,8 @@ def get(self):
folder = StochSSFolder(path="")
# file will be excluded if test passes
test = lambda ext, root, file: bool(".wkfl" in root or f"{path}" in root or \
- "trash" in root.split("/"))
+ "trash" in root.split("/") or \
+ ".presentations" in root)
data = folder.get_file_list(ext=[".mdl", ".smdl"], test=test)
log.debug(f"List of models: {data}")
self.write(data)
diff --git a/stochss/handlers/util/ensemble_simulation.py b/stochss/handlers/util/ensemble_simulation.py
index 949fe70b36..31ed2c8253 100644
--- a/stochss/handlers/util/ensemble_simulation.py
+++ b/stochss/handlers/util/ensemble_simulation.py
@@ -104,7 +104,9 @@ def run(self, preview=False, verbose=True):
if preview:
if verbose:
log.info(f"Running {self.g_model.name} preview simulation")
- results = self.g_model.run(timeout=5)
+ live_file = f".{self.g_model.name}-preview.json"
+ options = {"file_path": live_file}
+ results = self.g_model.run(timeout=60, live_output="graph", live_output_options=options)
if verbose:
log.info(f"{self.g_model.name} preview simulation has completed")
log.info(f"Generate result plot for {self.g_model.name} preview")
diff --git a/stochss/handlers/util/parameter_scan.py b/stochss/handlers/util/parameter_scan.py
index f1871b6138..a3c914b277 100644
--- a/stochss/handlers/util/parameter_scan.py
+++ b/stochss/handlers/util/parameter_scan.py
@@ -86,7 +86,7 @@ def __setup_model(self, variables):
return self.model
tmp_mdl = copy.deepcopy(self.model)
for name, value in variables.items():
- tmp_mdl.listOfParameters[name].set_expression(value)
+ tmp_mdl.listOfParameters[name].expression = value
return tmp_mdl
diff --git a/stochss/handlers/util/parameter_sweep_1d.py b/stochss/handlers/util/parameter_sweep_1d.py
index 14e0d2b3df..f93feaa49b 100644
--- a/stochss/handlers/util/parameter_sweep_1d.py
+++ b/stochss/handlers/util/parameter_sweep_1d.py
@@ -150,7 +150,7 @@ def run(self, job_id, verbose=False):
self.settings['variables'] = {self.param['parameter']:val}
else:
tmp_mdl = copy.deepcopy(self.model)
- tmp_mdl.listOfParameters[self.param['parameter']].set_expression(val)
+ tmp_mdl.listOfParameters[self.param['parameter']].expression = val
if verbose:
log.info(f"{job_id} --> running: {self.param['parameter']}={val}")
try:
diff --git a/stochss/handlers/util/parameter_sweep_2d.py b/stochss/handlers/util/parameter_sweep_2d.py
index f4cd173d57..abbee80cfd 100644
--- a/stochss/handlers/util/parameter_sweep_2d.py
+++ b/stochss/handlers/util/parameter_sweep_2d.py
@@ -153,8 +153,8 @@ def run(self, job_id, verbose=False):
self.settings['variables'] = variables
else:
tmp_mdl = copy.deepcopy(self.model)
- tmp_mdl.listOfParameters[self.params[0]['parameter']].set_expression(val1)
- tmp_mdl.listOfParameters[self.params[1]['parameter']].set_expression(val2)
+ tmp_mdl.listOfParameters[self.params[0]['parameter']].expression = val1
+ tmp_mdl.listOfParameters[self.params[1]['parameter']].expression = val2
if verbose:
message = f"{job_id} --> running: {self.params[0]['parameter']}={val1}, "
message += f"{self.params[1]['parameter']}={val2}"
diff --git a/stochss/handlers/util/parameter_sweep_notebook.py b/stochss/handlers/util/parameter_sweep_notebook.py
index 75f586e1c3..165790df63 100644
--- a/stochss/handlers/util/parameter_sweep_notebook.py
+++ b/stochss/handlers/util/parameter_sweep_notebook.py
@@ -128,7 +128,7 @@ def __create_1d_run_str(self):
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].set_expression(v1)"])
+ 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):",
@@ -252,8 +252,8 @@ def __create_2d_run_str(self):
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].set_expression(v1)",
- f"{pad*4}tmp_model.listOfParameters[c.p2].set_expression(v2)"])
+ 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):",
diff --git a/stochss/handlers/util/sciope_notebook.py b/stochss/handlers/util/sciope_notebook.py
index a3ec3b7625..ecfbc87234 100644
--- a/stochss/handlers/util/sciope_notebook.py
+++ b/stochss/handlers/util/sciope_notebook.py
@@ -141,7 +141,7 @@ def __create_mi_simulator_cell(self):
run = f"{pad}res = model.run(**kwargs, variables=variables)"
else:
func_def = "def set_model_parameters(params, model):"
- body = f"{pad*2}model.get_parameter(pname).set_expression(params[e])"
+ 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)"
diff --git a/stochss/handlers/util/stochss_base.py b/stochss/handlers/util/stochss_base.py
index 43a5b73ea8..f8727ad40b 100644
--- a/stochss/handlers/util/stochss_base.py
+++ b/stochss/handlers/util/stochss_base.py
@@ -46,6 +46,28 @@ def __init__(self, path):
self.logs = []
+ def add_presentation_name(self, file, name):
+ '''
+ Add a new presentation to the presentation names file.
+
+ Attributes
+ ----------
+ file : str
+ Name of the presentation file
+ name : str
+ Name of the presentation
+ '''
+ path = os.path.join(self.user_dir, ".presentations", ".presentation_names.json")
+ if os.path.exists(path):
+ with open(path, "r") as names_file:
+ names = json.load(names_file)
+ else:
+ names = {}
+ names[file] = name
+ with open(path, "w") as names_file:
+ json.dump(names, names_file)
+
+
@classmethod
def check_project_format(cls, path):
'''
@@ -87,6 +109,23 @@ def check_workflow_format(cls, path):
return True
+ def delete_presentation_name(self, file):
+ '''
+ Remove a presentation name from the presentation names file.
+
+ Attributes
+ ----------
+ file : str
+ Name of the presentation file to remove
+ '''
+ path = os.path.join(self.user_dir, ".presentations", ".presentation_names.json")
+ with open(path, "r") as names_file:
+ names = json.load(names_file)
+ del names[file]
+ with open(path, "w") as names_file:
+ json.dump(names, names_file)
+
+
@classmethod
def get_new_path(cls, dst_path):
'''
diff --git a/stochss/handlers/util/stochss_file.py b/stochss/handlers/util/stochss_file.py
index 2c621494fc..2e7cb3f815 100644
--- a/stochss/handlers/util/stochss_file.py
+++ b/stochss/handlers/util/stochss_file.py
@@ -60,6 +60,8 @@ def delete(self):
'''
path = self.get_path(full=True)
try:
+ if ".presentations" in path:
+ self.delete_presentation_name(self.get_file())
os.remove(path)
return "The file {0} was successfully deleted.".format(self.get_file())
except FileNotFoundError as err:
diff --git a/stochss/handlers/util/stochss_folder.py b/stochss/handlers/util/stochss_folder.py
index 3c49af47e1..1e21d5afd6 100644
--- a/stochss/handlers/util/stochss_folder.py
+++ b/stochss/handlers/util/stochss_folder.py
@@ -18,9 +18,11 @@
import os
import json
+import pickle
import shutil
import string
import zipfile
+import datetime
import traceback
import requests
@@ -98,6 +100,56 @@ def __build_jstree_node(self, path, file):
return node
+ @classmethod
+ def __get_presentation_job_name(cls, file_path):
+ with open(file_path, "rb") as job_file:
+ job = pickle.load(job_file)
+ name = job['name']
+ return name
+
+
+ @classmethod
+ def __get_presentation_model_name(cls, file_path):
+ with open(file_path, "r") as model_file:
+ name = json.load(model_file)['name']
+ return name
+
+
+ @classmethod
+ def __get_presentation_name(cls, names, file, file_path):
+ ext = file.split('.').pop()
+ if file in names.keys():
+ name = names[file]
+ else:
+ if ext in ("mdl", "smdl"):
+ name = cls.__get_presentation_model_name(file_path)
+ elif ext == "job":
+ name = cls.__get_presentation_job_name(file_path)
+ elif ext == "ipynb":
+ name = cls.__get_presentation_notebook_name(file_path)
+ names[file] = name
+ return name, ext
+
+
+ @classmethod
+ def __get_names_from_file(cls, path, files):
+ if not os.path.exists(os.path.join(path, ".presentation_names.json")):
+ return {}
+ with open(os.path.join(path, ".presentation_names.json"), "r") as names_file:
+ names = json.load(names_file)
+ if len(names.keys()) != len(files):
+ return {}
+ return names
+
+
+ @classmethod
+ def __get_presentation_notebook_name(cls, file_path):
+ with open(file_path, "r") as nb_file:
+ file = json.load(nb_file)['file']
+ name = cls.get_name(cls, path=file)
+ return name
+
+
@classmethod
def __overwrite(cls, path, ext):
if ext == "zip":
@@ -360,26 +412,34 @@ def get_presentations(cls):
----------
'''
path = os.path.join(cls.user_dir, ".presentations")
+ files = [file for file in os.listdir(path) if not file.startswith('.')]
presentations = []
- if not os.path.isdir(path):
+ if not files:
return presentations
+ names = cls.__get_names_from_file(path, files)
+ need_names = not bool(names)
safe_chars = set(string.ascii_letters + string.digits)
hostname = escape(os.environ.get('JUPYTERHUB_USER'), safe=safe_chars)
- for file in os.listdir(path):
+ for file in files:
file_path = os.path.join(path, file)
- query_str = f"?owner={hostname}&file={file}"
+ ctime = os.path.getctime(file_path)
routes = {
"smdl": "present-model",
"mdl": "present-model",
"job": "present-job",
"ipynb": "present-notebook"
}
- route = routes[file.split('.').pop()]
- link = f"/stochss/{route}{query_str}"
+ name, ext = cls.__get_presentation_name(names, file, file_path)
presentation = {
- "file": file, "link": link, "size": os.path.getsize(file_path)
+ "file": file, "name": f"{name}.{ext}",
+ "link": f"/stochss/{routes[ext]}?owner={hostname}&file={file}",
+ "size": os.path.getsize(file_path),
+ "ctime": datetime.datetime.fromtimestamp(ctime).strftime("%b %d, %Y")
}
presentations.append(presentation)
+ if need_names or len(names.keys()) != len(files):
+ with open(os.path.join(path, ".presentation_names.json"), "w") as names_file:
+ json.dump(names, names_file)
return presentations
@@ -479,6 +539,13 @@ def upload_from_link(self, remote_path, overwrite=False):
body = json.dumps(json.loads(body)['notebook'])
else:
file = self.get_file(path=remote_path)
+ if "404: Not Found" in body.decode():
+ message = f"Could not upload this file as {file} was not found."
+ if "?token=" in file:
+ message += " The token for this file may be out of date."
+ return {"message": message, "reason":"File Not Found"}
+ if "?token=" in file:
+ file = file.split("?token=")[0]
path = self.get_new_path(dst_path=file)
if os.path.exists(path):
if not overwrite:
@@ -515,6 +582,8 @@ def validate_upload_link(self, remote_path):
body = json.dumps(json.loads(body)['notebook'])
else:
file = self.get_file(path=remote_path)
+ if "?token=" in file:
+ file = file.split("?token=")[0]
path = self.get_new_path(dst_path=file)
if ext == "zip":
with zipfile.ZipFile(path, "r") as zip_file:
diff --git a/stochss/handlers/util/stochss_job.py b/stochss/handlers/util/stochss_job.py
index 61663da1b4..11dd944084 100644
--- a/stochss/handlers/util/stochss_job.py
+++ b/stochss/handlers/util/stochss_job.py
@@ -746,6 +746,7 @@ def publish_presentation(self, name=None):
else:
exists = False
name = self.get_file() if name is None else name
+ self.add_presentation_name(file, name)
path = os.path.join(self.__get_results_path(), "results.p")
with open(path, "rb") as results_file:
results = pickle.load(results_file)
diff --git a/stochss/handlers/util/stochss_model.py b/stochss/handlers/util/stochss_model.py
index 3bfe947995..69276e5340 100644
--- a/stochss/handlers/util/stochss_model.py
+++ b/stochss/handlers/util/stochss_model.py
@@ -406,6 +406,28 @@ def convert_to_spatial(self):
return {"Message":message, "File":s_file}, {"spatial":model, "path":s_path}
+ def get_live_results(self):
+ '''
+ Get the live output figure for the preview.
+
+ Attributes
+ ----------
+ '''
+ file_name = f".{self.get_name()}-preview.json"
+ try:
+ with open(file_name, "r") as live_fig:
+ fig = json.load(live_fig)
+ fig["config"] = {
+ "displayModeBar": True,
+ "responsive": True
+ }
+ return {"results": fig, "timeout":False}
+ except FileNotFoundError:
+ return ""
+ except json.decoder.JSONDecodeError:
+ return ""
+
+
def get_notebook_data(self):
'''
Get the needed data for converting to notebook
@@ -485,6 +507,7 @@ def publish_presentation(self):
if os.path.exists(dst):
data = None
else:
+ self.add_presentation_name(file, self.model['name'])
data = {"path": dst, "new":True, "model":self.model}
query_str = f"?owner={hostname}&file={file}"
present_link = f"/stochss/present-model{query_str}"
diff --git a/stochss/handlers/util/stochss_notebook.py b/stochss/handlers/util/stochss_notebook.py
index bc0731e057..9db45a860d 100644
--- a/stochss/handlers/util/stochss_notebook.py
+++ b/stochss/handlers/util/stochss_notebook.py
@@ -222,8 +222,8 @@ def __create_import_cell(self):
imports = ["import numpy as np"]
if self.s_model['is_spatial']:
imports.append("import spatialpy")
- imports.append("from spatialpy import Model, Species, Parameter, Reaction, Mesh,\\")
- imports.append(" PlaceInitialCondition, \\")
+ 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))
@@ -275,12 +275,12 @@ def __create_initial_condition_strings(self, model, pad):
raise StochSSModelFormatError(message, traceback.format_exc()) from err
- def __create_mesh_string(self, model, pad):
- mesh = ["", f"{pad}# Domain",
- f"{pad}mesh = Mesh.read_stochss_domain('{self.s_model['path']}')",
- f"{pad}self.add_mesh(mesh)",
- "", f"{pad}self.staticDomain = {self.s_model['domain']['static']}"]
- model.extend(mesh)
+ 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):
@@ -289,7 +289,7 @@ def __create_model_cell(self):
model = [f"class {self.get_class_name()}(Model):",
" def __init__(self):",
f'{pad}Model.__init__(self, name="{self.get_name()}")']
- self.__create_mesh_string(model=model, pad=pad)
+ 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)
self.__create_initial_condition_strings(model=model, pad=pad)
@@ -403,7 +403,7 @@ def __create_species_strings(self, model, pad):
if self.s_model['is_spatial']:
names.append(spec["name"])
spec_str = f'{pad}{spec["name"]} = Species(name="{spec["name"]}", '
- spec_str += f"diffusion_constant={spec['diffusionConst']})"
+ spec_str += f"diffusion_coefficient={spec['diffusionConst']})"
if len(spec['types']) < len(self.s_model['domain']['types']) - 1:
type_str = f"{pad}self.restrict({spec['name']}, {str(spec['types'])})"
types_str.append(type_str)
@@ -564,7 +564,7 @@ def get_class_name(self):
return f"M{name}"
if l_char in string.ascii_lowercase:
return name.replace(l_char, l_char.upper(), 1)
- return name
+ return name.replace(" ", "")
def get_gillespy2_solver_name(self):
@@ -604,6 +604,7 @@ def publish_presentation(self):
if os.path.exists(dst):
exists = True
else:
+ self.add_presentation_name(file, self.get_name())
exists = False
with open(dst, "w") as presentation_file:
json.dump(notebook_pres, presentation_file)
diff --git a/stochss/handlers/util/stochss_spatial_model.py b/stochss/handlers/util/stochss_spatial_model.py
index 6e2ea225f9..552c765eaf 100644
--- a/stochss/handlers/util/stochss_spatial_model.py
+++ b/stochss/handlers/util/stochss_spatial_model.py
@@ -26,7 +26,7 @@
import numpy
import plotly
from escapism import escape
-from spatialpy import Model, Species, Parameter, Reaction, Mesh, MeshError, BoundaryCondition, \
+from spatialpy import Model, Species, Parameter, Reaction, Domain, DomainError, BoundaryCondition, \
PlaceInitialCondition, UniformInitialCondition, ScatterInitialCondition
from .stochss_base import StochSSBase
@@ -93,7 +93,7 @@ def expression(self): # pylint: disable=no-self-use
def __build_stochss_domain(cls, s_domain, data=None):
particles = cls.__build_stochss_domain_particles(s_domain=s_domain, data=data)
gravity = [0] * 3 if s_domain.gravity is None else s_domain.gravity
- domain = {"size":s_domain.mesh_size,
+ domain = {"size":s_domain.domain_size,
"rho_0":s_domain.rho0, # density
"c_0":s_domain.c0, # approx./artificial speed of sound
"p_0":s_domain.P0, # atmos/background pressure
@@ -136,6 +136,8 @@ def __build_stochss_domain_particles(cls, s_domain, data=None):
"mass":s_domain.mass[i],
"type":type_id,
"nu":viscosity,
+ "rho":s_domain.rho[i],
+ "c":s_domain.c[i],
"fixed":fixed}
particles.append(particle)
return particles
@@ -163,9 +165,9 @@ def __convert_domain(self, model):
gravity = self.model['domain']['gravity']
if gravity == [0, 0, 0]:
gravity = None
- mesh = Mesh(0, xlim, ylim, zlim, rho0=rho0, c0=c_0, P0=p_0, gravity=gravity)
- self.__convert_particles(mesh=mesh)
- model.add_mesh(mesh)
+ domain = Domain(0, xlim, ylim, zlim, rho0=rho0, c0=c_0, P0=p_0, gravity=gravity)
+ self.__convert_particles(domain=domain)
+ model.add_domain(domain)
model.staticDomain = self.model['domain']['static']
except KeyError as err:
message = "Spatial model domain properties are not properly formatted or "
@@ -221,11 +223,12 @@ def __convert_parameters(self, model):
raise StochSSModelFormatError(message, traceback.format_exc()) from err
- def __convert_particles(self, mesh):
+ def __convert_particles(self, domain):
try:
for particle in self.model['domain']['particles']:
- mesh.add_point(particle['point'], particle['volume'], particle['mass'],
- particle['type'], particle['nu'], particle['fixed'])
+ domain.add_point(particle['point'], particle['volume'], particle['mass'],
+ 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)}"
@@ -264,7 +267,7 @@ def __convert_species(self, model):
try:
for species in self.model['species']:
name = species['name']
- s_species = Species(name=name, diffusion_constant=species['diffusionConst'])
+ s_species = Species(name=name, diffusion_coefficient=species['diffusionConst'])
model.add_species(s_species)
model.restrict(s_species, species['types'])
except KeyError as err:
@@ -321,7 +324,7 @@ def __load_domain_from_file(self, path):
if path.endswith(".domn"):
with open(path, "r") as domain_file:
return json.load(domain_file)
- s_domain = Mesh.read_xml_mesh(filename=path)
+ s_domain = Domain.read_xml_mesh(filename=path)
return self.__build_stochss_domain(s_domain=s_domain)
except FileNotFoundError as err:
message = f"Could not find the domain file: {str(err)}"
@@ -329,7 +332,7 @@ def __load_domain_from_file(self, path):
except json.decoder.JSONDecodeError as err:
message = f"The domain file is not JSON decobable: {str(err)}"
raise FileNotJSONFormatError(message, traceback.format_exc()) from err
- except MeshError as err:
+ except DomainError as err:
message = f"The domain file is not in proper format: {str(err)}"
raise DomainFormatError(message, traceback.format_exc()) from err
@@ -346,6 +349,19 @@ def __read_model_file(self):
raise FileNotJSONFormatError(message, traceback.format_exc()) from err
+ def __update_domain(self):
+ if "domain" not in self.model.keys() or len(self.model['domain'].keys()) < 6:
+ self.model['domain'] = self.get_model_template()['domain']
+ elif "static" not in self.model['domain'].keys():
+ self.model['domain']['static'] = True
+ if self.model['domain']['particles']:
+ if "rho" not in self.model['domain']['particles'][0].keys() or \
+ "c" not in self.model['domain']['particles'][0].keys():
+ for particle in self.model['domain']['particles']:
+ particle['rho'] = particle['mass']/particle['volume']
+ particle['c'] = 10
+
+
def convert_to_model(self):
'''
Convert a spatial model to a non_spatial model
@@ -425,37 +441,44 @@ def get_domain(self, path=None, new=False):
return self.__load_domain_from_file(path=path)
- def get_domain_plot(self, domain=None, path=None, new=False):
+ def get_domain_plot(self, path=None, new=False):
'''
Get a plotly plot of the models domain or a prospective domain
Attributes
----------
- domain : dict
- The domain to be plotted
path : str
Path to a prospective domain
new : bool
Indicates whether or not to load an new domain
'''
- if domain is None:
- domain = self.get_domain(path=path, new=new)
- 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)
+ if new:
+ path = '/stochss/stochss_templates/nonSpatialModelTemplate.json'
+ elif path is None:
+ path = self.path
+ domain = Domain.read_stochss_domain(path)
+ fig = domain.plot_types(return_plotly_figure=True)
+ if not fig['data']:
+ fig['data'].append(self.__get_trace_data(particles=[], name="Un-Assigned"))
+ else:
+ s_domain = self.load()['domain']
+ for i, d_type in enumerate(s_domain['types']):
+ if len(s_domain['types']) > 1:
+ particles = list(filter(lambda partictle, key=i: particle['type'] == key,
+ s_domain['particles']))
+ else:
+ particles = s_domain['particles']
+ ids = list(map(lambda particle: particle['particle_id'], particles))
+ index = fig['data'].index(
+ list(filter(lambda trace: trace['name'].endswith(str(d_type['typeID'])), fig['data']))[0]
+ )
+ fig['data'][index]['name'] = d_type['name']
+ fig['data'][index]['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 get_notebook_data(self):
@@ -490,8 +513,15 @@ def get_particles_from_3d_domain(cls, data):
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']]
- s_domain = Mesh.create_3D_domain(xlim=xlim, ylim=ylim, zlim=zlim, nx=data['nx'],
- ny=data['ny'], nz=data['nz'], **data['type'])
+ 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, nx=data['nx'],
+ ny=data['ny'], nz=data['nz'], **data['type'])
+ else:
+ s_domain = Domain.create_2D_domain(xlim=xlim, ylim=ylim,
+ nx=data['nx'], ny=data['ny'], **data['type'])
domain = cls.__build_stochss_domain(s_domain=s_domain)
limits = {"x_lim":domain['x_lim'], "y_lim":domain['y_lim'], "z_lim":domain['z_lim']}
return {"particles":domain['particles'], "limits":limits}
@@ -512,9 +542,9 @@ def get_particles_from_remote(cls, mesh, data, types):
List of type discriptions (lines from an uploaded file)
'''
file = tempfile.NamedTemporaryFile()
- with open(file.name, "w") as mesh_file:
- mesh_file.write(mesh)
- s_domain = Mesh.read_xml_mesh(filename=file.name)
+ with open(file.name, "w") as domain_file:
+ domain_file.write(mesh)
+ 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)
@@ -576,10 +606,7 @@ def load(self):
self.model['defaultMode'] = "discrete"
if "timestepSize" not in self.model['modelSettings'].keys():
self.model['modelSettings']['timestepSize'] = 1e-5
- if "domain" not in self.model.keys() or len(self.model['domain'].keys()) < 6:
- self.model['domain'] = self.get_model_template()['domain']
- elif "static" not in self.model['domain'].keys():
- self.model['domain']['static'] = True
+ self.__update_domain()
if "boundaryConditions" not in self.model.keys():
self.model['boundaryConditions'] = []
for species in self.model['species']:
@@ -615,6 +642,7 @@ def publish_presentation(self):
if os.path.exists(dst):
data = None
else:
+ self.add_presentation_name(file, self.model['name'])
data = {"path": dst, "new":True, "model":self.model}
query_str = f"?owner={hostname}&file={file}"
present_link = f"/stochss/present-model{query_str}"
diff --git a/stochss/tests/test_stochss_base.py b/stochss/tests/test_stochss_base.py
index 11e2f1f920..89cbc2e068 100644
--- a/stochss/tests/test_stochss_base.py
+++ b/stochss/tests/test_stochss_base.py
@@ -59,6 +59,30 @@ def tearDown(self):
if StochSSBase.user_dir != os.path.expanduser("~"):
StochSSBase.user_dir = os.path.expanduser("~")
+ ################################################################################################
+ # Unit tests for the StochSS base class add_presentation_name function.
+ ################################################################################################
+
+ def test_add_presentation_name__file_exists(self):
+ ''' Check if the presentation name is add to the presentation names file. '''
+ StochSSBase.user_dir = self.tempdir.name
+ test_base = StochSSBase(path="")
+ with mock.patch("os.path.exists", return_value=True):
+ with mock.patch("builtins.open", mock.mock_open(read_data="{}")):
+ with mock.patch("json.dump") as mock_json_dump:
+ test_base.add_presentation_name("foo", "bar")
+ mock_json_dump.assert_called_once_with({'foo': 'bar'}, mock.ANY)
+
+
+ def test_add_presentation_name__file_does_not_exists(self):
+ ''' Check if the presentation names file was created with the given entry. '''
+ StochSSBase.user_dir = self.tempdir.name
+ test_base = StochSSBase(path="")
+ with mock.patch("builtins.open", mock.mock_open()):
+ with mock.patch("json.dump") as mock_json_dump:
+ test_base.add_presentation_name("foo", "bar")
+ mock_json_dump.assert_called_once_with({'foo': 'bar'}, mock.ANY)
+
################################################################################################
# Unit tests for the StochSS base class check_project_format function.
################################################################################################
@@ -130,6 +154,19 @@ def test_check_workflow_format__new(self):
''' Check if the workflow is identified as old. '''
self.assertTrue(StochSSBase.check_workflow_format(path=self.test_folderpath))
+ ################################################################################################
+ # Unit tests for the StochSS base class delete_presentation_name function.
+ ################################################################################################
+
+ def test_delete_presentation_name(self):
+ ''' Check if the target presentation was removed from the presentation names file. '''
+ StochSSBase.user_dir = self.tempdir.name
+ test_base = StochSSBase(path="")
+ with mock.patch("builtins.open", mock.mock_open(read_data='{"foo": "bar"}')):
+ with mock.patch("json.dump") as mock_json_dump:
+ test_base.delete_presentation_name("foo")
+ mock_json_dump.assert_called_once_with({}, mock.ANY)
+
################################################################################################
# Unit tests for the StochSS base class get_new_path function.
################################################################################################