Skip to content

Commit 89a4d14

Browse files
authored
Merge pull request #1642 from virtualcell/dan-ss-enableSSLD
SpringSaLaD UI bug fixes, enhancements
2 parents 6652779 + f3d9c39 commit 89a4d14

15 files changed

Lines changed: 212 additions & 106 deletions

File tree

.github/workflows/ci_cd.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,9 @@ jobs:
106106
steps:
107107
- uses: actions/checkout@v4
108108

109+
- name: Set Docker API version workaround
110+
run: echo "api.version=1.44" >> ~/.docker-java.properties
111+
109112
- name: Install hdf5 tools needed for testing
110113
run: sudo apt -y update && sudo apt -y install hdf5-tools
111114

pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@
145145
<jai-core.version>1.1.3</jai-core.version>
146146
<javax-mail.version>1.4</javax-mail.version>
147147
<jsbml.version>1.6.1-VCELL</jsbml.version>
148+
<json-smart.version>2.4.11</json-smart.version>
148149
<jung-api.version>2.1.1</jung-api.version>
149150
<jung-algorithms.version>2.1.1</jung-algorithms.version>
150151
<jung-graph-impl.version>2.1.1</jung-graph-impl.version>

vcell-client/src/main/java/cbit/vcell/client/data/SimulationWorkspaceModelInfo.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -379,17 +379,17 @@ public void populateDataSymbolMetadata(ColumnDescription[] cdArray) {
379379
String columnName = cd.getName();
380380
ModelCategoryType filterCategory = null; // parse name to find the filter category
381381
SsldUtils.LangevinResult lr = SsldUtils.LangevinResult.fromString(columnName);
382-
if(!(lr.qualifier.equals("FREE") || lr.qualifier.equals("BOUND") || lr.qualifier.equals("TOTAL"))) {
383-
System.out.println("Ignoring LangevinResult token: " + columnName);
382+
if(lr.qualifier.equals(SsldUtils.Qualifier.NONE)) {
383+
System.out.println("Ignoring LangevinResult token: " + columnName + ", qualifier missing");
384384
continue;
385385
}
386386
if(!lr.molecule.isEmpty() && lr.site.isEmpty() && lr.state.isEmpty()) {
387387
filterCategory = BioModelCategoryType.Molecules;
388-
} else if(lr.qualifier.equals("FREE") && !lr.molecule.isEmpty() && !lr.site.isEmpty() && !lr.state.isEmpty()) {
388+
} else if(lr.qualifier.equals(SsldUtils.Qualifier.FREE) && !lr.molecule.isEmpty() && !lr.site.isEmpty() && !lr.state.isEmpty()) {
389389
filterCategory = BioModelCategoryType.FreeSites;
390-
} else if(lr.qualifier.equals("BOUND") && !lr.molecule.isEmpty() && !lr.site.isEmpty() && !lr.state.isEmpty()) {
390+
} else if(lr.qualifier.equals(SsldUtils.Qualifier.BOUND) && !lr.molecule.isEmpty() && !lr.site.isEmpty() && !lr.state.isEmpty()) {
391391
filterCategory = BioModelCategoryType.BoundSites;
392-
} else if(lr.qualifier.equals("TOTAL") && !lr.molecule.isEmpty() && !lr.site.isEmpty() && !lr.state.isEmpty()) {
392+
} else if(lr.qualifier.equals(SsldUtils.Qualifier.TOTAL) && !lr.molecule.isEmpty() && !lr.site.isEmpty() && !lr.state.isEmpty()) {
393393
filterCategory = BioModelCategoryType.TotalSites;
394394
}
395395
VCUnitDefinition unit = null;

vcell-client/src/main/java/cbit/vcell/client/desktop/biomodel/BioModelEditorApplicationsPanel.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import javax.swing.SwingConstants;
2828
import javax.swing.UIManager;
2929

30+
import org.vcell.model.ssld.SsldUtils;
3031
import org.vcell.util.gui.DialogUtils;
3132
import org.vcell.util.gui.DownArrowIcon;
3233

@@ -355,8 +356,7 @@ private javax.swing.JPopupMenu getNewAppPopupMenu() {
355356
appsPopupMenu.add(appNewDeterministicApp);
356357
appsPopupMenu.add(appNewStochApp);
357358
appsPopupMenu.add(appNewRulebasedApp);
358-
boolean enableSpringSaLaD = PropertyLoader.getBooleanProperty(PropertyLoader.enableSpringSaLaD,
359-
PropertyLoader.enableSpringSaLaD_default_value);
359+
boolean enableSpringSaLaD = SsldUtils.isSsldEnabled();
360360
if (enableSpringSaLaD) {
361361
appsPopupMenu.add(appNewSpringSaLaDApp);
362362
}

vcell-client/src/main/java/cbit/vcell/client/desktop/biomodel/DocumentEditor.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,10 @@
4949
import org.apache.logging.log4j.LogManager;
5050
import org.apache.logging.log4j.Logger;
5151
import org.vcell.client.logicalwindow.LWNamespace;
52+
import org.vcell.model.ssld.SsldUtils;
5253
import org.vcell.util.Issue;
5354
import org.vcell.util.Issue.Severity;
55+
import org.vcell.util.document.VCellSoftwareVersion;
5456
import org.vcell.util.gui.DialogUtils;
5557
import org.vcell.util.gui.GuiUtils;
5658
import org.vcell.util.gui.JTabbedPaneEnhanced;
@@ -721,8 +723,8 @@ private void construcutPopupMenu() {
721723
addNewAppMenu.add(addNewAppDeterministicMenuItem);
722724
addNewAppMenu.add(addNewAppStochasticMenuItem);
723725
addNewAppMenu.add(addNewAppRulebasedMenuItem);
724-
boolean enableSpringSaLaD = PropertyLoader.getBooleanProperty(PropertyLoader.enableSpringSaLaD,
725-
PropertyLoader.enableSpringSaLaD_default_value);
726+
727+
boolean enableSpringSaLaD = SsldUtils.isSsldEnabled();
726728
if (enableSpringSaLaD) {
727729
addNewAppMenu.add(addNewAppSpringSaLaDMenuItem);
728730
}

vcell-core/src/main/java/cbit/vcell/export/SpringSaLaDExporter.java

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import javax.swing.JOptionPane;
2424
import javax.swing.JPanel;
2525

26+
import cbit.vcell.mapping.*;
2627
import org.vcell.model.rbm.ComponentStateDefinition;
2728
import org.vcell.model.rbm.MolecularComponent;
2829
import org.vcell.model.rbm.MolecularType;
@@ -33,12 +34,6 @@
3334
import cbit.vcell.biomodel.BioModel;
3435
import cbit.vcell.geometry.Geometry;
3536
import cbit.vcell.geometry.GeometrySpec;
36-
import cbit.vcell.mapping.GeometryContext;
37-
import cbit.vcell.mapping.ReactionContext;
38-
import cbit.vcell.mapping.ReactionRuleSpec;
39-
import cbit.vcell.mapping.ReactionSpec;
40-
import cbit.vcell.mapping.SimulationContext;
41-
import cbit.vcell.mapping.SpeciesContextSpec;
4237
import cbit.vcell.model.Model;
4338
import cbit.vcell.model.RbmKineticLaw;
4439
import cbit.vcell.model.ReactionRule;
@@ -445,12 +440,22 @@ public static void writeStateCounters(SimulationContext simContext, StringBuilde
445440
List<MolecularComponent> mcList = mt.getComponentList();
446441
for(MolecularComponent mc : mcList) {
447442
List<ComponentStateDefinition> csdList = mc.getComponentStateDefinitions();
448-
for(ComponentStateDefinition csd : csdList) {
443+
if(csdList == null || csdList.isEmpty()) {
444+
ComponentStateDefinition fakeCsd = new ComponentStateDefinition(SiteAttributesSpec.StateZero);
449445
sb.append("'").append(mt.getName()).append("' : ")
450-
.append("'").append(mc.getName()).append("' : ")
451-
.append("'").append(csd.getName()).append("' : ")
452-
.append("Measure Total Free Bound");
446+
.append("'").append(mc.getName()).append("' : ")
447+
.append("'").append(fakeCsd.getName()).append("' : ")
448+
.append("Measure Total Free Bound");
453449
sb.append("\n");
450+
451+
} else {
452+
for (ComponentStateDefinition csd : csdList) {
453+
sb.append("'").append(mt.getName()).append("' : ")
454+
.append("'").append(mc.getName()).append("' : ")
455+
.append("'").append(csd.getName()).append("' : ")
456+
.append("Measure Total Free Bound");
457+
sb.append("\n");
458+
}
454459
}
455460
}
456461
}

vcell-core/src/main/java/cbit/vcell/mapping/ReactionRuleSpec.java

Lines changed: 36 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,8 @@ public void firePropertyChange(String propertyName, Object oldValue, Object newV
220220
public static final String BondTransitionCounter = "bondTransitionCounter";
221221
public static final String CspReactantBond = "cspReactantBond";
222222

223+
public static final String NoStateSpecified = "NoStateSpecified"; // used when we have a binding transition but no state specified for the transitioning site
224+
223225
// --------------------------------------------------------------------------------------------------------
224226
public void analizeReaction(Map<String, Object> analysisResults) {
225227
List<ReactantPattern> rpList = reactionRule.getReactantPatterns();
@@ -347,7 +349,7 @@ public void analizeReaction(Map<String, Object> analysisResults) {
347349

348350
String stateReactant;
349351
if(cspReactant == null) {
350-
stateReactant = "ERROR";
352+
stateReactant = NoStateSpecified;
351353
} else if(cspReactant.isAny()) {
352354
stateReactant = ANY_STATE_STRING;
353355
} else {
@@ -424,14 +426,10 @@ public TransitionCondition getTransitionCondition(Map<String, Object> analysisRe
424426
for(MolecularComponentPattern mcpReactant : mtpReactant.getComponentPatternList()) {
425427
ComponentStatePattern cspReactant = mcpReactant.getComponentStatePattern();
426428
BondType bondTypeReactant = mcpReactant.getBondType();
429+
427430
if(cspReactant == null) {
428-
return null; // all sites must have at least one state
429-
}
430-
// if(!cspReactant.isAny() && BondType.Possible != bondTypeReactant) {
431-
// // this is the transition site, bond must be "Possible"
432-
// return null;
433-
// }
434-
if(!cspReactant.isAny()) {
431+
numAnyStates++; // no state for this site, so counts as any (no transition)
432+
} else if(!cspReactant.isAny()) {
435433
;
436434
} else {
437435
numAnyStates++;
@@ -491,9 +489,6 @@ public enum TransitionCondition { // everywhere internally in vcell we use RBM b
491489
}
492490
for(MolecularComponentPattern mcpReactant : mtpReactant.getComponentPatternList()) {
493491
ComponentStatePattern cspReactant = mcpReactant.getComponentStatePattern();
494-
if(cspReactant == null) {
495-
return null; // all sites must have at least one state
496-
}
497492
BondType bondTypeReactant = mcpReactant.getBondType();
498493
if(BondType.Specified == bondTypeReactant) { // we must have exactly one Specified bond, all the others must be Possible
499494
numSpecifiedBonds[i]++; // the condition binding site has bond type "Exists"
@@ -502,7 +497,7 @@ public enum TransitionCondition { // everywhere internally in vcell we use RBM b
502497
return null; // all other bonds in the reactant must be of type "Possible"
503498
}
504499

505-
if(!cspReactant.isAny()) { // we also need to reack explicit states
500+
if(cspReactant != null && !cspReactant.isAny()) { // we also need to reack explicit states
506501
numExplicitStates[i]++;
507502
totalExplicitStates++;
508503
mcpExplicitStates[i] = mcpReactant;
@@ -603,7 +598,7 @@ private boolean isAllostericReaction(Map<String, Object> analysisResults) {
603598
}
604599
for(MolecularComponentPattern mcp : mcpReactantList) {
605600
if(mcp.getComponentStatePattern() == null) {
606-
return false; // all sites must have at least one state
601+
continue; // if no state defined for this site, counts as any state, so we skip it
607602
}
608603
if(!mcp.getComponentStatePattern().isAny()) {
609604
explicitReactantStatesSet.add(mcp.getComponentStatePattern().getComponentStateDefinition());
@@ -613,7 +608,7 @@ private boolean isAllostericReaction(Map<String, Object> analysisResults) {
613608
int matchedStates = 0;
614609
int unmatchedStates = 0;
615610
for(MolecularComponentPattern mcp : mcpProductList) {
616-
if(!mcp.getComponentStatePattern().isAny()) {
611+
if(mcp.getComponentStatePattern() != null && !mcp.getComponentStatePattern().isAny()) {
617612
ComponentStateDefinition csdProductExplicit = mcp.getComponentStatePattern().getComponentStateDefinition();
618613
if(explicitReactantStatesSet.contains(csdProductExplicit)) {
619614
matchedStates++;
@@ -669,8 +664,8 @@ private boolean isBindingReaction(Map<String, Object> analysisResults) {
669664
for(MolecularComponentPattern mcp : mtp.getComponentPatternList()) {
670665
BondType bt = mcp.getBondType();
671666
ComponentStatePattern csp = mcp.getComponentStatePattern();
672-
if(csp == null || (!csp.isAny() && BondType.Possible == bt)) {
673-
// all the sites not binding must be in Any state
667+
if((csp != null && !csp.isAny()) && BondType.Possible == bt) {
668+
// all the sites not binding must be in Any state or must have no state defined
674669
return false;
675670
}
676671
}
@@ -754,7 +749,9 @@ private void writeTransitionData(StringBuilder sb, Subtype subtype, Map<String,
754749
for(MolecularComponentPattern mcpCandidate : mtpConditionReactant.getComponentPatternList()) {
755750
if(BondType.Specified == mcpCandidate.getBondType()) {
756751
mcpConditionReactant = mcpCandidate; // found the bond condition site, it's the one with bond type "Specified"
757-
if(!mcpConditionReactant.getComponentStatePattern().isAny()) {
752+
if(mcpConditionReactant.getComponentStatePattern() == null) {
753+
stateConditionReactant = SiteAttributesSpec.StateZero;
754+
} else if(!mcpConditionReactant.getComponentStatePattern().isAny()) {
758755
stateConditionReactant = mcpConditionReactant.getComponentStatePattern().getComponentStateDefinition().getName();
759756
}
760757
break;
@@ -799,7 +796,7 @@ private void writeAllostericData(StringBuilder sb, Subtype subtype, Map<String,
799796
if(mcpTransitionReactant == mcp) {
800797
continue; // found the allosteric site index
801798
}
802-
if(mcp.getComponentStatePattern().isAny()) {
799+
if(mcp.getComponentStatePattern() == null || mcp.getComponentStatePattern().isAny()) {
803800
continue; // the allosteric state must be explicit
804801
}
805802
mcpAllostericReactant = mcp;
@@ -835,7 +832,13 @@ private void writeBindingData(StringBuilder sb, Subtype subtype, Map<String, Obj
835832
MolecularComponentPattern mcpReactantOne = (MolecularComponentPattern)analysisResults.get(McpReactantBond + "1");
836833
MolecularComponentPattern mcpReactantTwo = (MolecularComponentPattern)analysisResults.get(McpReactantBond + "2");
837834
String stateReactantOne = (String)analysisResults.get(CspReactantBond + "1");
835+
if(stateReactantOne.equals(NoStateSpecified)) {
836+
stateReactantOne = SiteAttributesSpec.StateZero;
837+
}
838838
String stateReactantTwo = (String)analysisResults.get(CspReactantBond + "2");
839+
if(stateReactantTwo.equals(NoStateSpecified)) {
840+
stateReactantTwo = SiteAttributesSpec.StateZero;
841+
}
839842
if(mtpReactantOne == null || mtpReactantTwo == null || mcpReactantOne == null || mcpReactantTwo == null) {
840843
throw new RuntimeException("writeBindingData() error: something is wrong");
841844
}
@@ -1215,6 +1218,13 @@ public void gatherIssues(IssueContext issueContext, List<Issue> issueList, React
12151218
siteAttributesMapTwo = scs.getSiteAttributesMap();
12161219
}
12171220
}
1221+
if(siteAttributesMapOne == null) {
1222+
// this may happen if the reaction uses a Molecule for which no Species was yet defined
1223+
String msg = "Could not match reactant '" + mtOursOne.getName() + "' to any Species in the model.";
1224+
String tip = "Verify that a Species has been defined for the molecule type '" + mtOursOne.getName() + "'.";
1225+
issueList.add(new Issue(r, issueContext, IssueCategory.Identifiers, msg, tip, Issue.Severity.ERROR));
1226+
return;
1227+
}
12181228
SiteAttributesSpec sasOne = null;
12191229
SiteAttributesSpec sasTwo = null;
12201230
for(Map.Entry<MolecularComponentPattern, SiteAttributesSpec> entry : siteAttributesMapOne.entrySet()) {
@@ -1234,8 +1244,14 @@ public void gatherIssues(IssueContext issueContext, List<Issue> issueList, React
12341244
// happened when the reaction was between 2 molecules of the same type A + A -> A.A
12351245
// the temp fixwas: we check for non-null siteAttributesMapTwo
12361246
// as from feb 24, 2025 this was fixed and siteAttributesMapTwo should never be null
1237-
if(siteAttributesMapTwo == null) {
1238-
throw new RuntimeException("Unexpected null value for siteAttributesMapTwo");
1247+
// feb 18 2026 - it actually may still be null if no Species is defined for the molecule type,
1248+
// // so we keep the check and fire an issue if it's nevertheless null
1249+
if(siteAttributesMapTwo == null) {
1250+
// this may happen if the reaction uses a Molecule for which no Species was yet defined
1251+
String msg = "Could not match reactant '" + mtOursTwo.getName() + "' to any Species in the model.";
1252+
String tip = "Verify that a Species has been defined for the molecule type '" + mtOursTwo.getName() + "'.";
1253+
issueList.add(new Issue(r, issueContext, IssueCategory.Identifiers, msg, tip, Issue.Severity.ERROR));
1254+
return;
12391255
}
12401256
if(sasOne != null && sasTwo != null) {
12411257
for(Map.Entry<MolecularComponentPattern, SiteAttributesSpec> entry : siteAttributesMapTwo.entrySet()) {

vcell-core/src/main/java/cbit/vcell/mapping/SiteAttributesSpec.java

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ public class SiteAttributesSpec implements Serializable, Identifiable, Displayab
4646
private Structure fieldLocation = null; // feature or membrane
4747
private Coordinate fieldCoordinate = new Coordinate(0,0,0); // double x,y,z; has distanceTo()
4848
private NamedColor fieldColor = Colors.RED;
49+
50+
public static final String StateZero = "State0"; // default state name if no states are defined in the model; used for I/O only, not stored in the model
4951
// the ComponentStatePattern must not be Any; can be recovered from the MolecularComponentPattern
5052
// the BondType must be None, can be recovered from the MolecularComponentPattern
5153

@@ -131,8 +133,12 @@ public void writeType(StringBuilder sb) { // I/O the Site Type (mMlecularCompon
131133
sb.append("TYPE: Name \"" + mc.getName() + "\"");
132134
sb.append(" Radius " + IOHelp.DF[5].format(getRadius()) + " D " + IOHelp.DF[3].format(getDiffusionRate()) + " Color " + getColor().getName());
133135
sb.append(" STATES ");
134-
for (ComponentStateDefinition state : csdList) {
135-
sb.append("\"" + state.getName() + "\"" + " ");
136+
if(csdList == null || csdList.isEmpty()) {
137+
sb.append("\"" + StateZero + "\"" + " ");
138+
} else {
139+
for (ComponentStateDefinition state : csdList) {
140+
sb.append("\"" + state.getName() + "\"" + " ");
141+
}
136142
}
137143
sb.append("\n");
138144
}
@@ -141,14 +147,12 @@ public void writeSite(StringBuilder sb) {
141147
throw new RuntimeException("writeSite(): MolecularComponentPattern is null");
142148
}
143149
ComponentStatePattern csp = getMolecularComponentPattern().getComponentStatePattern();
144-
if(csp == null) {
145-
sb.append("SITE " + (this.getIndex()-1) + " : " + getLocation().getName() + " : Initial State '" + "ERROR: at least one State is needed" + "'");
146-
sb.append("\n");
147-
return;
148-
}
149-
ComponentStateDefinition csd = csp.getComponentStateDefinition();
150-
if(csd == null) {
151-
throw new RuntimeException("writeSite(): csd is null");
150+
ComponentStateDefinition csd = null;
151+
if(csp != null) {
152+
csd = csp.getComponentStateDefinition();
153+
}
154+
if(csp == null || csd == null) {
155+
csd = new ComponentStateDefinition(StateZero);
152156
}
153157
String initialState = csd.getName();
154158
sb.append("SITE " + (this.getIndex()-1) + " : " + getLocation().getName() + " : Initial State '" + initialState + "'");

vcell-core/src/main/java/cbit/vcell/mapping/SpeciesContextSpec.java

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1289,16 +1289,6 @@ public void gatherIssues(IssueContext issueContext, List<Issue> issueVector){
12891289
issueVector.add(new Issue(this, issueContext, IssueCategory.Identifiers, msg, tip, Issue.Severity.WARNING));
12901290
return;
12911291
}
1292-
for(MolecularComponentPattern mcp : mcpList){
1293-
MolecularComponent mc = mcp.getMolecularComponent();
1294-
List<ComponentStateDefinition> csd = mc.getComponentStateDefinitions();
1295-
if(csd.size() < 1){
1296-
String msg = "Each Site must have at least one State.";
1297-
String tip = msg;
1298-
issueVector.add(new Issue(this, issueContext, IssueCategory.Identifiers, msg, tip, Issue.Severity.WARNING));
1299-
return;
1300-
}
1301-
}
13021292
for(MolecularInternalLinkSpec mils : getInternalLinkSet()){
13031293
if(mils.getMolecularComponentPatternOne() == mils.getMolecularComponentPatternTwo()){
13041294
String msg = "Both sites of the Link are identical. A site cannot be linked to itself.";
@@ -1342,11 +1332,6 @@ public void gatherIssues(IssueContext issueContext, List<Issue> issueVector){
13421332
}
13431333
}
13441334
}
1345-
if(mcpList.size() == 1){
1346-
String msg = "Internal Links are possible only when the Molecule has at least 2 sites.";
1347-
String tip = msg;
1348-
issueVector.add(new Issue(this, issueContext, IssueCategory.Identifiers, msg, tip, Issue.Severity.WARNING));
1349-
}
13501335

13511336
// if the species context is on membrane it must have a site named Anchor on the membrane, the other sites must NOT be on membrane
13521337
Structure struct = sc.getStructure();

0 commit comments

Comments
 (0)