Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SBML Simulation Feature Improvements #761

Merged
merged 3 commits into from
Feb 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions app/js/app-utilities.js
Original file line number Diff line number Diff line change
Expand Up @@ -793,6 +793,12 @@ appUtilities.defaultGeneralProperties = {
extraHighlightThickness: 2
};

appUtilities.defaultSimulationProperties = {
startTime: 0,
stopTime: 10,
stepCount: 50
}

appUtilities.setFileContent = function (fileName) {
var span = document.getElementById('file-name');
var displayedSpan = document.getElementById('displayed-file-name');
Expand Down
139 changes: 112 additions & 27 deletions app/js/backbone-views.js
Original file line number Diff line number Diff line change
Expand Up @@ -1968,23 +1968,42 @@ var SimulationPanelView = Backbone.View.extend({
$(self.el).modal("show");

var plotElement = document.getElementById("simulation-plot");
// let data = [];
// for (let i = 1; i < simulationData.num_variables; i++) {
// var trace = {
// x: simulationData.columns[0],
// y: simulationData.columns[i],
// mode: "lines",
// name: simulationData.titles[i]
// };
// data.push(trace);
// }
Plotly.newPlot(plotElement, simulationData, {margin: {pad: 15}});
var layout = {
margin: {
pad: 15
},
showlegend: true,
legend: {
x: 1,
xanchor: 'right',
y: 1,
bgcolor: 'rgba(255, 255, 255, 0.1)'
},
xaxis: {
title: {
text: "Time (s)",
}
},
yaxis: {
title: {
text: "Quantity",
standoff: 30
}
}
}
Plotly.newPlot(plotElement, simulationData, layout);
return this;
},
});

var simulationTabPanel = GeneralPropertiesParentView.extend({
initialize: function() {
$(document).on("click", "#map-simulation-default-button", function (evt) {
$("#inspector-simulation-start").val(appUtilities.defaultSimulationProperties.startTime);
$("#inspector-simulation-end").val(appUtilities.defaultSimulationProperties.stopTime);
$("#inspector-simulation-step").val(appUtilities.defaultSimulationProperties.stepCount);
})

$(document).on("click", "#map-simulate-button", function (evt) {
if(appUtilities.getActiveChiseInstance().elementUtilities.mapType !== "SBML"){
new ExportErrorView({el: "#exportError-table"}).render();
Expand Down Expand Up @@ -2014,7 +2033,6 @@ var simulationTabPanel = GeneralPropertiesParentView.extend({
}
var chiseInstance = appUtilities.getActiveChiseInstance();
chiseInstance.startSpinner("simulation-spinner");

$.ajax({
url: "/simulate",
type: "POST",
Expand All @@ -2037,18 +2055,72 @@ var simulationTabPanel = GeneralPropertiesParentView.extend({
render: function() {
// use the active cy instance
var cy = appUtilities.getActiveCy();

// get current general properties of cy
var currentGeneralProperties = appUtilities.getScratch(
cy,
"currentGeneralProperties"
);
var chise = appUtilities.getActiveChiseInstance();

this.template = _.template($("#map-tab-simulation-template").html());
this.$el.empty();
this.$el.html(this.template(currentGeneralProperties));
this.$el.html(this.template());

var width = $("#sbgn-inspector").width() * 0.45;
$("#sbml-param-table-row").html("");
var paramHTML = "<td class='header' style='padding-right:5px;'>"
+ "<span style='text-align: right;' class='add-on layout-text' title='SBML global parameters'> Parameters </span>"
+ "</td><td id='sbml-parameters' style='padding-left: 5px;'></td>";
$("#sbml-param-table-row").append(paramHTML);

var addParameters = function() {
var parameters = chise.getParameters();
var labelIdx = 0;
$("#sbml-parameters").html(""); // clear the field before populating
for (var param of parameters) {
var param_ = '<div style="display: flex; flex-direction: row; align-items: center; margin-bottom:5px;">'
+ '<table><tbody><tr><td>'
+ '<textarea id="inspector-param-name' + labelIdx + '" cols="8" rows="1" style="min-width: ' + width / 1.25 + 'px;" class="inspector-input-box" placeholder="Name">' + param.name + '</textarea></td>'
+ '</tr><tr><td>'
+ '<input id="inspector-param-value' + labelIdx + '" class="inspector-input-box" type="number" value="' + param.value + '" style="width: ' + (width-1) / 2.51 + 'px;">'
+ '<select id="inspector-param-unit' + labelIdx + '" class="inspector-input-box sbgn-input-medium layout-text" style="width: ' + (width-1) / 2.51 + 'px !important; margin-left: 1px;">'
+ '<option value="litre" selected>litre</option>'
+ '<option value="m3">m³</option>'
+ '</select></td></tr></tbody></table>'
+ '<img id="inspector-param-delete' + labelIdx + '" width="16px" height="16px" class="pointer-button" style="margin-left: 3px;" src="app/img/toolbar/delete-simple.svg">'
+ '</div>';

$("#sbml-parameters").append(param_);

(function (labelIdx){
$('#inspector-param-delete' + labelIdx).off('click').on('click', function() {
var deleteId = chise.getParameters()[labelIdx].id;
chise.removeParameter(deleteId);
addParameters();
})})(labelIdx);

(function (labelIdx){
$('#inspector-param-name' + labelIdx).off('change').on('change', function() {
var name = document.getElementById("inspector-param-name" + labelIdx).value;
var modifyId = chise.getParameters()[labelIdx].id;
chise.setParameter(modifyId, "name", name);
})})(labelIdx);

(function (labelIdx){
$('#inspector-param-value' + labelIdx).off('change').on('change', function() {
var value = parseFloat(document.getElementById("inspector-param-value" + labelIdx).value);
var modifyId = chise.getParameters()[labelIdx].id;
chise.setParameter(modifyId, "value", value);
})})(labelIdx);

labelIdx += 1;
}
param_ = '<img width="16px" height="16px" id="inspector-add-param" src="app/img/add.svg" class="pointer-button">';
$("#sbml-parameters").append(param_);
$("#inspector-add-param").off('click').on('click', function() {
chise.addParameter("", 0, "", true);
addParameters(); // Re-render after adding a new parameter
});

return this;
return this;
}

addParameters();
}
});

Expand Down Expand Up @@ -3602,11 +3674,25 @@ var sbmlKineticLawView = Backbone.View.extend({
})
$(self.el).modal("show");

var kineticLaw = document.getElementById('kinetic-law-field');

setTimeout(() => {
kineticLaw.value = node.data('simulation')['kineticLawVisible'] || "";
}, 200)
var chiseInstance = appUtilities.getActiveChiseInstance();
var cy = chiseInstance.getCy();

var kineticLaw = document.getElementById('kinetic-law-field');
var kineticLawRawText = node.data('simulation')['kineticLaw'] || "";
let idSorted = idArray.map((value, index) => ({ value, index }));
idSorted.sort((a, b) => b.value.length - a.value.length);
let replacementMap = new Map(
idSorted.map(function (id, i) {
var node = cy.getElementById(id.value);
return [id.value, '[' + (node.data('label') || node.data('id')) + '_' + id.index + ']'];
}
));
function escapeRegExp(text) {
return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
}
let regex = new RegExp(idSorted.map((id) => (escapeRegExp(id.value))).join("|"), "g");
kineticLawRawText = kineticLawRawText.replace(regex, match => replacementMap.get(match));
kineticLaw.value = kineticLawRawText;

$(document)
.off("click", "#save-kinetic-law")
Expand All @@ -3615,9 +3701,8 @@ var sbmlKineticLawView = Backbone.View.extend({
var kineticLawText = kineticLaw.value;
const result = kineticLawText.replace(/\[(.+?)_(\d+)]/g, (match, prefix, intStr) => {
const intValue = parseInt(intStr, 10);
return `(${idArray[intValue]})`;
return `${idArray[intValue]}`;
});
node.data('simulation')['kineticLawVisible'] = kineticLawText;
node.data('simulation')['kineticLaw'] = result;
$(self.el).modal("toggle");
});
Expand Down
68 changes: 39 additions & 29 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2220,45 +2220,55 @@ <h4 class="modal-title">Layout Properties</h4>
</script>

<script type="text/template" id="map-tab-simulation-template">
<table class="table-condensed layout-table dialog-table map-panel-table">
<tbody>
<tr>
<td class="header">
<span class="add-on layout-text" title="Starting time for the simulation"> Start Time (s) </span>
</td>
<td>
<input id="inspector-simulation-start" class="inspector-input-box" type="number" style="width: 50px;" value="0">
</td>
<div class="panel-body">
<table class="layout-table map-panel-table">
<tr id="sbml-param-table-row">
<!-- this field will be filled in during run time -->
</tr>
<tr>
<td class="header">
<span class="add-on layout-text" title="Stopping time for the simulation"> End Time (s) </span>
</td>
<td>
<input id="inspector-simulation-end" class="inspector-input-box" type="number" style="width: 50px;" value="10">
</td>
</tr>
<tr>
<td class="header">
<span class="add-on layout-text" title="Step count for the simulation"> Step Count </span>
</td>
<td>
<input id="inspector-simulation-step" class="inspector-input-box integer-input" type="number" min="0" style="width: 50px;" value="10">
</td>
</tr>
</tbody>
</table>
</table>
</div>
<div class="panel-body palette-panel-body" style="border-top: 1px solid #ddd; margin-top: 4px;">
<table class="table-condensed layout-table dialog-table map-panel-table">
<tbody>
<tr>
<td class="header">
<span class="add-on layout-text" title="Starting time for the simulation"> Start Time (s) </span>
</td>
<td>
<input id="inspector-simulation-start" class="inspector-input-box" type="number" style="width: 50px;" value="0">
</td>
</tr>
<tr>
<td class="header">
<span class="add-on layout-text" title="Stopping time for the simulation"> End Time (s) </span>
</td>
<td>
<input id="inspector-simulation-end" class="inspector-input-box" type="number" style="width: 50px;" value="10">
</td>
</tr>
<tr>
<td class="header">
<span class="add-on layout-text" title="Step count for the simulation"> Step Count </span>
</td>
<td>
<input id="inspector-simulation-step" class="inspector-input-box integer-input" type="number" min="0" style="width: 50px;" value="50">
</td>
</tr>
</tbody>
</table>
</div>
<div style="text-align: center; margin-top: 5px;">
<button class="btn btn-default" id="map-simulate-button">Simulate</button>
<button class="btn btn-default" id="map-simulation-default-button">Default</button>
<div>
</script>

<script type="text/template" id="simulation-view-template">
<div class="modal-dialog sbgn-modal-dialog" style="width: 800px;">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"></button>
<h4 class="modal-title" style="text-align: center">Simulation</h4>
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h4 class="modal-title">Simulation Results</h4>
</div>
<div class="modal-body" style="display: flex; justify-content: center; align-items: center;">
<div id="simulation-plot"></div>
Expand Down
4 changes: 2 additions & 2 deletions simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
import tellurium as te

sbmlString: str = sys.argv[1]
start: int = int(sys.argv[2])
stop: int = int(sys.argv[3])
start: int = float(sys.argv[2])
stop: int = float(sys.argv[3])
step: int = int(sys.argv[4])

document: SBMLDocument = readSBMLFromString(sbmlString)
Expand Down