From 754f263ac8074da0c050e43f749eb6033a90fc29 Mon Sep 17 00:00:00 2001 From: Dan Vasilescu Date: Tue, 21 Apr 2026 15:19:36 -0400 Subject: [PATCH 01/16] Generalized use of LinkNode. --- .../mapping/gui/LinkSpecsTableModel.java | 10 +- .../gui/MolecularStructuresPanel.java | 13 +- .../graph/SpeciesContextSpecLargeShape.java | 28 ++-- .../vcell/mapping/LangevinMathMapping.java | 10 +- .../mapping/MolecularInternalLinkSpec.java | 132 ++++++++++-------- .../vcell/mapping/SpeciesContextSpec.java | 72 ++++++---- .../cbit/vcell/mapping/StructuralSite.java | 48 +++++++ .../main/java/cbit/vcell/xml/Xmlproducer.java | 4 +- .../java/org/vcell/model/rbm/LinkNode.java | 13 ++ .../model/rbm/MolecularComponentPattern.java | 11 +- 10 files changed, 221 insertions(+), 120 deletions(-) create mode 100644 vcell-core/src/main/java/cbit/vcell/mapping/StructuralSite.java create mode 100644 vcell-core/src/main/java/org/vcell/model/rbm/LinkNode.java diff --git a/vcell-client/src/main/java/cbit/vcell/mapping/gui/LinkSpecsTableModel.java b/vcell-client/src/main/java/cbit/vcell/mapping/gui/LinkSpecsTableModel.java index 91cfc0f201..b108fef6ce 100644 --- a/vcell-client/src/main/java/cbit/vcell/mapping/gui/LinkSpecsTableModel.java +++ b/vcell-client/src/main/java/cbit/vcell/mapping/gui/LinkSpecsTableModel.java @@ -94,8 +94,6 @@ public void setValueAt(Object aValue, int row, int col) { ColumnType columnType = columns.get(col); SpeciesContextSpec scs = getSpeciesContextSpec(); - MolecularComponentPattern mcpOne = mils.getMolecularComponentPatternOne(); - MolecularComponentPattern mcpTwo = mils. getMolecularComponentPatternTwo(); SiteAttributesSpec sasOne = mils. getSite1(); SiteAttributesSpec sasTwo = mils. getSite2(); @@ -267,11 +265,11 @@ public int compare(MolecularInternalLinkSpec mils1, MolecularInternalLinkSpec mi if(fieldSpeciesContextSpec != scs) { throw new RuntimeException("SpeciesContextSpec inconsistent."); } - MolecularComponentPattern mcp1 = mils1.getLink().one; - MolecularComponentPattern mcp2 = mils2.getLink().one; + LinkNode node1 = mils1.getLink().one; + LinkNode node2 = mils2.getLink().one; Map siteAttributesMap = getSpeciesContextSpec().getSiteAttributesMap(); - SiteAttributesSpec sas1 = siteAttributesMap.get(mcp1); - SiteAttributesSpec sas2 = siteAttributesMap.get(mcp2); + SiteAttributesSpec sas1 = siteAttributesMap.get(node1); + SiteAttributesSpec sas2 = siteAttributesMap.get(node2); Double z1 = sas1.getCoordinate().getZ(); Double z2 = sas2.getCoordinate().getZ(); return z1.compareTo(z2); diff --git a/vcell-client/src/main/java/org/vcell/model/springsalad/gui/MolecularStructuresPanel.java b/vcell-client/src/main/java/org/vcell/model/springsalad/gui/MolecularStructuresPanel.java index 0686778c3e..3d0e8938ad 100644 --- a/vcell-client/src/main/java/org/vcell/model/springsalad/gui/MolecularStructuresPanel.java +++ b/vcell-client/src/main/java/org/vcell/model/springsalad/gui/MolecularStructuresPanel.java @@ -25,6 +25,7 @@ import cbit.vcell.parser.Expression; import cbit.vcell.units.VCUnitDefinition; import org.apache.commons.lang3.StringEscapeUtils; +import org.vcell.model.rbm.LinkNode; import org.vcell.model.rbm.MolecularComponentPattern; import org.vcell.model.rbm.MolecularTypePattern; import org.vcell.model.rbm.SpeciesPattern; @@ -32,7 +33,6 @@ import org.vcell.util.gui.*; import org.vcell.util.gui.ScrollTable.ScrollTableBooleanCellRenderer; import org.vcell.util.gui.sorttable.SortTableModel; -import org.vcell.util.springsalad.Colors; import org.vcell.util.springsalad.NamedColor; import javax.swing.*; @@ -42,7 +42,6 @@ import javax.swing.event.ListSelectionListener; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.TableModel; -import javax.swing.table.TableRowSorter; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; @@ -630,16 +629,16 @@ public Component getTableCellRendererComponent(JTable table, Object value, boole if (table.getModel() instanceof LinkSpecsTableModel) { if (value instanceof MolecularInternalLinkSpec) { MolecularInternalLinkSpec mils = (MolecularInternalLinkSpec)value; - MolecularComponentPattern firstMcp = mils.getMolecularComponentPatternOne(); - MolecularComponentPattern secondtMcp = mils.getMolecularComponentPatternTwo(); - setText(firstMcp.getMolecularComponent().getName() + " :: " + secondtMcp.getMolecularComponent().getName()); + LinkNode firstNode = mils.getLinkNodeOne(); + LinkNode secondNode = mils.getLinkNodeTwo(); + setText(firstNode.getName() + " :: " + secondNode.getName()); SpeciesContextSpec scs = mils.getSpeciesContextSpec(); if(fieldSpeciesContextSpec != scs) { throw new RuntimeException("SpeciesContextSpec inconsistent."); } Map siteAttributesMap = getSpeciesContextSpec().getSiteAttributesMap(); - SiteAttributesSpec sasFirst = siteAttributesMap.get(firstMcp); - SiteAttributesSpec sasSecond = siteAttributesMap.get(secondtMcp); + SiteAttributesSpec sasFirst = siteAttributesMap.get(firstNode); + SiteAttributesSpec sasSecond = siteAttributesMap.get(secondNode); NamedColor ncFirst = sasFirst.getColor(); NamedColor ncSecond = sasSecond.getColor(); Icon iconFirst = new ColorIcon(10,10,ncFirst.getColor(), true); diff --git a/vcell-core/src/main/java/cbit/vcell/graph/SpeciesContextSpecLargeShape.java b/vcell-core/src/main/java/cbit/vcell/graph/SpeciesContextSpecLargeShape.java index 875697883c..69b9cd9761 100644 --- a/vcell-core/src/main/java/cbit/vcell/graph/SpeciesContextSpecLargeShape.java +++ b/vcell-core/src/main/java/cbit/vcell/graph/SpeciesContextSpecLargeShape.java @@ -3,6 +3,7 @@ import cbit.vcell.mapping.MolecularInternalLinkSpec; import cbit.vcell.mapping.SiteAttributesSpec; import cbit.vcell.mapping.SpeciesContextSpec; +import cbit.vcell.mapping.StructuralSite; import cbit.vcell.model.SpeciesContext; import cbit.vcell.model.Structure; import org.vcell.model.rbm.*; @@ -413,12 +414,23 @@ public void paintSelf(Graphics g, boolean bPaintContour) { return; } Map sasMap = scs.getSiteAttributesMap(); + Map structuralSiteAttributesMap = scs.getStructuralSiteAttributesMap(); + Map merged = new LinkedHashMap<>(); + // Add physiological sites + for (Map.Entry e : sasMap.entrySet()) { + merged.put(e.getKey(), e.getValue()); + } + // Add structural sites + for (Map.Entry e : structuralSiteAttributesMap.entrySet()) { + merged.put(e.getKey(), e.getValue()); + } + Set internalLinkSet = scs.getInternalLinkSet(); // we draw all objects as unselected first, and then we redraw the selected objects on top of everything else, // so that they look highlighted and are not hidden behind other objects - for(MolecularInternalLinkSpec mils : internalLinkSet) { - Pair link = mils.getLink(); + for(MolecularInternalLinkSpec mils : internalLinkSet) { // draw links first, so that they are under the sites + Pair link = mils.getLink(); SiteAttributesSpec sas1 = sasMap.get(link.one); SiteAttributesSpec sas2 = sasMap.get(link.two); double x1 = x_offset + sas1.getCoordinate().getZ(); @@ -429,8 +441,8 @@ public void paintSelf(Graphics g, boolean bPaintContour) { lineToMilsMap.put(line, mils); milsToLineMap.put(mils, line); } - for(MolecularComponentPattern mcp : mtp.getComponentPatternList()) { - SiteAttributesSpec sas = sasMap.get(mcp); + for (Map.Entry entry : merged.entrySet()) { // draw the sites now + SiteAttributesSpec sas = entry.getValue(); Coordinate coord = sas.getCoordinate(); double radius = sas.getRadius(); NamedColor color = sas.getColor(); @@ -456,7 +468,7 @@ public void paintSelf(Graphics g, boolean bPaintContour) { ellipseToSasMap.put(newOval, sas); } if(milsSelected != null) { - Pair link = milsSelected.getLink(); + Pair link = milsSelected.getLink(); SiteAttributesSpec sas1 = sasMap.get(link.one); SiteAttributesSpec sas2 = sasMap.get(link.two); double x1 = x_offset + sas1.getCoordinate().getZ(); @@ -474,8 +486,8 @@ public void paintSelf(Graphics g, boolean bPaintContour) { // we now redraw the last selected object on top of everything else if(lastSelectedObject != null) { - if(lastSelectedObject instanceof MolecularComponentPattern) { - MolecularComponentPattern mcp = (MolecularComponentPattern)lastSelectedObject; + if(lastSelectedObject instanceof LinkNode) { + LinkNode mcp = (LinkNode)lastSelectedObject; SiteAttributesSpec sas = sasMap.get(mcp); Coordinate coord = sas.getCoordinate(); double radius = sas.getRadius(); @@ -489,7 +501,7 @@ public void paintSelf(Graphics g, boolean bPaintContour) { ellipseToSasMap.put(newOval, sas); } else if(lastSelectedObject instanceof MolecularInternalLinkSpec) { MolecularInternalLinkSpec mils = (MolecularInternalLinkSpec)lastSelectedObject; - Pair link = mils.getLink(); + Pair link = mils.getLink(); SiteAttributesSpec sas1 = sasMap.get(link.one); SiteAttributesSpec sas2 = sasMap.get(link.two); double x1 = x_offset + sas1.getCoordinate().getZ(); diff --git a/vcell-core/src/main/java/cbit/vcell/mapping/LangevinMathMapping.java b/vcell-core/src/main/java/cbit/vcell/mapping/LangevinMathMapping.java index 5b51b62947..b2bc208cbd 100644 --- a/vcell-core/src/main/java/cbit/vcell/mapping/LangevinMathMapping.java +++ b/vcell-core/src/main/java/cbit/vcell/mapping/LangevinMathMapping.java @@ -15,13 +15,7 @@ import java.util.*; import cbit.vcell.geometry.surface.GeometricRegion; -import org.vcell.model.rbm.ComponentStateDefinition; -import org.vcell.model.rbm.ComponentStatePattern; -import org.vcell.model.rbm.MolecularComponent; -import org.vcell.model.rbm.MolecularComponentPattern; -import org.vcell.model.rbm.MolecularType; -import org.vcell.model.rbm.MolecularTypePattern; -import org.vcell.model.rbm.SpeciesPattern; +import org.vcell.model.rbm.*; import org.vcell.util.Pair; import org.vcell.util.TokenMangler; @@ -1199,7 +1193,7 @@ private HashMap addSpeciesPatterns throw new RuntimeException("LangevinMathMapping: the internal link set cannot be null"); } for(MolecularInternalLinkSpec mils : internalLinkSet) { - Pair link = mils.getLink(); + Pair link = mils.getLink(); LangevinParticleMolecularComponent one = mcpToLpmc.get(link.one); LangevinParticleMolecularComponent two = mcpToLpmc.get(link.two); // error: Cannot invoke "Object.equals(Object)" because "this.one" is null diff --git a/vcell-core/src/main/java/cbit/vcell/mapping/MolecularInternalLinkSpec.java b/vcell-core/src/main/java/cbit/vcell/mapping/MolecularInternalLinkSpec.java index 0db9a98e1e..8c7141654f 100644 --- a/vcell-core/src/main/java/cbit/vcell/mapping/MolecularInternalLinkSpec.java +++ b/vcell-core/src/main/java/cbit/vcell/mapping/MolecularInternalLinkSpec.java @@ -11,33 +11,25 @@ package cbit.vcell.mapping; import java.awt.Color; import java.awt.Graphics2D; -import java.io.PrintWriter; import java.io.Serializable; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; import cbit.vcell.model.*; +import org.vcell.model.rbm.LinkNode; import org.vcell.model.rbm.MolecularComponentPattern; import org.vcell.model.rbm.MolecularTypePattern; import org.vcell.model.rbm.SpeciesPattern; import org.vcell.util.*; -import org.vcell.util.Issue.IssueCategory; import org.vcell.util.Issue.IssueSource; -import org.vcell.util.IssueContext.ContextType; import org.vcell.util.document.Identifiable; -import org.vcell.util.document.VersionFlag; @SuppressWarnings("serial") public class MolecularInternalLinkSpec implements Identifiable, IssueSource, Matchable, Serializable { private final SpeciesContextSpec fieldSpeciesContextSpec; - private MolecularComponentPattern fieldMolecularComponentPatternOne = null; - private MolecularComponentPattern fieldMolecularComponentPatternTwo = null; + private LinkNode fieldLinkNodeOne = null; + private LinkNode fieldLinkNodeTwo = null; // private double linkLength = 0; // it's a derived value which we don't store, we just compute it at need - public MolecularInternalLinkSpec(SpeciesContextSpec scs, MolecularComponentPattern linkOne, MolecularComponentPattern linkTwo) throws IllegalArgumentException { + public MolecularInternalLinkSpec(SpeciesContextSpec scs, LinkNode linkOne, LinkNode linkTwo) throws IllegalArgumentException { fieldSpeciesContextSpec = scs; SpeciesContext sc = scs.getSpeciesContext(); SpeciesPattern sp = sc.getSpeciesPattern(); @@ -46,12 +38,18 @@ public MolecularInternalLinkSpec(SpeciesContextSpec scs, MolecularComponentPatte } MolecularTypePattern mtp = sp.getMolecularTypePatterns().get(0); // the one and only // sanity check - if(linkOne == null || linkOne.getMolecularComponent() == null) { + if(linkOne == null) { throw new IllegalArgumentException("A link component doesn't exist"); } - if(linkTwo == null || linkTwo.getMolecularComponent() == null) { + if(linkOne instanceof MolecularComponentPattern mcp && mcp.getMolecularComponent() == null) { + throw new IllegalArgumentException("The MolecularComponent of a link component is null"); + } + if(linkTwo == null) { throw new IllegalArgumentException("A link component doesn't exist"); } + if(linkTwo instanceof MolecularComponentPattern mcp && mcp.getMolecularComponent() == null) { + throw new IllegalArgumentException("The MolecularComponent of a link component is null"); + } boolean foundOne = false; boolean foundTwo = false; for(MolecularComponentPattern mpc : mtp.getComponentPatternList()) { @@ -59,42 +57,70 @@ public MolecularInternalLinkSpec(SpeciesContextSpec scs, MolecularComponentPatte foundOne = true; } } + if(!foundOne && linkOne instanceof MolecularComponentPattern) { + throw new IllegalArgumentException("A MolecularComponentPattern link component doesn't match any molecule component"); + } for(MolecularComponentPattern mpc : mtp.getComponentPatternList()) { if(mpc == linkTwo) { foundTwo = true; } } - if(!foundOne || !foundTwo) { - throw new IllegalArgumentException("A link component doesn't match any molecule component"); + if(!foundTwo && linkTwo instanceof MolecularComponentPattern) { + throw new IllegalArgumentException("A MolecularComponentPattern link component doesn't match any molecule component"); } - // order them based on position in the Molecule // we may only have one molecule in the species pattern for the current version of springsalad impl, but who knows in the future - for(MolecularComponentPattern mpc : mtp.getComponentPatternList()) { - if(mpc == linkOne) { // linkOne comes first - fieldMolecularComponentPatternOne = linkOne; - fieldMolecularComponentPatternTwo = linkTwo; - break; - } else if(mpc == linkTwo) { // linkTwo comes first - fieldMolecularComponentPatternOne = linkTwo; - fieldMolecularComponentPatternTwo = linkOne; - break; - } + // deterministic ordering (for equals/hashCode) + if(compareOrder(linkOne, linkTwo) <= 0) { + fieldLinkNodeOne = linkOne; + fieldLinkNodeTwo = linkTwo; + } else { + fieldLinkNodeOne = linkTwo; + fieldLinkNodeTwo = linkOne; + } + } + + private int compareOrder(LinkNode a, LinkNode b) { + if (a == b) { + return 0; + } + if (a == null) { + return -1; + } + if (b == null) { + return 1; + } + + String nameA = a.getName(); + String nameB = b.getName(); + + // Null-safe name comparison + if (nameA == null && nameB == null) { + return 0; + } + if (nameA == null) { + return -1; + } + if (nameB == null) { + return 1; } + + return nameA.compareTo(nameB); } + public SpeciesContextSpec getSpeciesContextSpec() { return fieldSpeciesContextSpec; } - public MolecularComponentPattern getMolecularComponentPatternOne() { - return fieldMolecularComponentPatternOne; + public LinkNode getLinkNodeOne() { + return fieldLinkNodeOne; } - public MolecularComponentPattern getMolecularComponentPatternTwo() { - return fieldMolecularComponentPatternTwo; + public LinkNode getLinkNodeTwo() { + return fieldLinkNodeTwo; } public SiteAttributesSpec getSite1() { - return getSpeciesContextSpec().getSiteAttributesMap().get(fieldMolecularComponentPatternOne); + return getSpeciesContextSpec().getSiteAttributesMap().get(fieldLinkNodeOne); } public SiteAttributesSpec getSite2() { - return getSpeciesContextSpec().getSiteAttributesMap().get(fieldMolecularComponentPatternTwo); + return getSpeciesContextSpec().getSiteAttributesMap().get(fieldLinkNodeTwo); } public double getX1() { @@ -174,31 +200,24 @@ public boolean contains(int px, int py, int pz) { } - public Pair getLink() { - return new Pair(fieldMolecularComponentPatternOne, fieldMolecularComponentPatternTwo); - } - public static Pair getLink(MolecularInternalLinkSpec internalLink) { - if(internalLink == null) { - return null; - } - return new Pair(internalLink.fieldMolecularComponentPatternOne, internalLink.fieldMolecularComponentPatternTwo); + public Pair getLink() { + return new Pair(fieldLinkNodeOne, fieldLinkNodeTwo); } - public void setLink(Pair link) { + + public void setLink(Pair link) { SpeciesContext sc = fieldSpeciesContextSpec.getSpeciesContext(); SpeciesPattern sp = sc.getSpeciesPattern(); MolecularTypePattern mtp = sp.getMolecularTypePatterns().get(0); // the one and only - for(MolecularComponentPattern mpc : mtp.getComponentPatternList()) { - if(mpc == link.one) { // linkOne comes first - fieldMolecularComponentPatternOne = link.one; - fieldMolecularComponentPatternTwo = link.two; - break; - } else if(mpc == link.two) { // linkTwo comes first - fieldMolecularComponentPatternOne = link.two; - fieldMolecularComponentPatternTwo = link.one; - break; - } + + if(compareOrder(link.one, link.two) <= 0) { + fieldLinkNodeOne = link.one; + fieldLinkNodeTwo = link.two; + } else { + fieldLinkNodeOne = link.two; + fieldLinkNodeTwo = link.one; } } + @Override public boolean compareEqual(Matchable obj) { if (obj == this) { @@ -215,11 +234,10 @@ public boolean compareEqual(Matchable obj) { if(!fieldSpeciesContextSpec.getSpeciesContext().compareEqual(theirMils.getSpeciesContextSpec().getSpeciesContext())) { return false; } - // note that while the link is scalar, not vector, we order one and two by the position of the - // MolecularComponent in the MolecularType definition - // hence, no need to also compare thisOne with that.Two (I hope, at least) - if((fieldMolecularComponentPatternOne.compareEqual(theirMils.fieldMolecularComponentPatternOne)) && - (fieldMolecularComponentPatternTwo.compareEqual(theirMils.fieldMolecularComponentPatternTwo))) { + // note that while the link is scalar, not vector, we order one and two alphabetically + // hence, no need to also compare this.One with that.Two (I hope, at least) + if((fieldLinkNodeOne.compareEqual(theirMils.fieldLinkNodeOne)) && + (fieldLinkNodeTwo.compareEqual(theirMils.fieldLinkNodeTwo))) { return true; } return false; diff --git a/vcell-core/src/main/java/cbit/vcell/mapping/SpeciesContextSpec.java b/vcell-core/src/main/java/cbit/vcell/mapping/SpeciesContextSpec.java index 8a3d40565b..089b2b13e5 100644 --- a/vcell-core/src/main/java/cbit/vcell/mapping/SpeciesContextSpec.java +++ b/vcell-core/src/main/java/cbit/vcell/mapping/SpeciesContextSpec.java @@ -16,16 +16,10 @@ import java.math.MathContext; import java.util.*; -import cbit.vcell.math.MathUtilities; import cbit.vcell.model.*; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.vcell.model.rbm.ComponentStateDefinition; -import org.vcell.model.rbm.MolecularComponent; -import org.vcell.model.rbm.MolecularComponentPattern; -import org.vcell.model.rbm.MolecularType; -import org.vcell.model.rbm.MolecularTypePattern; -import org.vcell.model.rbm.SpeciesPattern; +import org.vcell.model.rbm.*; import org.vcell.util.*; import org.vcell.util.Issue.IssueCategory; import org.vcell.util.Issue.IssueSource; @@ -457,6 +451,7 @@ public void targetPropertyChange(PropertyChangeEvent evt){ // SpringSaLaD specific entities public Set internalLinkSet = new LinkedHashSet<>(); private Map siteAttributesMap = new LinkedHashMap<>(); + private Map structuralSiteAttributesMap = new LinkedHashMap<>(); // is2D flag, used by the solver for collision / overlapping calculations, exact meaning uncertain // membrane species may have it set to true, for compartment species is always false // for now we have it hardcoded to false and non-editable @@ -691,6 +686,7 @@ public void initializeForSpatial(){ } } + // TODO: add sanity checks for StructuralSites public void initializeForSpringSaLaD(MolecularType molecularType) { // we need to make sure that the mcp in the siteAttributesMap: // Map siteAttributesMap = scs.getSiteAttributesMap(); @@ -878,10 +874,13 @@ public void initializeForSpringSaLaD(MolecularType molecularType) { // step 1.1: build the set of "old" mcp and mils from the siteAttributesMap oldMcpSet = new LinkedHashSet<> (); // Set for(MolecularInternalLinkSpec oldMils : getInternalLinkSet()) { - MolecularComponentPattern mcpOne = oldMils.getMolecularComponentPatternOne(); - MolecularComponentPattern mcpTwo = oldMils.getMolecularComponentPatternTwo(); - oldMcpSet.add(mcpOne); - oldMcpSet.add(mcpTwo); + LinkNode lnOne = oldMils.getLinkNodeOne(); + LinkNode lnTwo = oldMils.getLinkNodeTwo(); + if(lnOne instanceof MolecularComponentPattern) { + oldMcpSet.add((MolecularComponentPattern) lnOne); + } if(lnTwo instanceof MolecularComponentPattern) { + oldMcpSet.add((MolecularComponentPattern) lnTwo); + } } // step 1.2: we build set of authoritative MolecularComponentPattern from the SpeciesPattern Set authoritativeMcpSet = new LinkedHashSet<>(); @@ -928,17 +927,24 @@ public void initializeForSpringSaLaD(MolecularType molecularType) { // step 3: we iterate through the internal link set, we replace all mcs with the new mcs in oldToNewMcp // any mcp not found there was deleted, we must delete the link containing it + // TODO: keep checking this code carefuly after introducing StructuralSites, it's easy to mess up the logic Set linksToRemove = new LinkedHashSet<> (); for(MolecularInternalLinkSpec oldMils : getInternalLinkSet()) { - Pair link = oldMils.getLink(); - MolecularComponentPattern mcpOne = oldMils.getMolecularComponentPatternOne(); - MolecularComponentPattern mcpTwo = oldMils.getMolecularComponentPatternTwo(); - if(!oldToNewMcp.containsKey(mcpOne) || !oldToNewMcp.containsKey(mcpTwo)) { + Pair link = oldMils.getLink(); + LinkNode lnOne = oldMils.getLinkNodeOne(); + LinkNode lnTwo = oldMils.getLinkNodeTwo(); + if(lnOne instanceof MolecularComponentPattern mcpOne && !oldToNewMcp.containsKey(mcpOne)) { linksToRemove.add(oldMils); - } else { - Pair newLink = new Pair(oldToNewMcp.get(mcpOne), oldToNewMcp.get(mcpTwo)); - oldMils.setLink(newLink); + continue; } + if(lnTwo instanceof MolecularComponentPattern mcpTwo && !oldToNewMcp.containsKey(mcpTwo)) { + linksToRemove.add(oldMils); + continue; + } + // both lnOne and lnTwo are either StructuralSites or are MolecularComponentPattern with a match in oldToNewMcp, + // then we update the link + Pair newLink = new Pair(oldToNewMcp.get(lnOne), oldToNewMcp.get(lnTwo)); + oldMils.setLink(newLink); } getInternalLinkSet().removeAll(linksToRemove); @@ -951,14 +957,14 @@ public void initializeForSpringSaLaD(MolecularType molecularType) { authoritativeMcpSet2.add(mcp); } // step 4.2: we can't assume how many links we have, even 0 is possible if we deleted them manually - // hoever, any mcp instance present in a link must be valid (present in the authoritativeMcpSet) + // however, any mcp instance present in a link must be valid (present in the authoritativeMcpSet) for(MolecularInternalLinkSpec link : getInternalLinkSet()) { - MolecularComponentPattern mcpOne = link.getMolecularComponentPatternOne(); - MolecularComponentPattern mcpTwo = link.getMolecularComponentPatternTwo(); - if(!authoritativeMcpSet2.contains(mcpOne)) { + LinkNode lnOne = link.getLinkNodeOne(); + LinkNode lnTwo = link.getLinkNodeTwo(); + if(lnOne instanceof MolecularComponentPattern mcpOne && !authoritativeMcpSet2.contains(mcpOne)) { throw new RuntimeException(ilSetExceptionPrefix + "has invalid MolecularComponentPattern instance"); } - if(!authoritativeMcpSet2.contains(mcpTwo)) { + if(lnTwo instanceof MolecularComponentPattern mcpTwo && !authoritativeMcpSet2.contains(mcpTwo)) { throw new RuntimeException(ilSetExceptionPrefix + "has invalid MolecularComponentPattern instance"); } } @@ -1290,7 +1296,7 @@ public void gatherIssues(IssueContext issueContext, List issueVector){ return; } for(MolecularInternalLinkSpec mils : getInternalLinkSet()){ - if(mils.getMolecularComponentPatternOne() == mils.getMolecularComponentPatternTwo()){ + if(mils.getLinkNodeOne() == mils.getLinkNodeTwo()){ String msg = "Both sites of the Link are identical. A site cannot be linked to itself."; String tip = msg; issueVector.add(new Issue(this, issueContext, IssueCategory.Identifiers, msg, tip, Issue.Severity.WARNING)); @@ -1305,7 +1311,7 @@ public void gatherIssues(IssueContext issueContext, List issueVector){ mcpMap.put(mcp, i); } for(MolecularInternalLinkSpec mils : getInternalLinkSet()) { - Pair link = mils.getLink(); + Pair link = mils.getLink(); int one = mcpMap.get(link.one); int two = mcpMap.get(link.two); graph.addEdge(one, two); @@ -1323,8 +1329,8 @@ public void gatherIssues(IssueContext issueContext, List issueVector){ continue; } if(candidate.compareEqual(other)){ - String one = candidate.getMolecularComponentPatternOne().getMolecularComponent().getName(); - String two = candidate.getMolecularComponentPatternTwo().getMolecularComponent().getName(); + String one = candidate.getLinkNodeOne().getName(); + String two = candidate.getLinkNodeTwo().getName(); String msg = "Duplicate link: " + one + " :: " + two; String tip = msg; issueVector.add(new Issue(this, issueContext, IssueCategory.Identifiers, msg, tip, Issue.Severity.WARNING)); @@ -2453,22 +2459,26 @@ public List computeApplicableParameterList(){ return speciesContextSpecParameterList; } + // springsalad-related collections getter / setters public Set getInternalLinkSet(){ return internalLinkSet; } - public void setInternalLinkSet(Set internalLinkSet){ this.internalLinkSet = internalLinkSet; } - - public Map getSiteAttributesMap(){ return siteAttributesMap; } - public void setSiteAttributesMap(Map siteAttributesMap){ this.siteAttributesMap = siteAttributesMap; } + // TODO: call these wherever we call the above get/set methods + public Map getStructuralSiteAttributesMap(){ + return structuralSiteAttributesMap; + } + public void setStructuralSiteAttributesMap(Map structuralSiteAttributesMap){ + this.structuralSiteAttributesMap = structuralSiteAttributesMap; + } public SpatialQuantity[] getVelocityQuantities(QuantityComponent component){ ArrayList velocityQuantities = new ArrayList(); diff --git a/vcell-core/src/main/java/cbit/vcell/mapping/StructuralSite.java b/vcell-core/src/main/java/cbit/vcell/mapping/StructuralSite.java new file mode 100644 index 0000000000..0d3e9b6ac5 --- /dev/null +++ b/vcell-core/src/main/java/cbit/vcell/mapping/StructuralSite.java @@ -0,0 +1,48 @@ +package cbit.vcell.mapping; + +import org.vcell.model.rbm.LinkNode; +import org.vcell.model.rbm.MolecularComponentPattern; +import org.vcell.model.rbm.RbmElementAbstract; +import org.vcell.util.Matchable; + +public class StructuralSite extends RbmElementAbstract implements LinkNode { + + private String name; + + public StructuralSite(String name) { + this.name = name; + } + + @Override + public String getName() { + return null; + } + + + + @Override + public boolean compareEqual(Matchable aThat) { + if (this == aThat) { + return true; + } + if (!(aThat instanceof MolecularComponentPattern)) { + return false; + } + StructuralSite that = (StructuralSite)aThat; + if(!this.name.equals(that.name)) { + return false; + } + return true; + } + + public static final String typeName = "Site"; + @Override + public String getDisplayName() { + return null; + } + @Override + public String getDisplayType() { + return typeName; + } + +} diff --git a/vcell-core/src/main/java/cbit/vcell/xml/Xmlproducer.java b/vcell-core/src/main/java/cbit/vcell/xml/Xmlproducer.java index 13e26fcb2b..bd76f73483 100644 --- a/vcell-core/src/main/java/cbit/vcell/xml/Xmlproducer.java +++ b/vcell-core/src/main/java/cbit/vcell/xml/Xmlproducer.java @@ -1718,8 +1718,8 @@ else if(initAmt != null) Element milsElement = new Element(XMLTags.InternalLinkSpecTag); milsElement.setAttribute(XMLTags.MoleculeRefAttrTag, mt.getName()); - milsElement.setAttribute(XMLTags.SiteOneRefAttrTag, mils.getMolecularComponentPatternOne().getMolecularComponent().getName()); - milsElement.setAttribute(XMLTags.SiteTwoRefAttrTag, mils.getMolecularComponentPatternTwo().getMolecularComponent().getName()); + milsElement.setAttribute(XMLTags.SiteOneRefAttrTag, mils.getLinkNodeOne().getName()); + milsElement.setAttribute(XMLTags.SiteTwoRefAttrTag, mils.getLinkNodeTwo().getName()); speciesContextSpecElement.addContent(milsElement); } } diff --git a/vcell-core/src/main/java/org/vcell/model/rbm/LinkNode.java b/vcell-core/src/main/java/org/vcell/model/rbm/LinkNode.java new file mode 100644 index 0000000000..c55c939475 --- /dev/null +++ b/vcell-core/src/main/java/org/vcell/model/rbm/LinkNode.java @@ -0,0 +1,13 @@ +package org.vcell.model.rbm; + +import org.vcell.util.Displayable; +import org.vcell.util.Matchable; + +// Used to introduce a common endpoint type for SpringSaLaD related entities +// more exactly, connects StructuralSites and MolecularComponentPatterns as linkable entities +public interface LinkNode extends Displayable, Matchable { + + // used only for MolecularComponentPattern as getMolecularComponent().getName() + // and directly as getName() for StructuralSite + public String getName(); +} diff --git a/vcell-core/src/main/java/org/vcell/model/rbm/MolecularComponentPattern.java b/vcell-core/src/main/java/org/vcell/model/rbm/MolecularComponentPattern.java index 48f4036f9b..25717d1967 100644 --- a/vcell-core/src/main/java/org/vcell/model/rbm/MolecularComponentPattern.java +++ b/vcell-core/src/main/java/org/vcell/model/rbm/MolecularComponentPattern.java @@ -20,7 +20,7 @@ @SuppressWarnings("serial") -public class MolecularComponentPattern extends RbmElementAbstract implements Matchable, IssueSource, Displayable +public class MolecularComponentPattern extends RbmElementAbstract implements LinkNode, Matchable, IssueSource, Displayable { public static final String PROPERTY_NAME_COMPONENT_STATE = "componentStatePattern"; public static final String PROPERTY_NAME_BOND_TYPE = "bondType"; @@ -201,6 +201,15 @@ public boolean compareEqual(Matchable aThat) { return true; } + @Override + public String getName() { // implementation of LinkNode interface, we return the name of the molecular component + if(getMolecularComponent() != null) { + return getMolecularComponent().getName(); + } else { + return null; + } + } + @Override public void gatherIssues(IssueContext issueContext, List issueList) { if(molecularComponent == null) { From 25256249da822c95cb25b529f8febead2bee32eb Mon Sep 17 00:00:00 2001 From: Dan Vasilescu Date: Thu, 23 Apr 2026 19:14:51 -0400 Subject: [PATCH 02/16] Generalized use of LinkNode, creation, deletion, visualization, edit --- .../mapping/gui/LinkSpecsTableModel.java | 6 +- .../MolecularStructuresPropertiesPanel.java | 31 ++- .../gui/MolecularTypeSpecsTableModel.java | 78 ++++--- .../model/springsalad/gui/AddLinkPanel.java | 55 ++--- .../gui/MolecularStructuresPanel.java | 219 ++++++++++++------ .../graph/SpeciesContextSpecLargeShape.java | 84 +++---- .../mapping/MolecularInternalLinkSpec.java | 7 +- .../cbit/vcell/mapping/ReactionRuleSpec.java | 1 + .../vcell/mapping/SiteAttributesSpec.java | 84 ++++--- .../vcell/mapping/SpeciesContextSpec.java | 35 ++- .../cbit/vcell/mapping/StructuralSite.java | 4 +- .../java/cbit/vcell/solver/Simulation.java | 10 +- .../java/org/vcell/model/ssld/SsldUtils.java | 12 + .../modeldb/SpeciesContextSpecTable.java | 7 +- .../main/java/org/vcell/util/ColorUtil.java | 24 ++ 15 files changed, 405 insertions(+), 252 deletions(-) diff --git a/vcell-client/src/main/java/cbit/vcell/mapping/gui/LinkSpecsTableModel.java b/vcell-client/src/main/java/cbit/vcell/mapping/gui/LinkSpecsTableModel.java index b108fef6ce..a51df12f72 100644 --- a/vcell-client/src/main/java/cbit/vcell/mapping/gui/LinkSpecsTableModel.java +++ b/vcell-client/src/main/java/cbit/vcell/mapping/gui/LinkSpecsTableModel.java @@ -267,9 +267,9 @@ public int compare(MolecularInternalLinkSpec mils1, MolecularInternalLinkSpec mi } LinkNode node1 = mils1.getLink().one; LinkNode node2 = mils2.getLink().one; - Map siteAttributesMap = getSpeciesContextSpec().getSiteAttributesMap(); - SiteAttributesSpec sas1 = siteAttributesMap.get(node1); - SiteAttributesSpec sas2 = siteAttributesMap.get(node2); + Map merged = getSpeciesContextSpec().getAllSiteAttributes(); + SiteAttributesSpec sas1 = merged.get(node1); + SiteAttributesSpec sas2 = merged.get(node2); Double z1 = sas1.getCoordinate().getZ(); Double z2 = sas2.getCoordinate().getZ(); return z1.compareTo(z2); diff --git a/vcell-client/src/main/java/cbit/vcell/mapping/gui/MolecularStructuresPropertiesPanel.java b/vcell-client/src/main/java/cbit/vcell/mapping/gui/MolecularStructuresPropertiesPanel.java index cf515fb823..020732c686 100644 --- a/vcell-client/src/main/java/cbit/vcell/mapping/gui/MolecularStructuresPropertiesPanel.java +++ b/vcell-client/src/main/java/cbit/vcell/mapping/gui/MolecularStructuresPropertiesPanel.java @@ -2,12 +2,9 @@ import cbit.vcell.biomodel.BioModel; import cbit.vcell.client.desktop.biomodel.DocumentEditorSubPanel; -import cbit.vcell.graph.PointLocationInShapeContext; import cbit.vcell.graph.ReactionCartoon; import cbit.vcell.graph.SpeciesContextSpecLargeShape; -import cbit.vcell.graph.SpeciesPatternLargeShape; import cbit.vcell.graph.gui.LargeShapePanel; -import cbit.vcell.graph.gui.SsldLargeShapePanel; import cbit.vcell.graph.gui.ZoomShapeIcon; import cbit.vcell.mapping.MolecularInternalLinkSpec; import cbit.vcell.mapping.SiteAttributesSpec; @@ -15,11 +12,10 @@ import cbit.vcell.model.GroupingCriteria; import cbit.vcell.model.Model; import cbit.vcell.model.RuleParticipantSignature; -import cbit.vcell.model.SpeciesContext; import cbit.vcell.parser.ExpressionException; +import org.vcell.model.rbm.LinkNode; import org.vcell.model.rbm.MolecularComponentPattern; import org.vcell.model.rbm.MolecularTypePattern; -import org.vcell.model.rbm.SpeciesPattern; import javax.swing.*; import java.awt.*; @@ -27,7 +23,6 @@ import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionAdapter; -import java.awt.geom.Ellipse2D; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; @@ -44,7 +39,7 @@ public class MolecularStructuresPropertiesPanel extends DocumentEditorSubPanel { private LargeShapePanel shapePanel = null; private SpeciesContextSpecLargeShape scsls; // the real thing - private MolecularComponentPattern mcpSelected = null; + private LinkNode lnSelected = null; private MolecularInternalLinkSpec milsSelected = null; private Object lastSelectedObject = null; // may be either MolecularComponentPattern or MolecularInternalLinkSpec @@ -80,10 +75,10 @@ public void propertyChange(PropertyChangeEvent evt) { } else if(evt.getSource() instanceof SpeciesContextSpec && evt.getPropertyName().equals(SpeciesContextSpec.PROPERTY_NAME_SITE_SELECTED_IN_TABLE)) { Object o = evt.getNewValue(); if(o instanceof MolecularComponentPattern mcp) { - mcpSelected = mcp; - lastSelectedObject = mcpSelected; + lnSelected = mcp; + lastSelectedObject = lnSelected; } else { - mcpSelected = null; + lnSelected = null; if(milsSelected != null) { // if we had a link selected before, it becomes the last (and only) selected lastSelectedObject = milsSelected; @@ -100,9 +95,9 @@ public void propertyChange(PropertyChangeEvent evt) { lastSelectedObject = milsSelected; } else { milsSelected = null; - if(mcpSelected != null) { + if(lnSelected != null) { // if we had a site selected before, it becomes the last (and only) selected - lastSelectedObject = mcpSelected; + lastSelectedObject = lnSelected; } else { // nothing is selected anymore lastSelectedObject = null; @@ -138,7 +133,7 @@ public void paintComponent(Graphics g) { return; } scsls = new SpeciesContextSpecLargeShape(speciesContextSpec, shapePanel, speciesContextSpec, - mcpSelected, milsSelected, lastSelectedObject, issueManager); + lnSelected, milsSelected, lastSelectedObject, issueManager); scsls.paintSelf(g); } @Override @@ -211,12 +206,12 @@ public void mouseClicked(MouseEvent e) { // we tell the table in the upper panel to update the selected row speciesContextSpec.firePropertyChange(PROPERTY_NAME_LINK_SELECTED_IN_SHAPE, null, milsSelected); } else if(overObject instanceof SiteAttributesSpec sas) { - mcpSelected = sas.getMolecularComponentPattern(); - lastSelectedObject = mcpSelected; + lnSelected = sas.getLinkNode(); + lastSelectedObject = lnSelected; // redraw the whole shape to highlight the selected link line updateShape(); // we tell the table in the upper panel to update the selected row - speciesContextSpec.firePropertyChange(PROPERTY_NAME_SITE_SELECTED_IN_SHAPE, null, mcpSelected); + speciesContextSpec.firePropertyChange(PROPERTY_NAME_SITE_SELECTED_IN_SHAPE, null, lnSelected); } } }); @@ -301,7 +296,7 @@ private void updateShape() { return; } scsls = new SpeciesContextSpecLargeShape(speciesContextSpec, shapePanel, speciesContextSpec, - mcpSelected, milsSelected, lastSelectedObject, issueManager); + lnSelected, milsSelected, lastSelectedObject, issueManager); // shapePanel.setPreferredSize(scsls.getMaxSize()); @@ -321,7 +316,7 @@ public void setSpeciesContextSpec(SpeciesContextSpec scSpec) { } // getSpeciesContextSpecParameterTableModel().setSpeciesContextSpec(scSpec); - mcpSelected = null; + lnSelected = null; milsSelected = null; updateInterface(); } diff --git a/vcell-client/src/main/java/cbit/vcell/mapping/gui/MolecularTypeSpecsTableModel.java b/vcell-client/src/main/java/cbit/vcell/mapping/gui/MolecularTypeSpecsTableModel.java index 69adcf4f2b..13b88544b6 100644 --- a/vcell-client/src/main/java/cbit/vcell/mapping/gui/MolecularTypeSpecsTableModel.java +++ b/vcell-client/src/main/java/cbit/vcell/mapping/gui/MolecularTypeSpecsTableModel.java @@ -22,12 +22,8 @@ import javax.swing.border.LineBorder; import javax.swing.table.TableCellRenderer; -import org.vcell.model.rbm.ComponentStatePattern; -import org.vcell.model.rbm.MolecularComponent; -import org.vcell.model.rbm.MolecularComponentPattern; -import org.vcell.model.rbm.MolecularType; -import org.vcell.model.rbm.MolecularTypePattern; -import org.vcell.model.rbm.SpeciesPattern; +import cbit.vcell.mapping.*; +import org.vcell.model.rbm.*; import org.vcell.util.Coordinate; import org.vcell.util.gui.ColorIcon; import org.vcell.util.gui.DialogUtils; @@ -35,11 +31,6 @@ import org.vcell.util.gui.ScrollTable; import cbit.vcell.client.desktop.biomodel.VCellSortTableModel; -import cbit.vcell.mapping.GeometryContext; -import cbit.vcell.mapping.ReactionContext; -import cbit.vcell.mapping.SimulationContext; -import cbit.vcell.mapping.SiteAttributesSpec; -import cbit.vcell.mapping.SpeciesContextSpec; import cbit.vcell.model.Parameter; import cbit.vcell.model.SpeciesContext; import cbit.vcell.model.Structure; @@ -53,7 +44,7 @@ * @author: */ @SuppressWarnings("serial") -public class MolecularTypeSpecsTableModel extends VCellSortTableModel implements java.beans.PropertyChangeListener { +public class MolecularTypeSpecsTableModel extends VCellSortTableModel implements java.beans.PropertyChangeListener { // TODO: the is2D flag here (a checkbox, var is SpeciesContextSpec) - membrane species may have it set to true, for compartment species is always false @@ -89,7 +80,7 @@ public Class getColumnClass(int column) { ColumnType columnType = columns.get(column); switch (columnType) { case COLUMN_SITE: - return MolecularComponentPattern.class; + return LinkNode.class; // case COLUMN_MOLECULE: // return MolecularType.class; case COLUMN_STRUCTURE: @@ -123,16 +114,21 @@ public Object getValueAt(int row, int col) { if(getSpeciesContextSpec() == null) { return null; } - Map siteAttributesMap = getSpeciesContextSpec().getSiteAttributesMap(); - MolecularComponentPattern mcp = getValueAt(row); - SiteAttributesSpec sas = siteAttributesMap.get(mcp); + LinkNode ln = getValueAt(row); + SpeciesContextSpec scs = getSpeciesContextSpec(); + SiteAttributesSpec sas = null; + if(ln instanceof MolecularComponentPattern) { + sas = scs.getSiteAttributesMap().get(ln); + } else if(ln instanceof StructuralSite) { + sas = scs.getStructuralSiteAttributesMap().get(ln); + } SpeciesContext sc = fieldSpeciesContextSpec.getSpeciesContext(); SpeciesPattern sp = sc.getSpeciesPattern(); MolecularTypePattern mtp = sp.getMolecularTypePatterns().get(0); ColumnType columnType = columns.get(col); switch (columnType) { case COLUMN_SITE: - return mcp.getMolecularComponent().getName(); + return ln.getName(); // case COLUMN_MOLECULE: // return mtp.getMolecularType().getName(); case COLUMN_STRUCTURE: @@ -190,22 +186,27 @@ public Object getValueAt(int row, int col) { } public void setValueAt(Object aValue, int row, int col) { - MolecularComponentPattern mcp = getValueAt(row); + LinkNode ln = getValueAt(row); ColumnType columnType = columns.get(col); SpeciesContextSpec scs = getSpeciesContextSpec(); if(scs == null) { return; } - SiteAttributesSpec sas = scs.getSiteAttributesMap().get(mcp); + SiteAttributesSpec sas = null; + if(ln instanceof MolecularComponentPattern) { + sas = scs.getSiteAttributesMap().get(ln); + } else if(ln instanceof StructuralSite) { + sas = scs.getStructuralSiteAttributesMap().get(ln); + } switch (columnType) { case COLUMN_SITE: // case COLUMN_MOLECULE: // case COLUMN_STATE: -// return; + return; case COLUMN_STRUCTURE: if(aValue instanceof Structure structure) { if(sas == null) { - sas = new SiteAttributesSpec(scs, mcp, structure); + sas = new SiteAttributesSpec(scs, ln, structure); } else { sas.setLocation(structure); } @@ -215,7 +216,7 @@ public void setValueAt(Object aValue, int row, int col) { case COLUMN_X: if (aValue instanceof String newExpressionString) { if(sas == null) { - sas = new SiteAttributesSpec(scs, mcp, scs.getSpeciesContext().getStructure()); + sas = new SiteAttributesSpec(scs, ln, scs.getSpeciesContext().getStructure()); } double res = 0.0; try { @@ -236,7 +237,7 @@ public void setValueAt(Object aValue, int row, int col) { if (aValue instanceof String newExpressionString) { if(sas == null) { // TODO: is this necessary? - sas = new SiteAttributesSpec(scs, mcp, scs.getSpeciesContext().getStructure()); + sas = new SiteAttributesSpec(scs, ln, scs.getSpeciesContext().getStructure()); } double res = 0.0; try { @@ -256,7 +257,7 @@ public void setValueAt(Object aValue, int row, int col) { case COLUMN_Z: if (aValue instanceof String newExpressionString) { if(sas == null) { - sas = new SiteAttributesSpec(scs, mcp, scs.getSpeciesContext().getStructure()); + sas = new SiteAttributesSpec(scs, ln, scs.getSpeciesContext().getStructure()); } double res = 0.0; try { @@ -299,7 +300,7 @@ public void setValueAt(Object aValue, int row, int col) { return; } if(sas == null) { - sas = new SiteAttributesSpec(scs, mcp, scs.getSpeciesContext().getStructure()); + sas = new SiteAttributesSpec(scs, ln, scs.getSpeciesContext().getStructure()); } sas.setRadius(res); scs.firePropertyChange(SpeciesContextSpec.PROPERTY_NAME_SITE_ATTRIBUTE, null, sas); @@ -319,7 +320,7 @@ public void setValueAt(Object aValue, int row, int col) { return; } if(sas == null) { - sas = new SiteAttributesSpec(scs, mcp, scs.getSpeciesContext().getStructure()); + sas = new SiteAttributesSpec(scs, ln, scs.getSpeciesContext().getStructure()); } sas.setDiffusionRate(res); scs.firePropertyChange(SpeciesContextSpec.PROPERTY_NAME_SITE_ATTRIBUTE, null, sas); @@ -328,7 +329,7 @@ public void setValueAt(Object aValue, int row, int col) { case COLUMN_COLOR: if (aValue instanceof NamedColor namedColor) { if(sas == null) { - sas = new SiteAttributesSpec(scs, mcp, scs.getSpeciesContext().getStructure()); + sas = new SiteAttributesSpec(scs, ln, scs.getSpeciesContext().getStructure()); } sas.setColor(namedColor); scs.firePropertyChange(SpeciesContextSpec.PROPERTY_NAME_SITE_ATTRIBUTE, null, sas); @@ -353,7 +354,7 @@ public boolean isCellEditable(int row, int col) { case COLUMN_SITE: // case COLUMN_MOLECULE: // case COLUMN_STATE: -// return false; + return false; case COLUMN_STRUCTURE: case COLUMN_X: case COLUMN_Y: @@ -368,14 +369,14 @@ public boolean isCellEditable(int row, int col) { } @Override - public Comparator getComparator(final int col, final boolean ascending) { - return new Comparator() { + public Comparator getComparator(final int col, final boolean ascending) { + return new Comparator() { /** * Compares its two arguments for order. Returns a negative integer, * zero, or a positive integer as the first argument is less than, equal * to, or greater than the second.

*/ - public int compare(MolecularComponentPattern mcp1, MolecularComponentPattern mcp2){ + public int compare(LinkNode mcp1, LinkNode mcp2){ ColumnType columnType = columns.get(col); switch (columnType) { @@ -513,9 +514,9 @@ private void updateListenersReactionContext(ReactionContext reactionContext,bool // } // } - private void refreshData() { - List molecularComponentPatternList = computeData(); - setData(molecularComponentPatternList); + public void refreshData() { + List linkNodeList = computeData(); + setData(linkNodeList); GuiUtils.flexResizeTableColumns(ownerTable); updateLocationComboBox(); @@ -576,8 +577,8 @@ public Component getListCellRendererComponent(JList list, Object value, ownerTable.getColumnModel().getColumn(ColumnType.COLUMN_STRUCTURE.ordinal()).setCellEditor(new DefaultCellEditor(locationComboBoxCellEditor)); } - protected List computeData() { - ArrayList allParameterList = new ArrayList(); + protected List computeData() { + ArrayList allParameterList = new ArrayList(); if(fieldSpeciesContextSpec != null && fieldSpeciesContextSpec.getSpeciesContext() != null) { SpeciesPattern sp = fieldSpeciesContextSpec.getSpeciesContext().getSpeciesPattern(); if(sp == null) { @@ -590,6 +591,11 @@ protected List computeData() { MolecularComponentPattern mcp = mtp.getMolecularComponentPattern(mc); allParameterList.add(mcp); } + for (Map.Entry entry : getSpeciesContextSpec().getStructuralSiteAttributesMap().entrySet()) { + StructuralSite key = entry.getKey(); + SiteAttributesSpec value = entry.getValue(); + allParameterList.add(key); + } } else { return null; } diff --git a/vcell-client/src/main/java/org/vcell/model/springsalad/gui/AddLinkPanel.java b/vcell-client/src/main/java/org/vcell/model/springsalad/gui/AddLinkPanel.java index ffce0fe304..0529d2de3f 100644 --- a/vcell-client/src/main/java/org/vcell/model/springsalad/gui/AddLinkPanel.java +++ b/vcell-client/src/main/java/org/vcell/model/springsalad/gui/AddLinkPanel.java @@ -19,6 +19,8 @@ import java.awt.event.ActionListener; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; +import java.util.LinkedHashMap; +import java.util.Map; import javax.swing.DefaultListCellRenderer; import javax.swing.DefaultListModel; @@ -38,11 +40,9 @@ import javax.swing.event.TableModelEvent; import javax.swing.event.TableModelListener; -import org.vcell.model.rbm.MolecularComponent; -import org.vcell.model.rbm.MolecularComponentPattern; -import org.vcell.model.rbm.MolecularTypePattern; -import org.vcell.model.rbm.NetworkConstraints; -import org.vcell.model.rbm.SpeciesPattern; +import cbit.vcell.mapping.SiteAttributesSpec; +import cbit.vcell.mapping.StructuralSite; +import org.vcell.model.rbm.*; import org.vcell.util.gui.DialogUtils; import org.vcell.util.gui.EditorScrollTable; @@ -70,28 +70,28 @@ enum ActionButtons { private final MolecularStructuresPanel owner; private ChildWindow parentChildWindow; - private JList firstSiteList = null; - private DefaultListModel firstSiteListModel = new DefaultListModel<>(); + private JList firstSiteList = null; + private DefaultListModel firstSiteListModel = new DefaultListModel<>(); private ListCellRenderer firstSiteCellRenderer = new DefaultListCellRenderer(){ @Override public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { Component component = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); - if (value instanceof MolecularComponentPattern && component instanceof JLabel) { - MolecularComponentPattern mcp = (MolecularComponentPattern)value; - ((JLabel)component).setText(mcp.getMolecularComponent().getName()); + if (value instanceof LinkNode && component instanceof JLabel) { + LinkNode mcp = (LinkNode)value; + ((JLabel)component).setText(mcp.getName()); } return component; } }; - private JList secondSiteList = null; - private DefaultListModel secondSiteListModel = new DefaultListModel<>(); + private JList secondSiteList = null; + private DefaultListModel secondSiteListModel = new DefaultListModel<>(); private ListCellRenderer secondSiteCellRenderer = new DefaultListCellRenderer() { @Override public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { Component component = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); - if (value instanceof MolecularComponentPattern && component instanceof JLabel){ - MolecularComponentPattern mcp = (MolecularComponentPattern)value; - ((JLabel)component).setText(mcp.getMolecularComponent().getName()); + if (value instanceof LinkNode && component instanceof JLabel){ + LinkNode mcp = (LinkNode)value; + ((JLabel)component).setText(mcp.getName()); } return component; } @@ -117,8 +117,8 @@ public void focusLost(FocusEvent e) { @Override public void valueChanged(ListSelectionEvent e) { if(e.getSource() == firstSiteList || e.getSource() == secondSiteList) { - MolecularComponentPattern firstMcp = firstSiteList.getSelectedValue(); - MolecularComponentPattern secondMcp = secondSiteList.getSelectedValue(); + LinkNode firstMcp = firstSiteList.getSelectedValue(); + LinkNode secondMcp = secondSiteList.getSelectedValue(); if(firstMcp != null && secondMcp != null) { getApplyButton().setEnabled(true); } else { @@ -158,19 +158,24 @@ private void initialize() { return; } - firstSiteList = new JList(firstSiteListModel); + firstSiteList = new JList(firstSiteListModel); firstSiteList.setCellRenderer(firstSiteCellRenderer); - secondSiteList = new JList(secondSiteListModel); + secondSiteList = new JList(secondSiteListModel); secondSiteList.setCellRenderer(secondSiteCellRenderer); firstSiteList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); secondSiteList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - for(MolecularComponentPattern mcp : mtp.getComponentPatternList()) { + for(MolecularComponentPattern mcp : mtp.getComponentPatternList()) { // use the physiology to populate, that's authoritative firstSiteListModel.addElement(mcp); secondSiteListModel.addElement(mcp); } - - + for (Map.Entry entry : scs.getStructuralSiteAttributesMap().entrySet()) { + StructuralSite ss = entry.getKey(); + SiteAttributesSpec sas = entry.getValue(); + firstSiteListModel.addElement(ss); + secondSiteListModel.addElement(ss); + } + JPanel p = new JPanel(); p.setLayout(new GridBagLayout()); @@ -286,10 +291,8 @@ public void actionPerformed(ActionEvent e) { return cancelButton; } -public JList getFirstSiteList() { - return firstSiteList; -} -public JList getSecondSiteList() { +public JList getFirstSiteList() { return firstSiteList; } +public JList getSecondSiteList() { return secondSiteList; } diff --git a/vcell-client/src/main/java/org/vcell/model/springsalad/gui/MolecularStructuresPanel.java b/vcell-client/src/main/java/org/vcell/model/springsalad/gui/MolecularStructuresPanel.java index 3d0e8938ad..f131e87429 100644 --- a/vcell-client/src/main/java/org/vcell/model/springsalad/gui/MolecularStructuresPanel.java +++ b/vcell-client/src/main/java/org/vcell/model/springsalad/gui/MolecularStructuresPanel.java @@ -34,6 +34,7 @@ import org.vcell.util.gui.ScrollTable.ScrollTableBooleanCellRenderer; import org.vcell.util.gui.sorttable.SortTableModel; import org.vcell.util.springsalad.NamedColor; +import org.vcell.util.ColorUtil; import javax.swing.*; import javax.swing.border.Border; @@ -50,7 +51,10 @@ import java.beans.PropertyChangeListener; import java.text.DecimalFormat; import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; @SuppressWarnings("serial") @@ -59,7 +63,7 @@ public class MolecularStructuresPanel extends DocumentEditorSubPanel implements private EventHandler eventHandler = new EventHandler(); private SimulationContext fieldSimulationContext; private SpeciesContextSpec fieldSpeciesContextSpec; - private MolecularComponentPattern fieldMolecularComponentPattern; + private LinkNode fieldLinkNode; private MolecularInternalLinkSpec fieldMolecularInternalLinkSpec; private EditorScrollTable speciesContextSpecsTable = null; @@ -72,6 +76,8 @@ public class MolecularStructuresPanel extends DocumentEditorSubPanel implements private EditorScrollTable linkSpecsTable = null; private LinkSpecsTableModel linkSpecsTableModel = null; + private JButton addStructuralSiteButton = null; + private JButton deleteStructuralSiteButton = null; private JButton addLinkButton = null; private JButton deleteLinkButton = null; // @@ -120,12 +126,14 @@ private class EventHandler implements FocusListener, ActionListener, PropertyCha public void actionPerformed(ActionEvent e) { Object source = e.getSource(); - if(source == addLinkButton) { - addLinkActionPerformed(); - refreshSiteLinksList(); + if(source == addStructuralSiteButton) { // molecularTypeSpecsTableModel.refreshData() called within + addStructuralSiteActionPerformed(); + } else if(source == deleteStructuralSiteButton) { + deleteStructuralSiteActionPerformed(); + } else if(source == addLinkButton) { + addLinkActionPerformed(); // linkSpecsTableModel.refreshData() called within } else if(source == deleteLinkButton) { deleteLinkActionPerformed(); - refreshSiteLinksList(); } } public void focusGained(FocusEvent e) { @@ -141,9 +149,9 @@ public void propertyChange(java.beans.PropertyChangeEvent e) { // notified that the user clicked in a site oval shape or link line shape // we need to update the selected row in the corresponding table for(int row=0 ; row < molecularTypeSpecsTableModel.getRowCount(); row++) { - MolecularComponentPattern mcp = molecularTypeSpecsTableModel.getValueAt(row); - if(e.getNewValue() instanceof MolecularComponentPattern mcpSelected) { - if(mcpSelected == mcp) { + LinkNode ln = molecularTypeSpecsTableModel.getValueAt(row); + if(e.getNewValue() instanceof LinkNode lnSelected) { + if(lnSelected == ln) { // select the table row getMolecularTypeSpecsTable().setRowSelectionInterval(row, row); // bring the row into view if it's not visible @@ -195,10 +203,15 @@ public void valueChanged(javax.swing.event.ListSelectionEvent e) { } else if (e.getSource() == getMolecularTypeSpecsTable().getSelectionModel()) { System.out.println("valueChanged: molecularTypeSpecsTableModel"); int row = getMolecularTypeSpecsTable().getSelectedRow(); - MolecularComponentPattern mcmSelected = molecularTypeSpecsTableModel.getValueAt(row); - setMolecularComponentPattern(mcmSelected); + LinkNode lnSelected = molecularTypeSpecsTableModel.getValueAt(row); + setSelectedLinkNode(lnSelected); + if(lnSelected instanceof StructuralSite) { // we can only delete StructuralSites from here + deleteStructuralSiteButton.setEnabled(true); + } else { + deleteStructuralSiteButton.setEnabled(false); + } - } else if(e.getSource() == getLinkSpecsTable().getSelectionModel()) { // links table + } else if(e.getSource() == getLinkSpecsTable().getSelectionModel()) { // selection in the links table System.out.println("valueChanged: linkSpecsTableModel"); int row = getLinkSpecsTable().getSelectedRow(); MolecularInternalLinkSpec milsSelected = linkSpecsTableModel.getValueAt(row); @@ -223,6 +236,9 @@ public void setSearchText(String s) { } private void initConnections() throws java.lang.Exception { // listeners here! + + addStructuralSiteButton.addActionListener(eventHandler); + deleteStructuralSiteButton.addActionListener(eventHandler); addLinkButton.addActionListener(eventHandler); deleteLinkButton.addActionListener(eventHandler); @@ -248,9 +264,13 @@ private void initConnections() throws java.lang.Exception { // listeners here! private void initialize() { try { + addStructuralSiteButton = new JButton("Add Structural Site"); + deleteStructuralSiteButton = new JButton("Delete Structural Site"); addLinkButton = new JButton("Add Link"); deleteLinkButton = new JButton("Delete Link"); + addStructuralSiteButton.setEnabled(false); + deleteStructuralSiteButton.setEnabled(false); addLinkButton.setEnabled(true); deleteLinkButton.setEnabled(false); @@ -434,7 +454,6 @@ public Component getTableCellRendererComponent(JTable table, Object value, boole return this; } }; - getSpeciesContextSpecsTable().setDefaultRenderer(SpeciesContext.class, renderer); getSpeciesContextSpecsTable().setDefaultRenderer(Structure.class, renderer); getSpeciesContextSpecsTable().setDefaultRenderer(SpeciesPattern.class, rbmSpeciesShapeDepictionCellRenderer); // depiction icons @@ -473,13 +492,28 @@ public Component getTableCellRendererComponent(JTable table, Object value, boole gbc.weightx = 1.0; gbc.weighty = 1.0; gbc.gridwidth = 8; - gbc.gridheight = 5; +// gbc.gridheight = 5; gbc.fill = GridBagConstraints.BOTH; gbc.anchor = GridBagConstraints.NORTHWEST; gbc.insets = new Insets(2, 3, 3, 4); sitesPanel.add(pb, gbc); // MolecularTypeSpecsTable - // The NamedColor combobox cell renderer in the MolecularTypeSpecsTable + gbc = new GridBagConstraints(); + gbc.gridx = 0; + gbc.gridy = 1; +// gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.insets = new Insets(5, 2, 2, 3); + sitesPanel.add(addStructuralSiteButton, gbc); + + gbc = new GridBagConstraints(); + gbc.gridx = 1; + gbc.gridy = 1; +// gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.insets = new Insets(5, 2, 2, 3); + sitesPanel.add(deleteStructuralSiteButton, gbc); + + + // The NamedColor combobox cell renderer in the MolecularTypeSpecsTable DefaultScrollTableCellRenderer namedColorTableCellRenderer = new DefaultScrollTableCellRenderer() { final Color lightBlueBackground = new Color(214, 234, 248); @Override @@ -620,39 +654,39 @@ public Component getTableCellRendererComponent(JTable table, Object value, boole gbc.insets = new Insets(5, 2, 2, 3); linksPanel.add(linksScrollPane, gbc); - DefaultScrollTableCellRenderer linkSpecsTableCellRenderer = new DefaultScrollTableCellRenderer() { - final Color lightBlueBackground = new Color(214, 234, 248); - @Override - public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, + DefaultScrollTableCellRenderer linkSpecsTableCellRenderer = new DefaultScrollTableCellRenderer() { + final Color lightBlueBackground = new Color(214, 234, 248); + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { - super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); - if (table.getModel() instanceof LinkSpecsTableModel) { - if (value instanceof MolecularInternalLinkSpec) { - MolecularInternalLinkSpec mils = (MolecularInternalLinkSpec)value; - LinkNode firstNode = mils.getLinkNodeOne(); - LinkNode secondNode = mils.getLinkNodeTwo(); - setText(firstNode.getName() + " :: " + secondNode.getName()); - SpeciesContextSpec scs = mils.getSpeciesContextSpec(); - if(fieldSpeciesContextSpec != scs) { - throw new RuntimeException("SpeciesContextSpec inconsistent."); - } - Map siteAttributesMap = getSpeciesContextSpec().getSiteAttributesMap(); - SiteAttributesSpec sasFirst = siteAttributesMap.get(firstNode); - SiteAttributesSpec sasSecond = siteAttributesMap.get(secondNode); - NamedColor ncFirst = sasFirst.getColor(); - NamedColor ncSecond = sasSecond.getColor(); - Icon iconFirst = new ColorIcon(10,10,ncFirst.getColor(), true); - Icon iconSecond = new ColorIcon(10,10,ncSecond.getColor(), true); - Icon compositeIcon = new CompositeIcon(iconFirst, iconSecond); -// setHorizontalTextPosition(SwingConstants.RIGHT); - setIcon(compositeIcon); + super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + if (table.getModel() instanceof LinkSpecsTableModel) { + if (value instanceof MolecularInternalLinkSpec) { + MolecularInternalLinkSpec mils = (MolecularInternalLinkSpec)value; + LinkNode firstNode = mils.getLinkNodeOne(); + LinkNode secondNode = mils.getLinkNodeTwo(); + setText(firstNode.getName() + " :: " + secondNode.getName()); + SpeciesContextSpec scs = mils.getSpeciesContextSpec(); + if(fieldSpeciesContextSpec != scs) { + throw new RuntimeException("SpeciesContextSpec inconsistent."); } + Map merged = getSpeciesContextSpec().getAllSiteAttributes(); + SiteAttributesSpec sasFirst = merged.get(firstNode); + SiteAttributesSpec sasSecond = merged.get(secondNode); + NamedColor ncFirst = sasFirst.getColor(); + NamedColor ncSecond = sasSecond.getColor(); + Icon iconFirst = new ColorIcon(10,10,ncFirst.getColor(), true); + Icon iconSecond = new ColorIcon(10,10,ncSecond.getColor(), true); + Icon compositeIcon = new CompositeIcon(iconFirst, iconSecond); +// setHorizontalTextPosition(SwingConstants.RIGHT); + setIcon(compositeIcon); } - return this; } - }; - getLinkSpecsTable().setDefaultRenderer(MolecularInternalLinkSpec.class, linkSpecsTableCellRenderer); // MolecularInternalLinkSpec field cell renderer - getLinkSpecsTable().setDefaultRenderer(Expression.class, expressionTableCellRenderer); // Expression field cell renderer + return this; + } + }; + getLinkSpecsTable().setDefaultRenderer(MolecularInternalLinkSpec.class, linkSpecsTableCellRenderer); // MolecularInternalLinkSpec field cell renderer + getLinkSpecsTable().setDefaultRenderer(Expression.class, expressionTableCellRenderer); // Expression field cell renderer gbc = new GridBagConstraints(); gbc.gridx = 0; @@ -773,10 +807,10 @@ void setSpeciesContextSpec(SpeciesContextSpec newValue) { public SpeciesContextSpec getSpeciesContextSpec() { return fieldSpeciesContextSpec; } - void setMolecularComponentPattern(MolecularComponentPattern mcp) { - fieldMolecularComponentPattern = mcp; + void setSelectedLinkNode(LinkNode ln) { + fieldLinkNode = ln; // this is the selected link node if(fieldSpeciesContextSpec != null) { // mcp may be null - fieldSpeciesContextSpec.firePropertyChange(SpeciesContextSpec.PROPERTY_NAME_SITE_SELECTED_IN_TABLE, null, mcp); + fieldSpeciesContextSpec.firePropertyChange(SpeciesContextSpec.PROPERTY_NAME_SITE_SELECTED_IN_TABLE, null, ln); } updateInterface(); } @@ -810,6 +844,11 @@ private void updateInterface() { } else { addLinkButton.setEnabled(false); } + if(bNonNullMolecularTypePattern) { + addStructuralSiteButton.setEnabled(true); + } else { + addStructuralSiteButton.setEnabled(false); + } if(linkSpecsTableModel.getRowCount() > 0 && fieldMolecularInternalLinkSpec != null) { // there are links we can delete deleteLinkButton.setEnabled(true); } else { @@ -817,13 +856,6 @@ private void updateInterface() { } } - /* - * Here we refresh the links table when we add or delete a link - */ - private void refreshSiteLinksList() { - linkSpecsTableModel.refreshData(); - } - @Override protected void onSelectedObjectsChange(Object[] selectedObjects) { setTableSelections(selectedObjects, speciesContextSpecsTable, speciesContextSpecsTableModel); @@ -834,7 +866,7 @@ private void changeSpeciesContextSpec() { } private void changePosition(JTextField source) { - SiteAttributesSpec sas = fieldSpeciesContextSpec.getSiteAttributesMap().get(fieldMolecularComponentPattern); + SiteAttributesSpec sas = fieldSpeciesContextSpec.getSiteAttributesMap().get(fieldLinkNode); String text = source.getText(); if(sas == null || text == null) { return; @@ -847,7 +879,70 @@ private void changePosition(JTextField source) { return; } } - + private static final String StructuralSiteNamePrefix = "s"; + private int nextStructuralSiteIndex() { + Set existingNames = getSpeciesContextSpec() + .getStructuralSiteAttributesMap() + .keySet() + .stream() + .map(StructuralSite::getName) + .collect(Collectors.toSet()); + int i = 1; + while (existingNames.contains(StructuralSiteNamePrefix + i)) { + i++; + } + return i; + } + private void addStructuralSiteActionPerformed() { + int selectedRowBefore = molecularTypeSpecsTable.getSelectedRow(); // capture whether anything is selected BEFORE adding + // generate better site name here + int componentCount = fieldSpeciesContextSpec.getSiteAttributesMap().size(); + int structuralSiteCount = fieldSpeciesContextSpec.getStructuralSiteAttributesMap().size() + 1; + int structuralSiteIndex = nextStructuralSiteIndex(); + StructuralSite newSite = new StructuralSite(StructuralSiteNamePrefix + structuralSiteIndex); + Structure struct = getSpeciesContextSpec().getSpeciesContext().getStructure(); + if(Structure.SpringStructureEnum.Membrane.columnName.equals((struct.getName()))) { + struct = getSimulationContext().getModel().getStructure(Structure.SpringStructureEnum.Intracellular.columnName); + } + SiteAttributesSpec sas = new SiteAttributesSpec(getSpeciesContextSpec(), newSite, struct); + int displacement = componentCount*4 + structuralSiteCount*3; + Coordinate coordinate = new Coordinate(0, SpeciesContextSpec.INITIAL_YZ_SITE_OFFSET, SpeciesContextSpec.INITIAL_YZ_SITE_OFFSET + displacement); + sas.setCoordinate(coordinate); + NamedColor color = new NamedColor("siteColor" + structuralSiteCount, ColorUtil.LIGHT20[structuralSiteCount % ColorUtil.LIGHT20.length]); + sas.setColor(color); + double radius = SiteAttributesSpec.DEFAULT_STRUCTURAL_SITE_RADIUS; + sas.setRadius(radius); // for now we stay with the default +// sas.setDiffusionRate(diffusion); + Map structureSiteAttributesMap = getSpeciesContextSpec().getStructuralSiteAttributesMap(); + structureSiteAttributesMap.put(newSite, sas); + molecularTypeSpecsTableModel.refreshData(); // refresh the model + fieldSpeciesContextSpec.firePropertyChange(SpeciesContextSpec.PROPERTY_NAME_SITE_ATTRIBUTE, null, newSite); + if (selectedRowBefore < 0) { // nothing was selected before, scroll up at need so that the new row is visible + int newRow = molecularTypeSpecsTableModel.getRowCount() - 1; + SwingUtilities.invokeLater(() -> { // must run after table update to ensure the new row is present in the table +// molecularTypeSpecsTable.setRowSelectionInterval(newRow, newRow); + molecularTypeSpecsTable.scrollRectToVisible(molecularTypeSpecsTable.getCellRect(newRow, 0, true)); + }); + } + } + private void deleteStructuralSiteActionPerformed() { + int row = molecularTypeSpecsTable.getSelectedRow(); + int rowToSelect = row; // capture the row BEFORE deleting + LinkNode ln = molecularTypeSpecsTableModel.getValueAt(row); + if(ln instanceof StructuralSite ss) { + Map structureSiteAttributesMap = getSpeciesContextSpec().getStructuralSiteAttributesMap(); + structureSiteAttributesMap.remove(ss); + } + molecularTypeSpecsTableModel.refreshData(); // refresh the model + int rowCount = molecularTypeSpecsTableModel.getRowCount(); // compute the new row index after deletion + if (rowCount == 0) { + return; // table is empty now + } + if (rowToSelect >= rowCount) { + rowToSelect = rowCount - 1; // we deleted the last row, select new last row (one up) + } + molecularTypeSpecsTable.setRowSelectionInterval(rowToSelect, rowToSelect); // Restore selection + } private void addLinkActionPerformed() { AddLinkPanel panel = new AddLinkPanel(this); @@ -858,27 +953,21 @@ private void addLinkActionPerformed() { panel.setChildWindow(childWindow); childWindow.setPreferredSize(dim); childWindow.showModal(); - if(panel.getButtonPushed() == AddLinkPanel.ActionButtons.Apply) { - MolecularComponentPattern firstMcp = panel.getFirstSiteList().getSelectedValue(); - MolecularComponentPattern secondMcp = panel.getSecondSiteList().getSelectedValue(); + LinkNode firstMcp = panel.getFirstSiteList().getSelectedValue(); + LinkNode secondMcp = panel.getSecondSiteList().getSelectedValue(); MolecularInternalLinkSpec mils = new MolecularInternalLinkSpec(fieldSpeciesContextSpec, firstMcp, secondMcp); fieldSpeciesContextSpec.getInternalLinkSet().add(mils); -// System.out.println("addLinkActionPerformed"); - return; - } else { - return; } + linkSpecsTableModel.refreshData(); } private void deleteLinkActionPerformed() { - int row = linkSpecsTable.getSelectedRow(); MolecularInternalLinkSpec selectedValue = linkSpecsTableModel.getValueAt(row); if(selectedValue != null) { fieldSpeciesContextSpec.getInternalLinkSet().remove(selectedValue); -// System.out.println("deleteLinkActionPerformed"); } - return; + linkSpecsTableModel.refreshData(); } } diff --git a/vcell-core/src/main/java/cbit/vcell/graph/SpeciesContextSpecLargeShape.java b/vcell-core/src/main/java/cbit/vcell/graph/SpeciesContextSpecLargeShape.java index 69b9cd9761..9d68bf74a6 100644 --- a/vcell-core/src/main/java/cbit/vcell/graph/SpeciesContextSpecLargeShape.java +++ b/vcell-core/src/main/java/cbit/vcell/graph/SpeciesContextSpecLargeShape.java @@ -55,7 +55,7 @@ public boolean contains(double x, double y) { private Structure structure = null; private boolean hasAnchor = false; - private MolecularComponentPattern mcpAnchor = null; + private LinkNode mcpAnchor = null; private double membraneX = 0; private double membraneY = 0; private double membraneRadius = 0; @@ -65,9 +65,9 @@ public boolean contains(double x, double y) { // we use these to compute some offset from top and left, so that the molecule will look nicely centered on screen private double minX = 0; // coordinate of the leftmost site - private MolecularComponentPattern leftmostSite = null; + private LinkNode leftmostSite = null; private double minY = 0; // coordinate of the topmost site - private MolecularComponentPattern topmostSite = null; // if more sites qualify we just keep the first we find + private LinkNode topmostSite = null; // if more sites qualify we just keep the first we find private double maxX = 0; // coordinate of the rightmost site private double maxY = 0; // coordinate of the bottommost site @@ -77,12 +77,12 @@ public boolean contains(double x, double y) { Map sasToEllipseMap = new LinkedHashMap<>(); Map milsToLineMap = new LinkedHashMap<>(); - private MolecularComponentPattern mcpSelected = null; // any or both of these may be selected in their tables, they are highlighted on the shape + private LinkNode lnSelected = null; // any or both of these may be selected in their tables, they are highlighted on the shape private MolecularInternalLinkSpec milsSelected = null; private Object lastSelectedObject = null; // this is the last selected object, we show it on top of everything else public SpeciesContextSpecLargeShape(SpeciesContextSpec scs, LargeShapeCanvas shapePanel, Displayable owner, - MolecularComponentPattern mcpSelected, MolecularInternalLinkSpec milsSelected, + LinkNode lnSelected, MolecularInternalLinkSpec milsSelected, Object lastSelectedObject, IssueListProvider issueListProvider) { super(issueListProvider); @@ -90,7 +90,7 @@ public SpeciesContextSpecLargeShape(SpeciesContextSpec scs, LargeShapeCanvas sha this.scs = scs; this.shapePanel = shapePanel; - this.mcpSelected = mcpSelected; + this.lnSelected = lnSelected; this.milsSelected = milsSelected; this.lastSelectedObject = lastSelectedObject; @@ -110,26 +110,27 @@ public SpeciesContextSpecLargeShape(SpeciesContextSpec scs, LargeShapeCanvas sha return; } - Map sasMap = scs.getSiteAttributesMap(); + Map merged = scs.getAllSiteAttributes(); Set ilSet = scs.getInternalLinkSet(); MolecularType mt = mtp.getMolecularType(); int counter = 0; // site counter - for(MolecularComponentPattern mcp : mtp.getComponentPatternList()) { - MolecularComponent mc = mcp.getMolecularComponent(); - SiteAttributesSpec sas = sasMap.get(mcp); + + for(Map.Entry e : merged.entrySet()) { + LinkNode ln = e.getKey(); + SiteAttributesSpec sas = e.getValue(); Structure structure = sas.getLocation(); if(structure.getName().equals(Structure.SpringStructureEnum.Extracellular.columnName)) { hasExtracellularSite = true; } else if(structure.getName().equals(Structure.SpringStructureEnum.Intracellular.columnName)) { hasIntracellularSite = true; } - if(mc.getName().equals(SpeciesContextSpec.AnchorSiteString)) { + if(ln.getName().equals(SpeciesContextSpec.AnchorSiteString)) { hasAnchor = true; - mcpAnchor = mcp; + mcpAnchor = ln; hasMembraneSite = true; - membraneX = sasMap.get(mcp).getZ(); - membraneY = sasMap.get(mcp).getY(); - membraneRadius = sasMap.get(mcp).getRadius(); + membraneX = merged.get(ln).getZ(); + membraneY = merged.get(ln).getY(); + membraneRadius = merged.get(ln).getRadius(); } Coordinate coordinate = sas.getCoordinate(); double x = coordinate.getZ(); @@ -137,18 +138,18 @@ public SpeciesContextSpecLargeShape(SpeciesContextSpec scs, LargeShapeCanvas sha if(counter == 0) { minX = x; minY = y; - leftmostSite = mcp; - topmostSite = mcp; + leftmostSite = ln; + topmostSite = ln; maxX = x; maxY = y; } else { if(x < minX) { minX = x; - leftmostSite = mcp; + leftmostSite = ln; } if(y < minY) { minY = y; - topmostSite = mcp; + topmostSite = ln; } if(x > maxX) { maxX = x; @@ -166,15 +167,12 @@ public SpeciesContextSpecLargeShape(SpeciesContextSpec scs, LargeShapeCanvas sha } private boolean isPlanarYZ() { // we only show entities that are 2D in the YZ plane - Map sasMap = scs.getSiteAttributesMap(); - // here we could either iterate through the sasMap, or through the components of the mtp - // we use the second method because it provides a sanity check between the components in the sasMap (application level) - // and the physiology (which is authoritative) + Map merged = scs.getAllSiteAttributes(); double oldX = 0; // dummy value to indulge the compiler - for(int i=0; i< mtp.getComponentPatternList().size(); i++) { - MolecularComponentPattern mcp = mtp.getComponentPatternList().get(i); - SiteAttributesSpec sas = sasMap.get(mcp); - Structure structure = sas.getLocation(); + int i=0; + for(Map.Entry e : merged.entrySet()) { + LinkNode ln = e.getKey(); + SiteAttributesSpec sas = e.getValue(); Coordinate coordinate = sas.getCoordinate(); // to be planar in YZ, the X coordinates of all sites must be equal double x = coordinate.getX(); // here x means x @@ -184,6 +182,7 @@ private boolean isPlanarYZ() { // we only show entities that are 2D in the YZ if(oldX != x) { return false; } + i++; } return true; } @@ -413,26 +412,15 @@ public void paintSelf(Graphics g, boolean bPaintContour) { paintDummy(g); return; } - Map sasMap = scs.getSiteAttributesMap(); - Map structuralSiteAttributesMap = scs.getStructuralSiteAttributesMap(); - Map merged = new LinkedHashMap<>(); - // Add physiological sites - for (Map.Entry e : sasMap.entrySet()) { - merged.put(e.getKey(), e.getValue()); - } - // Add structural sites - for (Map.Entry e : structuralSiteAttributesMap.entrySet()) { - merged.put(e.getKey(), e.getValue()); - } - + Map merged = scs.getAllSiteAttributes(); Set internalLinkSet = scs.getInternalLinkSet(); // we draw all objects as unselected first, and then we redraw the selected objects on top of everything else, // so that they look highlighted and are not hidden behind other objects for(MolecularInternalLinkSpec mils : internalLinkSet) { // draw links first, so that they are under the sites Pair link = mils.getLink(); - SiteAttributesSpec sas1 = sasMap.get(link.one); - SiteAttributesSpec sas2 = sasMap.get(link.two); + SiteAttributesSpec sas1 = merged.get(link.one); + SiteAttributesSpec sas2 = merged.get(link.two); double x1 = x_offset + sas1.getCoordinate().getZ(); double x2 = x_offset + sas2.getCoordinate().getZ(); double y1 = y_offset + sas1.getCoordinate().getY(); @@ -454,8 +442,8 @@ public void paintSelf(Graphics g, boolean bPaintContour) { } // we redraw the selected objects on top of all unselected objects - if(mcpSelected != null) { - SiteAttributesSpec sas = sasMap.get(mcpSelected); + if(lnSelected != null) { + SiteAttributesSpec sas = merged.get(lnSelected); Coordinate coord = sas.getCoordinate(); double radius = sas.getRadius(); NamedColor color = sas.getColor(); @@ -469,8 +457,8 @@ public void paintSelf(Graphics g, boolean bPaintContour) { } if(milsSelected != null) { Pair link = milsSelected.getLink(); - SiteAttributesSpec sas1 = sasMap.get(link.one); - SiteAttributesSpec sas2 = sasMap.get(link.two); + SiteAttributesSpec sas1 = merged.get(link.one); + SiteAttributesSpec sas2 = merged.get(link.two); double x1 = x_offset + sas1.getCoordinate().getZ(); double x2 = x_offset + sas2.getCoordinate().getZ(); double y1 = y_offset + sas1.getCoordinate().getY(); @@ -488,7 +476,7 @@ public void paintSelf(Graphics g, boolean bPaintContour) { if(lastSelectedObject != null) { if(lastSelectedObject instanceof LinkNode) { LinkNode mcp = (LinkNode)lastSelectedObject; - SiteAttributesSpec sas = sasMap.get(mcp); + SiteAttributesSpec sas = merged.get(mcp); Coordinate coord = sas.getCoordinate(); double radius = sas.getRadius(); NamedColor color = sas.getColor(); @@ -502,8 +490,8 @@ public void paintSelf(Graphics g, boolean bPaintContour) { } else if(lastSelectedObject instanceof MolecularInternalLinkSpec) { MolecularInternalLinkSpec mils = (MolecularInternalLinkSpec)lastSelectedObject; Pair link = mils.getLink(); - SiteAttributesSpec sas1 = sasMap.get(link.one); - SiteAttributesSpec sas2 = sasMap.get(link.two); + SiteAttributesSpec sas1 = merged.get(link.one); + SiteAttributesSpec sas2 = merged.get(link.two); double x1 = x_offset + sas1.getCoordinate().getZ(); double x2 = x_offset + sas2.getCoordinate().getZ(); double y1 = y_offset + sas1.getCoordinate().getY(); diff --git a/vcell-core/src/main/java/cbit/vcell/mapping/MolecularInternalLinkSpec.java b/vcell-core/src/main/java/cbit/vcell/mapping/MolecularInternalLinkSpec.java index 8c7141654f..90dfafc78c 100644 --- a/vcell-core/src/main/java/cbit/vcell/mapping/MolecularInternalLinkSpec.java +++ b/vcell-core/src/main/java/cbit/vcell/mapping/MolecularInternalLinkSpec.java @@ -12,6 +12,7 @@ import java.awt.Color; import java.awt.Graphics2D; import java.io.Serializable; +import java.util.Map; import cbit.vcell.model.*; import org.vcell.model.rbm.LinkNode; @@ -117,10 +118,12 @@ public LinkNode getLinkNodeTwo() { return fieldLinkNodeTwo; } public SiteAttributesSpec getSite1() { - return getSpeciesContextSpec().getSiteAttributesMap().get(fieldLinkNodeOne); + Map merged = getSpeciesContextSpec().getAllSiteAttributes(); + return merged.get(fieldLinkNodeOne); } public SiteAttributesSpec getSite2() { - return getSpeciesContextSpec().getSiteAttributesMap().get(fieldLinkNodeTwo); + Map merged = getSpeciesContextSpec().getAllSiteAttributes(); + return merged.get(fieldLinkNodeTwo); } public double getX1() { diff --git a/vcell-core/src/main/java/cbit/vcell/mapping/ReactionRuleSpec.java b/vcell-core/src/main/java/cbit/vcell/mapping/ReactionRuleSpec.java index de2c7fefaa..c57e22aae6 100644 --- a/vcell-core/src/main/java/cbit/vcell/mapping/ReactionRuleSpec.java +++ b/vcell-core/src/main/java/cbit/vcell/mapping/ReactionRuleSpec.java @@ -1211,6 +1211,7 @@ public void gatherIssues(IssueContext issueContext, List issueList, React SpeciesPattern spCandidate = scCandidate.getSpeciesPattern(); MolecularTypePattern mtpCandidate = spCandidate.getMolecularTypePatterns().get(0); MolecularType mtCandidate = mtpCandidate.getMolecularType(); + // I think we can safely ignore the structural sites since they do not participate in any reaction if(mtOursOne == mtCandidate) { siteAttributesMapOne = scs.getSiteAttributesMap(); } diff --git a/vcell-core/src/main/java/cbit/vcell/mapping/SiteAttributesSpec.java b/vcell-core/src/main/java/cbit/vcell/mapping/SiteAttributesSpec.java index a84c35c64b..c654935f88 100644 --- a/vcell-core/src/main/java/cbit/vcell/mapping/SiteAttributesSpec.java +++ b/vcell-core/src/main/java/cbit/vcell/mapping/SiteAttributesSpec.java @@ -10,24 +10,14 @@ package cbit.vcell.mapping; -import java.awt.Color; -import java.io.PrintWriter; import java.io.Serializable; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; import java.util.List; import java.util.Map; -import java.util.Set; import cbit.vcell.model.*; -import org.vcell.model.rbm.ComponentStateDefinition; -import org.vcell.model.rbm.ComponentStatePattern; -import org.vcell.model.rbm.MolecularComponent; -import org.vcell.model.rbm.MolecularComponentPattern; -import org.vcell.model.rbm.MolecularType; -import org.vcell.model.rbm.MolecularTypePattern; -import org.vcell.model.rbm.SpeciesPattern; +import org.vcell.model.rbm.*; +import org.vcell.model.ssld.SsldUtils; import org.vcell.util.*; import org.vcell.util.Issue.IssueCategory; import org.vcell.util.Issue.IssueSource; @@ -39,8 +29,11 @@ @SuppressWarnings("serial") public class SiteAttributesSpec implements Serializable, Identifiable, Displayable, IssueSource, Matchable { + + public final static double DEFAULT_STRUCTURAL_SITE_RADIUS = 0.6; // nm, we make them smaller as visual cue + private final SpeciesContextSpec fieldSpeciesContextSpec; - private MolecularComponentPattern fieldMolecularComponentPattern = null; + private LinkNode fieldLinkNode = null; private double fieldRadius = 1.0; private double fieldDiffusionRate = 1.0; private Structure fieldLocation = null; // feature or membrane @@ -51,13 +44,13 @@ public class SiteAttributesSpec implements Serializable, Identifiable, Displayab // the ComponentStatePattern must not be Any; can be recovered from the MolecularComponentPattern // the BondType must be None, can be recovered from the MolecularComponentPattern - public SiteAttributesSpec(SpeciesContextSpec scs, MolecularComponentPattern mcp, Structure structure) { + public SiteAttributesSpec(SpeciesContextSpec scs, LinkNode ln, Structure structure) { fieldSpeciesContextSpec = scs; - setMolecularComponentPattern(mcp); + setLinkNode(ln); setLocation(structure); } - public SiteAttributesSpec(SpeciesContextSpec scs, MolecularComponentPattern mcp, double radius, double diffusion, Structure structure, Coordinate coordinate, NamedColor color) { - this(scs, mcp, structure); + public SiteAttributesSpec(SpeciesContextSpec scs, LinkNode ln, double radius, double diffusion, Structure structure, Coordinate coordinate, NamedColor color) { + this(scs, ln, structure); setRadius(radius); setDiffusionRate(diffusion); setCoordinate(coordinate); @@ -67,11 +60,11 @@ public SiteAttributesSpec(SpeciesContextSpec scs, MolecularComponentPattern mcp, public SpeciesContextSpec getSpeciesContextSpec() { return fieldSpeciesContextSpec; } - public MolecularComponentPattern getMolecularComponentPattern() { - return fieldMolecularComponentPattern; + public LinkNode getLinkNode() { + return fieldLinkNode; } - public void setMolecularComponentPattern(MolecularComponentPattern molecularComponentPattern) { - this.fieldMolecularComponentPattern = molecularComponentPattern; + public void setLinkNode(LinkNode ln) { + this.fieldLinkNode = ln; } public double getRadius() { @@ -119,18 +112,31 @@ public double getZ() { } public int getIndex() { - MolecularComponent mc = getMolecularComponentPattern().getMolecularComponent(); - int index = mc.getIndex(); - return index; + LinkNode ln = getLinkNode(); + if(ln instanceof MolecularComponentPattern mcp) { + MolecularComponent mc = mcp.getMolecularComponent(); + int index = mc.getIndex(); + return index; + } else if(ln instanceof StructuralSite ss) { + int saMapSize = getSpeciesContextSpec().getSiteAttributesMap().size(); + int idx = SsldUtils.indexOfKey(getSpeciesContextSpec().getStructuralSiteAttributesMap(), ss); + return saMapSize + idx; // structural sites are indexed after the molecular component sites + } else { + throw new RuntimeException("getIndex(): unknown LinkNode type"); + } } - + public void writeType(StringBuilder sb) { // I/O the Site Type (mMlecularComponent) - if(getMolecularComponentPattern() == null) { + if(getLinkNode() == null) { throw new RuntimeException("writeType(): MolecularComponentPattern is null"); } - MolecularComponent mc = getMolecularComponentPattern().getMolecularComponent(); - List csdList = mc.getComponentStateDefinitions(); - sb.append("TYPE: Name \"" + mc.getName() + "\""); + List csdList = null; + LinkNode ln = getLinkNode(); + if(ln instanceof MolecularComponentPattern mcp) { + MolecularComponent mc = mcp.getMolecularComponent(); + csdList = mc.getComponentStateDefinitions(); + } + sb.append("TYPE: Name \"" + ln.getName() + "\""); sb.append(" Radius " + IOHelp.DF[5].format(getRadius()) + " D " + IOHelp.DF[3].format(getDiffusionRate()) + " Color " + getColor().getName()); sb.append(" STATES "); if(csdList == null || csdList.isEmpty()) { @@ -143,10 +149,15 @@ public void writeType(StringBuilder sb) { // I/O the Site Type (mMlecularCompon sb.append("\n"); } public void writeSite(StringBuilder sb) { - if(getMolecularComponentPattern() == null) { + if(getLinkNode() == null) { throw new RuntimeException("writeSite(): MolecularComponentPattern is null"); } - ComponentStatePattern csp = getMolecularComponentPattern().getComponentStatePattern(); + ComponentStatePattern csp = null; + LinkNode ln = getLinkNode(); + if(ln instanceof MolecularComponentPattern mcp) { + MolecularComponent mc = mcp.getMolecularComponent(); + csp = mcp.getComponentStatePattern(); + } ComponentStateDefinition csd = null; if(csp != null) { csd = csp.getComponentStateDefinition(); @@ -177,7 +188,7 @@ public boolean compareEqual(Matchable obj) { if(!fieldSpeciesContextSpec.compareEqual(candidate.getSpeciesContextSpec())) { return false; } - if(!fieldMolecularComponentPattern.compareEqual(candidate.getMolecularComponentPattern())) { + if(!fieldLinkNode.compareEqual(candidate.getLinkNode())) { return false; } if(fieldRadius != candidate.getRadius()) { @@ -220,8 +231,13 @@ public void gatherIssues(IssueContext issueContext, List issueVector) { public static final String typeName = "SiteAttributesSpec"; @Override public String getDisplayName() { - if(fieldMolecularComponentPattern != null) { - return fieldMolecularComponentPattern.getMolecularComponent().getDisplayName(); + if(fieldLinkNode != null) { + LinkNode ln = getLinkNode(); + if(ln instanceof MolecularComponentPattern mcp) { + return mcp.getMolecularComponent().getDisplayName(); + } else { + return ln.getDisplayName(); + } } return("?"); } diff --git a/vcell-core/src/main/java/cbit/vcell/mapping/SpeciesContextSpec.java b/vcell-core/src/main/java/cbit/vcell/mapping/SpeciesContextSpec.java index 089b2b13e5..d71c66c894 100644 --- a/vcell-core/src/main/java/cbit/vcell/mapping/SpeciesContextSpec.java +++ b/vcell-core/src/main/java/cbit/vcell/mapping/SpeciesContextSpec.java @@ -72,7 +72,7 @@ public class SpeciesContextSpec implements Matchable, ScopedSymbolTable, Seriali public static final String PROPERTY_NAME_SITE_SELECTED_IN_SHAPE = "SiteSelectedInShape"; public static final String PROPERTY_NAME_LINK_SELECTED_IN_SHAPE = "LinkSelectedInShape"; - private static final int INITIAL_YZ_SITE_OFFSET = 4; + public static final int INITIAL_YZ_SITE_OFFSET = 4; public static final boolean TrackClusters = true; // SpringSaLaD specific public static final boolean InitialLocationRandom = true; @@ -941,7 +941,7 @@ public void initializeForSpringSaLaD(MolecularType molecularType) { linksToRemove.add(oldMils); continue; } - // both lnOne and lnTwo are either StructuralSites or are MolecularComponentPattern with a match in oldToNewMcp, + // lnOne and lnTwo are StructuralSites or MolecularComponentPattern with a match in oldToNewMcp, // then we update the link Pair newLink = new Pair(oldToNewMcp.get(lnOne), oldToNewMcp.get(lnTwo)); oldMils.setLink(newLink); @@ -975,7 +975,6 @@ public VCUnitDefinition computeFluxUnit(){ return speciesContext.getUnitDefinition().multiplyBy(getLengthPerTimeUnit()); } - /** * The addPropertyChangeListener method was generated to support the propertyChange field. */ @@ -983,7 +982,6 @@ public synchronized void addPropertyChangeListener(java.beans.PropertyChangeList getPropertyChange().addPropertyChangeListener(listener); } - /** * The addVetoableChangeListener method was generated to support the vetoPropertyChange field. */ @@ -1304,12 +1302,18 @@ public void gatherIssues(IssueContext issueContext, List issueVector){ } } if(mcpList.size() > 1 && getInternalLinkSet().size() > 0) { - GraphContinuity.Graph graph = new GraphContinuity.Graph(mcpList.size()); - Map mcpMap = new LinkedHashMap<> (); - for(int i=0; i mcpMap = new LinkedHashMap<> (); + int count = 0; // total number of LinkNodes (MolecularComponentPattern and StructuralSite) in the molecule, used to build the graph + for(MolecularComponentPattern mcp : mcpList) { + mcpMap.put(mcp, count); + count++; + } + for (Map.Entry e : getStructuralSiteAttributesMap().entrySet()) { + StructuralSite ss = e.getKey(); + mcpMap.put(ss, count); + count++; } + GraphContinuity.Graph graph = new GraphContinuity.Graph(count); for(MolecularInternalLinkSpec mils : getInternalLinkSet()) { Pair link = mils.getLink(); int one = mcpMap.get(link.one); @@ -2472,13 +2476,24 @@ public Map getSiteAttributesMap() public void setSiteAttributesMap(Map siteAttributesMap){ this.siteAttributesMap = siteAttributesMap; } - // TODO: call these wherever we call the above get/set methods public Map getStructuralSiteAttributesMap(){ return structuralSiteAttributesMap; } public void setStructuralSiteAttributesMap(Map structuralSiteAttributesMap){ this.structuralSiteAttributesMap = structuralSiteAttributesMap; } + // ----------------------------------------------------------------------------- + // utility for merging siteAttributesMap and structuralSiteAttributesMap + public Map getAllSiteAttributes() { + Map merged = new LinkedHashMap<>(); + for (var e : siteAttributesMap.entrySet()) { + merged.put(e.getKey(), e.getValue()); + } + for (var e : structuralSiteAttributesMap.entrySet()) { + merged.put(e.getKey(), e.getValue()); + } + return merged; + } public SpatialQuantity[] getVelocityQuantities(QuantityComponent component){ ArrayList velocityQuantities = new ArrayList(); diff --git a/vcell-core/src/main/java/cbit/vcell/mapping/StructuralSite.java b/vcell-core/src/main/java/cbit/vcell/mapping/StructuralSite.java index 0d3e9b6ac5..d264f2224c 100644 --- a/vcell-core/src/main/java/cbit/vcell/mapping/StructuralSite.java +++ b/vcell-core/src/main/java/cbit/vcell/mapping/StructuralSite.java @@ -15,7 +15,7 @@ public StructuralSite(String name) { @Override public String getName() { - return null; + return name; } @@ -25,7 +25,7 @@ public boolean compareEqual(Matchable aThat) { if (this == aThat) { return true; } - if (!(aThat instanceof MolecularComponentPattern)) { + if (!(aThat instanceof StructuralSite)) { return false; } StructuralSite that = (StructuralSite)aThat; diff --git a/vcell-core/src/main/java/cbit/vcell/solver/Simulation.java b/vcell-core/src/main/java/cbit/vcell/solver/Simulation.java index 42c4ea0465..018c341a5b 100644 --- a/vcell-core/src/main/java/cbit/vcell/solver/Simulation.java +++ b/vcell-core/src/main/java/cbit/vcell/solver/Simulation.java @@ -20,6 +20,7 @@ import cbit.vcell.math.MathException; import cbit.vcell.math.VCML; import cbit.vcell.solver.SolverDescription.SolverFeature; +import org.vcell.model.rbm.LinkNode; import org.vcell.model.rbm.MolecularComponentPattern; import org.vcell.util.*; import org.vcell.util.Issue.IssueCategory; @@ -957,14 +958,15 @@ public void gatherIssues(IssueContext issueContext, List issueList) { double timeStep = getSolverTaskDescription().getTimeStep().getDefaultTimeStep(); if(simContext.getReactionContext() != null && simContext.getReactionContext().getSpeciesContextSpecs() != null) { for (SpeciesContextSpec scs : simContext.getReactionContext().getSpeciesContextSpecs()) { - for (Map.Entry entry : scs.getSiteAttributesMap().entrySet()) { - MolecularComponentPattern mcp = entry.getKey(); + Map merged = scs.getAllSiteAttributes(); + for (Map.Entry entry : merged.entrySet()) { + LinkNode ln = entry.getKey(); SiteAttributesSpec sas = entry.getValue(); double siteDiameter = 2.0 * sas.getRadius(); if (siteDiameter >= xPart || siteDiameter >= yPart || siteDiameter >= zPart) { // diameter of each site must be smaller than partition size on x, y, z String msg = "Molecule '" + scs.getSpeciesContext().getName() + "' site diameter must be smaller than partition size."; - String tip = "Increase partition size or reduce size of site '" + mcp.getMolecularComponent().getName() + "'"; + String tip = "Increase partition size or reduce size of site '" + ln.getName() + "'"; issueList.add(new Issue(this, issueContext, IssueCategory.Identifiers, msg, tip, Issue.Severity.ERROR)); break; } @@ -976,7 +978,7 @@ public void gatherIssues(IssueContext issueContext, List issueList) { // diffusion speed * dt must be smaller than the partition size on x, y, z so that the particle // won't be able to jump outside the current partition or its neighboring partitions String msg = "Molecule '" + scs.getSpeciesContext().getName() + "' must not diffuse farther than any adjacent partition within the time interval."; - String tip = "Reduce diffusion rate of site '" + mcp.getMolecularComponent().getName() + "' or reduce the time interval"; + String tip = "Reduce diffusion rate of site '" + ln.getName() + "' or reduce the time interval"; issueList.add(new Issue(this, issueContext, IssueCategory.Identifiers, msg, tip, Issue.Severity.ERROR)); break; } diff --git a/vcell-core/src/main/java/org/vcell/model/ssld/SsldUtils.java b/vcell-core/src/main/java/org/vcell/model/ssld/SsldUtils.java index 0e67ea7990..2d84387b0b 100644 --- a/vcell-core/src/main/java/org/vcell/model/ssld/SsldUtils.java +++ b/vcell-core/src/main/java/org/vcell/model/ssld/SsldUtils.java @@ -42,6 +42,18 @@ public static Qualifier fromPrefix(String s) { } } + public static int indexOfKey(Map map, K key) { + // we know it's a LinkedHashMap, so the order is deterministic; we want the index of the key in the order of insertion + int index = 0; + for (K k : map.keySet()) { + if (k.equals(key)) { + return index; + } + index++; + } + return -1; // not found + } + // result entry from langevin output public static class LangevinResult { public final Qualifier qualifier; diff --git a/vcell-server/src/main/java/cbit/vcell/modeldb/SpeciesContextSpecTable.java b/vcell-server/src/main/java/cbit/vcell/modeldb/SpeciesContextSpecTable.java index c8b6914c5f..b1872e93c4 100644 --- a/vcell-server/src/main/java/cbit/vcell/modeldb/SpeciesContextSpecTable.java +++ b/vcell-server/src/main/java/cbit/vcell/modeldb/SpeciesContextSpecTable.java @@ -11,7 +11,6 @@ package cbit.vcell.modeldb; import cbit.vcell.mapping.MolecularInternalLinkSpec; -import cbit.vcell.mapping.SimulationContext; import cbit.vcell.mapping.SiteAttributesSpec; import cbit.vcell.model.SpeciesContext; import cbit.vcell.model.Structure; @@ -188,7 +187,7 @@ static String getSiteAttributesSQL(SpeciesContextSpec scs) { StringBuilder sb = new StringBuilder(); for(Map.Entry entry : scs.getSiteAttributesMap().entrySet()) { SiteAttributesSpec sas = entry.getValue(); - sb.append(sas.getMolecularComponentPattern().getMolecularComponent().getName() + ","); + sb.append(sas.getLinkNode().getName() + ","); sb.append(sas.getLocation().getName() + ","); sb.append(sas.getRadius() + ","); sb.append(sas.getDiffusionRate() +","); @@ -254,8 +253,8 @@ static String getInternalLinksSQL(SpeciesContextSpec scs) { for( MolecularInternalLinkSpec mils : scs.internalLinkSet) { SiteAttributesSpec sas1 = mils.getSite1(); SiteAttributesSpec sas2 = mils.getSite2(); - sb.append(sas1.getMolecularComponentPattern().getMolecularComponent().getName() + ","); - sb.append(sas2.getMolecularComponentPattern().getMolecularComponent().getName() + ";"); + sb.append(sas1.getLinkNode().getName() + ","); + sb.append(sas2.getLinkNode().getName() + ";"); } return sb.toString(); } diff --git a/vcell-util/src/main/java/org/vcell/util/ColorUtil.java b/vcell-util/src/main/java/org/vcell/util/ColorUtil.java index 3bb8022ab7..9ce2097a2b 100644 --- a/vcell-util/src/main/java/org/vcell/util/ColorUtil.java +++ b/vcell-util/src/main/java/org/vcell/util/ColorUtil.java @@ -193,4 +193,28 @@ public static int calcBrightness(int red,int grn,int blu){ new Color(231,203,148) // muted beige }; + public static final Color[] LIGHT20 = { + new Color(231,203,148), // muted beige + new Color(181,207,107), // muted olive green + new Color(174,199,232), // light blue + new Color(255,187,120), // light orange + new Color(152,223,138), // light green + new Color(255,152,150), // light red + new Color(197,176,213), // light purple + new Color(219,219,141), // light olive + new Color(196,156,148), // light brown + new Color(247,182,210), // light pink + new Color(199,199,199), // light gray + new Color(158,218,229), // light teal + new Color(240,210,170), // pale sand + new Color(210,225,180), // pale moss + new Color(200,220,245), // pale sky blue + new Color(255,205,160), // pale apricot + new Color(185,235,185), // pale mint + new Color(255,180,175), // pale rose + new Color(215,195,230), // pale lavender + new Color(230,230,170) // pale straw + }; + + } From a114b6e44672838fd9207fc7a4980b330df0c0c1 Mon Sep 17 00:00:00 2001 From: Dan Vasilescu Date: Fri, 24 Apr 2026 18:58:44 -0400 Subject: [PATCH 03/16] Generalized use of LinkNode: consistency, bug fixing, testing --- .../MolecularStructuresPropertiesPanel.java | 4 +- .../gui/MolecularStructuresPanel.java | 19 ++- .../mapping/MolecularInternalLinkSpec.java | 27 +++- .../vcell/mapping/SiteAttributesSpec.java | 39 ++++- .../vcell/mapping/SpeciesContextSpec.java | 13 ++ .../java/org/vcell/model/ssld/SsldUtils.java | 24 +-- .../main/java/org/vcell/util/Coordinate.java | 139 +++--------------- 7 files changed, 123 insertions(+), 142 deletions(-) diff --git a/vcell-client/src/main/java/cbit/vcell/mapping/gui/MolecularStructuresPropertiesPanel.java b/vcell-client/src/main/java/cbit/vcell/mapping/gui/MolecularStructuresPropertiesPanel.java index 020732c686..054c67d036 100644 --- a/vcell-client/src/main/java/cbit/vcell/mapping/gui/MolecularStructuresPropertiesPanel.java +++ b/vcell-client/src/main/java/cbit/vcell/mapping/gui/MolecularStructuresPropertiesPanel.java @@ -74,8 +74,8 @@ public void propertyChange(PropertyChangeEvent evt) { updateShape(); } else if(evt.getSource() instanceof SpeciesContextSpec && evt.getPropertyName().equals(SpeciesContextSpec.PROPERTY_NAME_SITE_SELECTED_IN_TABLE)) { Object o = evt.getNewValue(); - if(o instanceof MolecularComponentPattern mcp) { - lnSelected = mcp; + if(o instanceof LinkNode ln) { + lnSelected = ln; lastSelectedObject = lnSelected; } else { lnSelected = null; diff --git a/vcell-client/src/main/java/org/vcell/model/springsalad/gui/MolecularStructuresPanel.java b/vcell-client/src/main/java/org/vcell/model/springsalad/gui/MolecularStructuresPanel.java index f131e87429..9042ec677e 100644 --- a/vcell-client/src/main/java/org/vcell/model/springsalad/gui/MolecularStructuresPanel.java +++ b/vcell-client/src/main/java/org/vcell/model/springsalad/gui/MolecularStructuresPanel.java @@ -312,14 +312,22 @@ private void initialize() { sitesPanel.setBorder(titleSites); linksPanel.setBorder(titleLinks); - top.setMinimumSize(new Dimension(0, 100)); - + // calculate the preferred height of the speciesContextSpecsTable based on the row height and header height, + // so that we show 3.2 rows worth of data (with the "more below" hint) without needing to scroll + JTable speciesTable = getSpeciesContextSpecsTable(); + int rowHeight = speciesTable.getRowHeight(); + int headerHeight = speciesTable.getTableHeader().getPreferredSize().height; + // 3.15 rows worth of height so user sees "more below" + int visibleRows = 3; + int extraPixels = (int)(0.15 * rowHeight); + int scsTablePreferredHeight = headerHeight + visibleRows * rowHeight + extraPixels; + thePanel.setLayout(new GridBagLayout()); gbc = new GridBagConstraints(); gbc.gridx = 0; gbc.gridy = 0; gbc.weightx = 1.0; - gbc.weighty = 0.4; + gbc.weighty = 0.25; gbc.fill = GridBagConstraints.BOTH; gbc.insets = new Insets(3, 0, 0, 0); // top, left, bottom, right thePanel.add(top, gbc); @@ -328,7 +336,7 @@ private void initialize() { gbc.gridx = 0; gbc.gridy = 1; gbc.weightx = 1.0; - gbc.weighty = 0.6; + gbc.weighty = 0.75; // gbc.gridheight = 2; gbc.fill = GridBagConstraints.BOTH; gbc.insets = new Insets(1, 0, 0, 0); @@ -337,6 +345,7 @@ private void initialize() { // we may want to use a scroll pane whose viewing area is the JTable to provide similar look with NetGen Console JScrollPane pt = new JScrollPane(getSpeciesContextSpecsTable()); pt.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + pt.setPreferredSize(new Dimension(0, scsTablePreferredHeight)); top.setLayout(new GridBagLayout()); // --- table of top panel gbc = new GridBagConstraints(); gbc.gridx = 0; @@ -345,7 +354,7 @@ private void initialize() { gbc.weighty = 1.0; gbc.fill = GridBagConstraints.BOTH; gbc.anchor = GridBagConstraints.NORTHWEST; - gbc.insets = new Insets(2, 3, 3, 4); + gbc.insets = new Insets(2, 3, 2, 2); top.add(pt, gbc); DefaultTableCellRenderer renderer = new DefaultScrollTableCellRenderer() { diff --git a/vcell-core/src/main/java/cbit/vcell/mapping/MolecularInternalLinkSpec.java b/vcell-core/src/main/java/cbit/vcell/mapping/MolecularInternalLinkSpec.java index 90dfafc78c..245842bd93 100644 --- a/vcell-core/src/main/java/cbit/vcell/mapping/MolecularInternalLinkSpec.java +++ b/vcell-core/src/main/java/cbit/vcell/mapping/MolecularInternalLinkSpec.java @@ -13,6 +13,7 @@ import java.awt.Graphics2D; import java.io.Serializable; import java.util.Map; +import java.util.Objects; import cbit.vcell.model.*; import org.vcell.model.rbm.LinkNode; @@ -237,11 +238,14 @@ public boolean compareEqual(Matchable obj) { if(!fieldSpeciesContextSpec.getSpeciesContext().compareEqual(theirMils.getSpeciesContextSpec().getSpeciesContext())) { return false; } - // note that while the link is scalar, not vector, we order one and two alphabetically - // hence, no need to also compare this.One with that.Two (I hope, at least) + // we order linkOne and Two alphabetically in the constructor, but the user may rename the sites in the + // physiology editor, so we need to compare both orders here if((fieldLinkNodeOne.compareEqual(theirMils.fieldLinkNodeOne)) && (fieldLinkNodeTwo.compareEqual(theirMils.fieldLinkNodeTwo))) { return true; + } else if((fieldLinkNodeOne.compareEqual(theirMils.fieldLinkNodeTwo)) && + (fieldLinkNodeTwo.compareEqual(theirMils.fieldLinkNodeOne))) { + return true; } return false; } @@ -272,8 +276,25 @@ public void writeLink(StringBuilder sb) { sb.append("\n"); } + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof MolecularInternalLinkSpec)) { + return false; + } + MolecularInternalLinkSpec other = (MolecularInternalLinkSpec) obj; + return compareEqual(other); // delegate to the domain-specific equality + } + @Override + public int hashCode() { // the HashSet has mutable objects, so we can't compute a proper hashCode based on the + // fields, otherwise we would have to remove and re-add the object to the HashSet every time we change it, + // which is not feasible, so we just return a constant here. This is not ideal, but it works because we don't + // have too many objects in the HashSet. + return 27; + } - // // TODO: not working properly, will use direct calls from SpeciesContextSpec // public void gatherIssues(IssueContext issueContext, List issueVector) { // issueContext = issueContext.newChildContext(ContextType.MolecularInternalLinkSpec, this); diff --git a/vcell-core/src/main/java/cbit/vcell/mapping/SiteAttributesSpec.java b/vcell-core/src/main/java/cbit/vcell/mapping/SiteAttributesSpec.java index c654935f88..6a0897d360 100644 --- a/vcell-core/src/main/java/cbit/vcell/mapping/SiteAttributesSpec.java +++ b/vcell-core/src/main/java/cbit/vcell/mapping/SiteAttributesSpec.java @@ -30,7 +30,7 @@ @SuppressWarnings("serial") public class SiteAttributesSpec implements Serializable, Identifiable, Displayable, IssueSource, Matchable { - public final static double DEFAULT_STRUCTURAL_SITE_RADIUS = 0.6; // nm, we make them smaller as visual cue + public final static double DEFAULT_STRUCTURAL_SITE_RADIUS = 0.8; // nm, we make them smaller as visual cue private final SpeciesContextSpec fieldSpeciesContextSpec; private LinkNode fieldLinkNode = null; @@ -227,7 +227,42 @@ public void gatherIssues(IssueContext issueContext, List issueVector) { issueVector.add(new Issue(this, issueContext, IssueCategory.Identifiers, msg, tip, Issue.Severity.WARNING)); } } - + + public static Coordinate getMinCoordinate(Map map) { + double minX = Double.POSITIVE_INFINITY; + double minY = Double.POSITIVE_INFINITY; + double minZ = Double.POSITIVE_INFINITY; + for (SiteAttributesSpec sas : map.values()) { + Coordinate c = sas.getCoordinate(); + if (c != null) { + if (c.getX() < minX) minX = c.getX(); + if (c.getY() < minY) minY = c.getY(); + if (c.getZ() < minZ) minZ = c.getZ(); + } + } + if (minX == Double.POSITIVE_INFINITY) { + return null; // map empty or all coords null + } + return new Coordinate(minX, minY, minZ); + } + public static Coordinate getMaxCoordinate(Map map) { + double maxX = Double.NEGATIVE_INFINITY; + double maxY = Double.NEGATIVE_INFINITY; + double maxZ = Double.NEGATIVE_INFINITY; + for (SiteAttributesSpec sas : map.values()) { + Coordinate c = sas.getCoordinate(); + if (c != null) { + if (c.getX() > maxX) maxX = c.getX(); + if (c.getY() > maxY) maxY = c.getY(); + if (c.getZ() > maxZ) maxZ = c.getZ(); + } + } + if (maxX == Double.NEGATIVE_INFINITY) { + return null; // map empty or all coords null + } + return new Coordinate(maxX, maxY, maxZ); + } + public static final String typeName = "SiteAttributesSpec"; @Override public String getDisplayName() { diff --git a/vcell-core/src/main/java/cbit/vcell/mapping/SpeciesContextSpec.java b/vcell-core/src/main/java/cbit/vcell/mapping/SpeciesContextSpec.java index d71c66c894..bfaa8a0a4a 100644 --- a/vcell-core/src/main/java/cbit/vcell/mapping/SpeciesContextSpec.java +++ b/vcell-core/src/main/java/cbit/vcell/mapping/SpeciesContextSpec.java @@ -831,6 +831,19 @@ public void initializeForSpringSaLaD(MolecularType molecularType) { } else { // if this is a new site added to an existing molecule, the existing sites already have ; // attributes (like coordinates, diffusion rates, colors) and links. // We cannot guess how the user will want to deal with the new site. + // Dan 4/24/2026 On the other hand, why not do an educated guess. If the new site is added to an + // existing molecule, then we can assume that the user wants it to be similar to the existing sites. + // So we can initialize the new site's attributes based on the existing sites. For example, we can + // set the new site's coordinates to be offset from the existing sites, and we can set the new + // site's color to be the next in the color array. This way, the user will have a reasonable + // starting point for the new site, and they can then adjust it as needed. + Map merged = getAllSiteAttributes(); + Coordinate minc = SiteAttributesSpec.getMinCoordinate(merged); + Coordinate maxc = SiteAttributesSpec.getMaxCoordinate(merged); + Coordinate coordinate = new Coordinate(minc.getX(), minc.getY(), maxc.getZ() + INITIAL_YZ_SITE_OFFSET); + sas.setCoordinate(coordinate); + NamedColor nextColor = Colors.COLORARRAY[merged.size() % Colors.COLORARRAY.length]; + sas.setColor(nextColor); } siteAttributesMap.put(mcp, sas); } diff --git a/vcell-core/src/main/java/org/vcell/model/ssld/SsldUtils.java b/vcell-core/src/main/java/org/vcell/model/ssld/SsldUtils.java index 2d84387b0b..70707e2a73 100644 --- a/vcell-core/src/main/java/org/vcell/model/ssld/SsldUtils.java +++ b/vcell-core/src/main/java/org/vcell/model/ssld/SsldUtils.java @@ -30,18 +30,6 @@ public class SsldUtils { private static final Logger lg = LogManager.getLogger(SsldUtils.class); - public enum Qualifier { - FREE, BOUND, TOTAL, NONE; - public static Qualifier fromPrefix(String s) { - for (Qualifier q : values()) { - if (q != NONE && q.name().equals(s)) { - return q; - } - } - return NONE; - } - } - public static int indexOfKey(Map map, K key) { // we know it's a LinkedHashMap, so the order is deterministic; we want the index of the key in the order of insertion int index = 0; @@ -54,6 +42,18 @@ public static int indexOfKey(Map map, K key) { return -1; // not found } + public enum Qualifier { + FREE, BOUND, TOTAL, NONE; + public static Qualifier fromPrefix(String s) { + for (Qualifier q : values()) { + if (q != NONE && q.name().equals(s)) { + return q; + } + } + return NONE; + } + } + // result entry from langevin output public static class LangevinResult { public final Qualifier qualifier; diff --git a/vcell-util/src/main/java/org/vcell/util/Coordinate.java b/vcell-util/src/main/java/org/vcell/util/Coordinate.java index 5b4860460f..71099ca554 100644 --- a/vcell-util/src/main/java/org/vcell/util/Coordinate.java +++ b/vcell-util/src/main/java/org/vcell/util/Coordinate.java @@ -34,12 +34,7 @@ public Coordinate(double x, double y, double z) { this.y = y; this.z = z; } -/** - * Insert the method's description here. - * Creation date: (8/10/00 5:50:50 PM) - * @return cbit.vcell.geometry.Coordinate - * @param coord cbit.vcell.geometry.Coordinate - */ + private static final double axisConversion(double origX, double origY, double origZ, int axisElement, int normalAxis, boolean bToStandardXYZ) { double result = 0; switch (normalAxis) { @@ -117,11 +112,7 @@ private static final double axisConversion(double origX, double origY, double or } return result; } -/** - * Insert the method's description here. - * Creation date: (8/8/00 4:03:38 PM) - * @return java.lang.Object - */ + public Object clone() { try{ Coordinate c = (Coordinate)super.clone(); @@ -134,9 +125,7 @@ public Object clone() { throw new InternalError(e.toString()); } } -/** - * compareEqual method comment. - */ + public boolean compareEqual(org.vcell.util.Matchable obj) { if (obj == null) { return false; @@ -150,12 +139,7 @@ public boolean compareEqual(org.vcell.util.Matchable obj) { } return true; } -/** - * Insert the method's description here. - * Creation date: (8/10/00 5:50:50 PM) - * @return cbit.vcell.geometry.Coordinate - * @param coord cbit.vcell.geometry.Coordinate - */ + public static final Coordinate convertAxisFromStandardXYZToNormal(double origX, double origY, double origZ,int normalAxis) { Coordinate newCoord = new Coordinate( axisConversion(origX, origY, origZ, X_AXIS, normalAxis,false), @@ -177,30 +161,15 @@ public static final Extent convertAxisFromStandardXYZToNormal(Extent extent,int (int)axisConversion(extent.getX(), extent.getY(), extent.getZ(), Z_AXIS, normalAxis,false)); return newExtent; } -/** - * Insert the method's description here. - * Creation date: (8/10/00 5:50:50 PM) - * @return cbit.vcell.geometry.Coordinate - * @param coord cbit.vcell.geometry.Coordinate - */ + public static final double convertAxisFromStandardXYZToNormal(double origX, double origY, double origZ, int axisElement, int normalAxis) { return axisConversion(origX,origY,origZ,axisElement,normalAxis, false); } -/** - * Insert the method's description here. - * Creation date: (8/10/00 5:50:50 PM) - * @return cbit.vcell.geometry.Coordinate - * @param coord cbit.vcell.geometry.Coordinate - */ + public static final double convertAxisFromStandardXYZToNormal(Coordinate coord, int axisElement, int normalAxis) { return axisConversion(coord.getX(),coord.getY(),coord.getZ(),axisElement,normalAxis, false); } -/** - * Insert the method's description here. - * Creation date: (8/10/00 5:50:50 PM) - * @return cbit.vcell.geometry.Coordinate - * @param coord cbit.vcell.geometry.Coordinate - */ + public static final Coordinate convertCoordinateFromNormalToStandardXYZ(double origX,double origY,double origZ, int normalAxis) { Coordinate newCoord = new Coordinate( axisConversion(origX, origY, origZ, X_AXIS, normalAxis,true), @@ -208,12 +177,7 @@ public static final Coordinate convertCoordinateFromNormalToStandardXYZ(double o axisConversion(origX, origY, origZ, Z_AXIS, normalAxis,true)); return newCoord; } -/** - * Insert the method's description here. - * Creation date: (8/10/00 5:50:50 PM) - * @return cbit.vcell.geometry.Coordinate - * @param coord cbit.vcell.geometry.Coordinate - */ + public static final void convertCoordinateIndexFromNormalToStandardXYZ(org.vcell.util.CoordinateIndex coordIndex, int normalAxis) { //Re-uses CoordinateIndex double origX = coordIndex.x; @@ -223,29 +187,16 @@ public static final void convertCoordinateIndexFromNormalToStandardXYZ(org.vcell coordIndex.y = (int)axisConversion(origX, origY, origZ, Y_AXIS, normalAxis,true); coordIndex.z = (int)axisConversion(origX, origY, origZ, Z_AXIS, normalAxis,true); } -/** - * This method was created in VisualAge. - * @return double - * @param coord cbit.vcell.geometry.Coordinate - */ + public double distanceTo(double cx, double cy, double cz) { cx -= x; cy -= y; cz -= z; return Math.sqrt(cx*cx + cy*cy + cz*cz); } -/** - * This method was created in VisualAge. - * @return double - * @param coord cbit.vcell.geometry.Coordinate - */ + public double distanceTo(Coordinate coord) { return distanceTo(coord.x,coord.y,coord.z); } -/** - * Insert the method's description here. - * Creation date: (2/10/2002 11:20:36 PM) - * @return boolean - * @param obj java.lang.Object - */ + public boolean equals(Object obj) { if (obj instanceof Coordinate){ Coordinate c = (Coordinate) obj; @@ -253,20 +204,13 @@ public boolean equals(Object obj) { } return(false); } -/** - * Insert the method's description here. - * Creation date: (10/18/00 1:03:52 PM) - */ + public static final java.awt.geom.Point2D.Double get2DProjection(Coordinate coord,int normalAxis) { double newX = Coordinate.convertAxisFromStandardXYZToNormal(coord,Coordinate.X_AXIS,normalAxis); double newY = Coordinate.convertAxisFromStandardXYZToNormal(coord,Coordinate.Y_AXIS,normalAxis); return new java.awt.geom.Point2D.Double(newX,newY); } -/** - * Insert the method's description here. - * Creation date: (10/10/00 6:18:10 PM) - * @param axis int - */ + public double getAxisElement(int axis) { switch (axis) { case X_AXIS : @@ -279,60 +223,31 @@ public double getAxisElement(int axis) { throw new RuntimeException("Unknow axis"); } } -/** - * Insert the method's description here. - * Creation date: (12/17/2003 4:22:40 PM) - * @return java.lang.String - * @param normalAxis int - */ + public final static String getNormalAxisPlaneName(int normalAxis) { if(normalAxis < 0 || normalAxis >= PLANENAMES.length){ throw new IllegalArgumentException("Unknwon Normal Axis = "+normalAxis); } return PLANENAMES[normalAxis]; } -/** - * This method was created in VisualAge. - * @return boolean - * @param coord cbit.vcell.geometry2.Coordinate - */ + public double getX () { return (x); } -/** - * This method was created in VisualAge. - * @return boolean - * @param coord cbit.vcell.geometry2.Coordinate - */ + public double getY () { return (y); } -/** - * This method was created in VisualAge. - * @return boolean - * @param coord cbit.vcell.geometry2.Coordinate - */ + public double getZ () { return (z); } -/** - * Insert the method's description here. - * Creation date: (2/10/2002 11:21:50 PM) - * @return int - */ + public int hashCode() { long bits = Double.doubleToLongBits(x + y + z); return ((int)(bits ^ (bits >> 32))); } -/** - * Insert the method's description here. - * Creation date: (8/13/00 12:57:30 PM) - * @return boolean - * @param coord cbit.vcell.geometry.Coordinate - * @param origin cbit.util.Origin - * @param extent cbit.util.Extent - * @param delta cbit.vcell.geometry.Coordinate - */ + public static final boolean isCoordinateInBounds(Coordinate coord, org.vcell.util.Origin origin, org.vcell.util.Extent extent, Coordinate deltaCoord) { Coordinate w0 = new Coordinate(origin.getX(), origin.getY(), origin.getZ()); Coordinate w1 = new Coordinate(w0.getX() + extent.getX(), w0.getY() + extent.getY(), w0.getZ() + extent.getZ()); @@ -369,22 +284,11 @@ public static final boolean isCoordinateInBounds(Coordinate coord, org.vcell.uti // return true; } -/** - * Insert the method's description here. - * Creation date: (8/10/00 6:27:27 PM) - * @return java.lang.String - */ + public String toString() { return "X="+x+" Y="+y+" Z="+z; } -/** - * Insert the method's description here. - * Creation date: (8/12/2005 1:06:21 PM) - * @return cbit.vcell.geometry.Coordinate - * @param orig cbit.util.Origin - * @param ext cbit.util.Extent - * @param dimensionFlag int - */ + public static double coordComponentFromSinglePlanePolicy(Origin argOrigin, Extent argExtent, int argAxisFlag) { if(argAxisFlag == X_AXIS){ @@ -394,7 +298,6 @@ public static double coordComponentFromSinglePlanePolicy(Origin argOrigin, Exten }else if(argAxisFlag == Z_AXIS){ return argExtent.getZ()/2.0 + argOrigin.getZ(); } - throw new IllegalArgumentException("Unknown Axis Flag="+argAxisFlag); } } From 11f17a44043eac8e9113abcea10c2c761b8b1072 Mon Sep 17 00:00:00 2001 From: Dan Vasilescu Date: Mon, 27 Apr 2026 19:34:12 -0400 Subject: [PATCH 04/16] Generalized use of LinkNode: mouse coordinates capture --- .../MolecularStructuresPropertiesPanel.java | 28 ++++- .../gui/MolecularStructuresPanel.java | 22 +++- .../graph/SpeciesContextSpecLargeShape.java | 111 +++++++++++++++--- .../mapping/MolecularInternalLinkSpec.java | 3 + .../vcell/mapping/SpeciesContextSpec.java | 2 +- 5 files changed, 143 insertions(+), 23 deletions(-) diff --git a/vcell-client/src/main/java/cbit/vcell/mapping/gui/MolecularStructuresPropertiesPanel.java b/vcell-client/src/main/java/cbit/vcell/mapping/gui/MolecularStructuresPropertiesPanel.java index 054c67d036..c2f24f05a1 100644 --- a/vcell-client/src/main/java/cbit/vcell/mapping/gui/MolecularStructuresPropertiesPanel.java +++ b/vcell-client/src/main/java/cbit/vcell/mapping/gui/MolecularStructuresPropertiesPanel.java @@ -46,9 +46,14 @@ public class MolecularStructuresPropertiesPanel extends DocumentEditorSubPanel { private JButton zoomLargerButton = null; private JButton zoomSmallerButton = null; + // traching mouse coordinates in nm (not pixels) + // will use for drag and drop (not implemented yet, but we need to track the mouse movements for that) + private double mouseX_nm = Double.NaN; + private double mouseY_nm = Double.NaN; + private Integer mousePixelX = null; // we also track the mouse in pixel coordinates, to decide whether to show the coordinates (we hide them if the mouse is outside the panel) + private Integer mousePixelY = null; private class EventHandler implements ActionListener, PropertyChangeListener { - @Override public void actionPerformed(java.awt.event.ActionEvent e) { if (e.getSource() == getZoomLargerButton()) { @@ -134,7 +139,7 @@ public void paintComponent(Graphics g) { } scsls = new SpeciesContextSpecLargeShape(speciesContextSpec, shapePanel, speciesContextSpec, lnSelected, milsSelected, lastSelectedObject, issueManager); - scsls.paintSelf(g); + scsls.paintSelf(g, mouseX_nm, mouseY_nm, mousePixelX, mousePixelY, shapePanel.getWidth(), shapePanel.getHeight()); } @Override public DisplayMode getDisplayMode() { @@ -189,6 +194,16 @@ public void mouseMoved(MouseEvent e) { // } } }); + shapePanel.addMouseMotionListener(new MouseMotionAdapter() { + @Override + public void mouseMoved(MouseEvent e) { + mouseX_nm = scsls.screenToNmX(e.getX()); + mouseY_nm = scsls.screenToNmY(e.getY()); + mousePixelX = e.getX(); + mousePixelY = e.getY(); + shapePanel.repaint(); + } + }); shapePanel.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { @@ -214,6 +229,14 @@ public void mouseClicked(MouseEvent e) { speciesContextSpec.firePropertyChange(PROPERTY_NAME_SITE_SELECTED_IN_SHAPE, null, lnSelected); } } + @Override + public void mouseExited(MouseEvent e) { + mouseX_nm = Double.NaN; + mouseY_nm = Double.NaN; + mousePixelX = null; + mousePixelY = null; + shapePanel.repaint(); + } }); shapePanel.setPreferredSize(new Dimension(2000, 800)); shapePanel.setBackground(new Color(0xe0e0e0)); @@ -320,6 +343,7 @@ public void setSpeciesContextSpec(SpeciesContextSpec scSpec) { milsSelected = null; updateInterface(); } + public void setBioModel(BioModel newValue) { if (bioModel == newValue) { return; diff --git a/vcell-client/src/main/java/org/vcell/model/springsalad/gui/MolecularStructuresPanel.java b/vcell-client/src/main/java/org/vcell/model/springsalad/gui/MolecularStructuresPanel.java index 9042ec677e..cb61a5f57a 100644 --- a/vcell-client/src/main/java/org/vcell/model/springsalad/gui/MolecularStructuresPanel.java +++ b/vcell-client/src/main/java/org/vcell/model/springsalad/gui/MolecularStructuresPanel.java @@ -30,6 +30,7 @@ import org.vcell.model.rbm.MolecularTypePattern; import org.vcell.model.rbm.SpeciesPattern; import org.vcell.util.Coordinate; +import org.vcell.util.Pair; import org.vcell.util.gui.*; import org.vcell.util.gui.ScrollTable.ScrollTableBooleanCellRenderer; import org.vcell.util.gui.sorttable.SortTableModel; @@ -940,7 +941,25 @@ private void deleteStructuralSiteActionPerformed() { LinkNode ln = molecularTypeSpecsTableModel.getValueAt(row); if(ln instanceof StructuralSite ss) { Map structureSiteAttributesMap = getSpeciesContextSpec().getStructuralSiteAttributesMap(); - structureSiteAttributesMap.remove(ss); + structureSiteAttributesMap.remove(ss); // remove the site itself + + // we also need to remove any links that are connected to this site + Set links = fieldSpeciesContextSpec.getInternalLinkSet(); + java.util.List toRemove = new ArrayList<>(); + boolean hasLinksToRemove = false; + for (MolecularInternalLinkSpec mils : links) { + Pair p = mils.getLink(); + if (p.one == ss || p.two == ss) { + toRemove.add(mils); + hasLinksToRemove = true; + } + } + for (MolecularInternalLinkSpec mils : toRemove) { + links.remove(mils); + } + if(hasLinksToRemove) { + linkSpecsTableModel.refreshData(); + } } molecularTypeSpecsTableModel.refreshData(); // refresh the model int rowCount = molecularTypeSpecsTableModel.getRowCount(); // compute the new row index after deletion @@ -967,6 +986,7 @@ private void addLinkActionPerformed() { LinkNode secondMcp = panel.getSecondSiteList().getSelectedValue(); MolecularInternalLinkSpec mils = new MolecularInternalLinkSpec(fieldSpeciesContextSpec, firstMcp, secondMcp); fieldSpeciesContextSpec.getInternalLinkSet().add(mils); + fieldSpeciesContextSpec.firePropertyChange(SpeciesContextSpec.PROPERTY_NAME_LINK_LENGTH, null, mils); } linkSpecsTableModel.refreshData(); } diff --git a/vcell-core/src/main/java/cbit/vcell/graph/SpeciesContextSpecLargeShape.java b/vcell-core/src/main/java/cbit/vcell/graph/SpeciesContextSpecLargeShape.java index 9d68bf74a6..e11725cb5f 100644 --- a/vcell-core/src/main/java/cbit/vcell/graph/SpeciesContextSpecLargeShape.java +++ b/vcell-core/src/main/java/cbit/vcell/graph/SpeciesContextSpecLargeShape.java @@ -34,8 +34,8 @@ public boolean contains(double x, double y) { } private static final double NmToPixelRatio = 18; - private static final double DEFAULT_UPPER_CORNER = 5.3; // default screen coordinates where we want to display the first site - private static final double DEFAULT_LEFT_CORNER = 13; // in nm + private static final double DEFAULT_UPPER_CORNER = 3; // default screen coordinates where we want to display the first site + private static final double DEFAULT_LEFT_CORNER = 8; // in nm // x, y positions where we want to begin drawing the shape (nm from top and left of painting area) private double x_offset = DEFAULT_LEFT_CORNER; @@ -81,6 +81,13 @@ public boolean contains(double x, double y) { private MolecularInternalLinkSpec milsSelected = null; private Object lastSelectedObject = null; // this is the last selected object, we show it on top of everything else + private double mouseX_nm = Double.NaN; // tracking mouse over the shape, in nm coordinates (depends on the zoom level) + private double mouseY_nm = Double.NaN; + private Integer mousePixelX = null; // we also track the mouse in pixel coordinates, to decide whether to show the coordinates (we hide them if the mouse is outside the panel) + private Integer mousePixelY = null; + private int panelWidth = 0; + private int panelHeight = 0; + public SpeciesContextSpecLargeShape(SpeciesContextSpec scs, LargeShapeCanvas shapePanel, Displayable owner, LinkNode lnSelected, MolecularInternalLinkSpec milsSelected, Object lastSelectedObject, IssueListProvider issueListProvider) { @@ -162,8 +169,8 @@ public SpeciesContextSpecLargeShape(SpeciesContextSpec scs, LargeShapeCanvas sha } // now compute the offsets, we want the sites nicely centered on screen // UPPER_CORNER, LEFT_CORNER - x_offset = DEFAULT_LEFT_CORNER - minX; - y_offset = DEFAULT_UPPER_CORNER - minY; +// x_offset = DEFAULT_LEFT_CORNER - minX; +// y_offset = DEFAULT_UPPER_CORNER - minY; } private boolean isPlanarYZ() { // we only show entities that are 2D in the YZ plane @@ -224,7 +231,8 @@ private void paintCompartments(Graphics g) { g.setColor(Color.black); double VerticalTextOffset = 1.0; // vertical means y coord if(!hasMembraneSite) { - double x = minX+x_offset; +// double x = minX+x_offset; + double x = x_offset; double y = VerticalTextOffset; g2.drawString(name, (int)(nmToPixelRatio * x), (int)(NmToPixelRatio * y)); } else { @@ -257,18 +265,7 @@ private void paintAxes(Graphics g) { RenderingHints hintsOld = g2.getRenderingHints(); Stroke strokeOld = g2.getStroke(); - Font font; - int z = shapePanel.getZoomFactor(); - nmToPixelRatio = NmToPixelRatio + z; - g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - - if(z > -3) { - font = fontOld.deriveFont(Font.BOLD); - g.setFont(font); - } else { - font = fontOld; - g.setFont(font); - } + setFontForZoom(g); int startX = 15; // coordinates for the arrow line (z-axis) int startY = 15; @@ -318,10 +315,51 @@ private void paintAxes(Graphics g) { String regularText = "The molecule is "; g2.drawString(regularText, startX, startY); int regularTextWidth = fm.stringWidth(regularText); - font = fontOld.deriveFont(Font.BOLD); + Font font = fontOld.deriveFont(Font.BOLD); g2.setFont(font); g2.drawString("3D", startX + regularTextWidth, startY); } + g2.setStroke(strokeOld); + g2.setRenderingHints(hintsOld); + g2.setFont(fontOld); + g2.setPaint(paintOld); + g2.setColor(colorOld); + } + + private void paintCoordinates(Graphics g) { + + if (Double.isNaN(mouseX_nm) || Double.isNaN(mouseY_nm)) { + return; // do not draw coordinates yet + } + if (mousePixelX == null || mousePixelY == null) { + return; // no pixel position yet + } + if (mouseX_nm < 0 || mouseY_nm < 0) { + return; // do not show negative nm coordinates + } + if (mousePixelX < 0 || mousePixelX > panelWidth || mousePixelY < 0 || mousePixelY > panelHeight) { + return; // do not show if outside panel bounds + } + + Graphics2D g2 = (Graphics2D) g; + Color colorOld = g2.getColor(); + Paint paintOld = g2.getPaint(); + Font fontOld = g2.getFont(); + RenderingHints hintsOld = g2.getRenderingHints(); + Stroke strokeOld = g2.getStroke(); + + int startX = 15; // coordinates for the arrow line (Y-axis) + int startY = 160; + g2.setColor(Color.darkGray); + setFontForZoom(g); + + // snap to nearest 0.1 nm + double y_snapped = snapToTenth(mouseY_nm); + double z_snapped = snapToTenth(mouseX_nm); + // note that screen x coordinate corresponds to z coordinate in the model, + // and screen y coordinate corresponds to y coordinate in the model + String text = String.format("Y = %.1f nm, Z = %.1f nm", y_snapped, z_snapped); + g2.drawString(text, startX, startY); g2.setStroke(strokeOld); g2.setRenderingHints(hintsOld); @@ -408,6 +446,7 @@ public void paintSelf(Graphics g, boolean bPaintContour) { // } paintCompartments(g); paintAxes(g); + paintCoordinates(g); if(mtp == null || mtp.getComponentPatternList().size() == 0) { // paint empty dummy paintDummy(g); return; @@ -508,7 +547,6 @@ public void paintSelf(Graphics g, boolean bPaintContour) { public Object contains(Point point) { - for (Map.Entry entry : ellipseToSasMap.entrySet()) { Ellipse2D oval = entry.getKey(); SiteAttributesSpec sas = entry.getValue(); @@ -526,10 +564,45 @@ public Object contains(Point point) { return null; } + private Font setFontForZoom(Graphics g) { + Graphics2D g2 = (Graphics2D)g; + Font fontOld = g2.getFont(); + Font font; + int z = shapePanel.getZoomFactor(); + if(z > -3) { + font = fontOld.deriveFont(Font.BOLD); + g.setFont(font); + } else { + font = fontOld; + g.setFont(font); + } + return font; + } + public double screenToNmX(int pixelX) { + return (pixelX / nmToPixelRatio) - x_offset; + } + public double screenToNmY(int pixelY) { + return (pixelY / nmToPixelRatio) - y_offset; + } + private static double snapToTenth(double v) { + return Math.round(v * 10.0) / 10.0; + } + @Override public void paintSelf(Graphics g) { paintSelf(g, true); } + public void paintSelf(Graphics g, Double mouseX_nm, Double mouseY_nm, Integer mousePixelX, Integer mousePixelY, + int panelWidth, int panelHeight) { + this.mouseX_nm = mouseX_nm; + this.mouseY_nm = mouseY_nm; + this.mousePixelX = mousePixelX; + this.mousePixelY = mousePixelY; + this.panelWidth = panelWidth; + this.panelHeight = panelHeight; + paintSelf(g); + } + @Override public boolean isHighlighted() { return false; diff --git a/vcell-core/src/main/java/cbit/vcell/mapping/MolecularInternalLinkSpec.java b/vcell-core/src/main/java/cbit/vcell/mapping/MolecularInternalLinkSpec.java index 245842bd93..5bfe66e7c8 100644 --- a/vcell-core/src/main/java/cbit/vcell/mapping/MolecularInternalLinkSpec.java +++ b/vcell-core/src/main/java/cbit/vcell/mapping/MolecularInternalLinkSpec.java @@ -235,6 +235,9 @@ public boolean compareEqual(Matchable obj) { // we skip compareEqual for the SpeciesContextSpec, otherwise we end up in an infinite loop !!! // we just compare the SpeciesContext (which is perfectly legit from a springsalad perspective, // where we can only have one SpeciesContext object for each Molecule) + if (fieldSpeciesContextSpec == null || fieldSpeciesContextSpec.getSpeciesContext() == null) { + return false; // or throw + } if(!fieldSpeciesContextSpec.getSpeciesContext().compareEqual(theirMils.getSpeciesContextSpec().getSpeciesContext())) { return false; } diff --git a/vcell-core/src/main/java/cbit/vcell/mapping/SpeciesContextSpec.java b/vcell-core/src/main/java/cbit/vcell/mapping/SpeciesContextSpec.java index bfaa8a0a4a..070bef2967 100644 --- a/vcell-core/src/main/java/cbit/vcell/mapping/SpeciesContextSpec.java +++ b/vcell-core/src/main/java/cbit/vcell/mapping/SpeciesContextSpec.java @@ -1508,7 +1508,7 @@ public void gatherIssues(IssueContext issueContext, List issueVector){ if(!sc.getName().equals(mtp.getMolecularType().getName())){ String msg = "The Species and the Molecular Type must share the same name."; String tip = msg; - issueVector.add(new Issue(this, issueContext, IssueCategory.Identifiers, msg, tip, Issue.Severity.WARNING)); + issueVector.add(new Issue(this, issueContext, IssueCategory.Identifiers, msg, tip, Issue.Severity.ERROR)); return; } From 8d6a2a3f4acc09349030884937c6eec3856bc7e1 Mon Sep 17 00:00:00 2001 From: Dan Vasilescu Date: Tue, 28 Apr 2026 12:49:15 -0400 Subject: [PATCH 05/16] Test fixed, issue upgraded from WARNING to ERROR due to causing consistency errors --- .../java/cbit/vcell/biomodel/SpringSaLaDGoodReactionsTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vcell-core/src/test/java/cbit/vcell/biomodel/SpringSaLaDGoodReactionsTest.java b/vcell-core/src/test/java/cbit/vcell/biomodel/SpringSaLaDGoodReactionsTest.java index f3afaf1b9f..77b6943efe 100644 --- a/vcell-core/src/test/java/cbit/vcell/biomodel/SpringSaLaDGoodReactionsTest.java +++ b/vcell-core/src/test/java/cbit/vcell/biomodel/SpringSaLaDGoodReactionsTest.java @@ -100,7 +100,7 @@ public void test_springsalad_model_bad() throws IOException, XmlParseException, simContext.gatherIssues(issueContext, issueList, true); // bIgnoreMathDescription == true int numErrors = checkIssuesBySeverity(issueList, Issue.Severity.ERROR); int numWarnings = checkIssuesBySeverity(issueList, Issue.Severity.WARNING); - assertTrue((numErrors == 2 && numWarnings == 6) ? true : false, "expecting 2 errors and warnings for this model"); + assertTrue((numErrors == 4 && numWarnings == 4) ? true : false, "expecting 2 errors and warnings for this model"); /* We should detect the following: From e29e9df969dd1d21d632193dca483df359b1875f Mon Sep 17 00:00:00 2001 From: Dan Vasilescu Date: Tue, 28 Apr 2026 17:44:53 -0400 Subject: [PATCH 06/16] Bug fixed, lastSelectedObject could be stale --- .../mapping/gui/MolecularStructuresPropertiesPanel.java | 1 + .../cbit/vcell/graph/SpeciesContextSpecLargeShape.java | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/vcell-client/src/main/java/cbit/vcell/mapping/gui/MolecularStructuresPropertiesPanel.java b/vcell-client/src/main/java/cbit/vcell/mapping/gui/MolecularStructuresPropertiesPanel.java index c2f24f05a1..ae05154275 100644 --- a/vcell-client/src/main/java/cbit/vcell/mapping/gui/MolecularStructuresPropertiesPanel.java +++ b/vcell-client/src/main/java/cbit/vcell/mapping/gui/MolecularStructuresPropertiesPanel.java @@ -341,6 +341,7 @@ public void setSpeciesContextSpec(SpeciesContextSpec scSpec) { lnSelected = null; milsSelected = null; + lastSelectedObject = null; updateInterface(); } diff --git a/vcell-core/src/main/java/cbit/vcell/graph/SpeciesContextSpecLargeShape.java b/vcell-core/src/main/java/cbit/vcell/graph/SpeciesContextSpecLargeShape.java index e11725cb5f..dbee74f4da 100644 --- a/vcell-core/src/main/java/cbit/vcell/graph/SpeciesContextSpecLargeShape.java +++ b/vcell-core/src/main/java/cbit/vcell/graph/SpeciesContextSpecLargeShape.java @@ -514,8 +514,11 @@ public void paintSelf(Graphics g, boolean bPaintContour) { // we now redraw the last selected object on top of everything else if(lastSelectedObject != null) { if(lastSelectedObject instanceof LinkNode) { - LinkNode mcp = (LinkNode)lastSelectedObject; - SiteAttributesSpec sas = merged.get(mcp); + LinkNode ln = (LinkNode)lastSelectedObject; + SiteAttributesSpec sas = merged.get(ln); + if(sas == null) { + throw new RuntimeException("Stale last selected site: " + ln.getName()); + } Coordinate coord = sas.getCoordinate(); double radius = sas.getRadius(); NamedColor color = sas.getColor(); From d6a31f518cda6b79c9ae1a1e00a296680cee0e1d Mon Sep 17 00:00:00 2001 From: Dan Vasilescu Date: Wed, 29 Apr 2026 17:43:16 -0400 Subject: [PATCH 07/16] Cursor tracking, crosshair, snap to grid --- .../MolecularStructuresPropertiesPanel.java | 81 +++++++++---------- .../graph/SpeciesContextSpecLargeShape.java | 65 +++++++++------ .../src/main/java/cbit/vcell/xml/XMLTags.java | 3 +- 3 files changed, 79 insertions(+), 70 deletions(-) diff --git a/vcell-client/src/main/java/cbit/vcell/mapping/gui/MolecularStructuresPropertiesPanel.java b/vcell-client/src/main/java/cbit/vcell/mapping/gui/MolecularStructuresPropertiesPanel.java index ae05154275..7e5549ec2a 100644 --- a/vcell-client/src/main/java/cbit/vcell/mapping/gui/MolecularStructuresPropertiesPanel.java +++ b/vcell-client/src/main/java/cbit/vcell/mapping/gui/MolecularStructuresPropertiesPanel.java @@ -19,10 +19,7 @@ import javax.swing.*; import java.awt.*; -import java.awt.event.ActionListener; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.awt.event.MouseMotionAdapter; +import java.awt.event.*; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; @@ -46,11 +43,10 @@ public class MolecularStructuresPropertiesPanel extends DocumentEditorSubPanel { private JButton zoomLargerButton = null; private JButton zoomSmallerButton = null; - // traching mouse coordinates in nm (not pixels) + // tracking mouse coordinates // will use for drag and drop (not implemented yet, but we need to track the mouse movements for that) - private double mouseX_nm = Double.NaN; - private double mouseY_nm = Double.NaN; - private Integer mousePixelX = null; // we also track the mouse in pixel coordinates, to decide whether to show the coordinates (we hide them if the mouse is outside the panel) + private boolean mouseInsidePanel = false; // whether mouse is inside the panel client area (we use this to decide whether to show the coordinates or not, we hide them when the mouse is outside the panel) + private Integer mousePixelX = null; // we also track the mouse in pixel coordinates, which we transform in nm units in the shape private Integer mousePixelY = null; private class EventHandler implements ActionListener, PropertyChangeListener { @@ -137,9 +133,10 @@ public void paintComponent(Graphics g) { if (speciesContextSpec == null || speciesContextSpec.getSpeciesContext() == null) { return; } + Rectangle visibleViewport = getVisibleRect(); scsls = new SpeciesContextSpecLargeShape(speciesContextSpec, shapePanel, speciesContextSpec, lnSelected, milsSelected, lastSelectedObject, issueManager); - scsls.paintSelf(g, mouseX_nm, mouseY_nm, mousePixelX, mousePixelY, shapePanel.getWidth(), shapePanel.getHeight()); + scsls.paintSelf(g, mousePixelX, mousePixelY, mouseInsidePanel, visibleViewport); } @Override public DisplayMode getDisplayMode() { @@ -185,20 +182,12 @@ public boolean isViewSingleRow() { shapePanel.addMouseMotionListener(new MouseMotionAdapter() { @Override public void mouseMoved(MouseEvent e) { - // this will fire if the mouse moves over the panel, we may need it for drag and drop - super.mouseMoved(e); -// Point overWhat = e.getPoint(); -// Object overObject = scsls.contains(overWhat); -// if(overObject != null) { -// System.out.println("MouseMotionAdapter: over something"); -// } + mousePixelX = e.getX(); + mousePixelY = e.getY(); + shapePanel.repaint(); } - }); - shapePanel.addMouseMotionListener(new MouseMotionAdapter() { @Override - public void mouseMoved(MouseEvent e) { - mouseX_nm = scsls.screenToNmX(e.getX()); - mouseY_nm = scsls.screenToNmY(e.getY()); + public void mouseDragged(MouseEvent e) { mousePixelX = e.getX(); mousePixelY = e.getY(); shapePanel.repaint(); @@ -230,14 +219,39 @@ public void mouseClicked(MouseEvent e) { } } @Override + public void mouseReleased(MouseEvent e) { + mousePixelX = e.getX(); + mousePixelY = e.getY(); + shapePanel.repaint(); + } + @Override + public void mouseEntered(MouseEvent e) { + mouseInsidePanel = true; + } + @Override public void mouseExited(MouseEvent e) { - mouseX_nm = Double.NaN; - mouseY_nm = Double.NaN; + mouseInsidePanel = false; mousePixelX = null; mousePixelY = null; shapePanel.repaint(); } }); + shapePanel.addMouseWheelListener(new MouseWheelListener() { + @Override + public void mouseWheelMoved(MouseWheelEvent e) { + if (!mouseInsidePanel) { + return; + } + Point mouseScreen = MouseInfo.getPointerInfo().getLocation(); // get current mouse location on screen + Point mousePanel = new Point(mouseScreen); // convert to shapePanel coordinate system + SwingUtilities.convertPointFromScreen(mousePanel, shapePanel); + mousePixelX = mousePanel.x; // update stored pixel coordinates + mousePixelY = mousePanel.y; + shapePanel.repaint(); + shapePanel.getParent().dispatchEvent(e); // we dispatch the event to the parent scroll pane, which will handle the actual scrolling + } + }); + shapePanel.setPreferredSize(new Dimension(2000, 800)); shapePanel.setBackground(new Color(0xe0e0e0)); shapePanel.setZoomFactor(-2); @@ -269,20 +283,6 @@ public void mouseExited(MouseEvent e) { gbc.anchor = GridBagConstraints.WEST; optionsPanel.add(getZoomSmallerButton(), gbc); -// gbc = new GridBagConstraints(); -// gbc.gridx = 0; -// gbc.gridy = 2; -// gbc.anchor = GridBagConstraints.WEST; -// gbc.insets = new Insets(4, 4, 4, 10); -// optionsPanel.add(new JLabel("Reaction Radius"), gbc); -// -// gbc = new GridBagConstraints(); -// gbc.gridx = 1; -// gbc.gridy = 2; -// gbc.anchor = GridBagConstraints.WEST; -// gbc.insets = new Insets(4, 4, 4, 10); -// optionsPanel.add(new JLabel("2 nm"), gbc); - gbc = new GridBagConstraints(); gbc.gridx = 0; gbc.gridy = 2; // make this 3 if we show reaction radius here @@ -313,16 +313,12 @@ private void updateInterface() { } updateShape(); } - private void updateShape() { if(speciesContextSpec == null || speciesContextSpec.getSpeciesContext() == null) { return; } scsls = new SpeciesContextSpecLargeShape(speciesContextSpec, shapePanel, speciesContextSpec, lnSelected, milsSelected, lastSelectedObject, issueManager); - -// shapePanel.setPreferredSize(scsls.getMaxSize()); - shapePanel.repaint(); } @@ -337,8 +333,6 @@ public void setSpeciesContextSpec(SpeciesContextSpec scSpec) { this.speciesContextSpec = scSpec; scSpec.addPropertyChangeListener(eventHandler); } -// getSpeciesContextSpecParameterTableModel().setSpeciesContextSpec(scSpec); - lnSelected = null; milsSelected = null; lastSelectedObject = null; @@ -394,4 +388,5 @@ private JButton getZoomSmallerButton() { } return zoomSmallerButton; } + } diff --git a/vcell-core/src/main/java/cbit/vcell/graph/SpeciesContextSpecLargeShape.java b/vcell-core/src/main/java/cbit/vcell/graph/SpeciesContextSpecLargeShape.java index dbee74f4da..f4c464fcf0 100644 --- a/vcell-core/src/main/java/cbit/vcell/graph/SpeciesContextSpecLargeShape.java +++ b/vcell-core/src/main/java/cbit/vcell/graph/SpeciesContextSpecLargeShape.java @@ -81,12 +81,10 @@ public boolean contains(double x, double y) { private MolecularInternalLinkSpec milsSelected = null; private Object lastSelectedObject = null; // this is the last selected object, we show it on top of everything else - private double mouseX_nm = Double.NaN; // tracking mouse over the shape, in nm coordinates (depends on the zoom level) - private double mouseY_nm = Double.NaN; + boolean mouseInsidePanel = false; // we track whether the mouse is inside the panel, to decide whether to show the coordinates (we hide them if the mouse is outside the panel) private Integer mousePixelX = null; // we also track the mouse in pixel coordinates, to decide whether to show the coordinates (we hide them if the mouse is outside the panel) private Integer mousePixelY = null; - private int panelWidth = 0; - private int panelHeight = 0; + private Rectangle visibleViewport = null; // we track the visible viewport of the scroll pane, to decide where to show the coordinates public SpeciesContextSpecLargeShape(SpeciesContextSpec scs, LargeShapeCanvas shapePanel, Displayable owner, LinkNode lnSelected, MolecularInternalLinkSpec milsSelected, @@ -328,18 +326,23 @@ private void paintAxes(Graphics g) { private void paintCoordinates(Graphics g) { - if (Double.isNaN(mouseX_nm) || Double.isNaN(mouseY_nm)) { - return; // do not draw coordinates yet - } + int locationX = 15; // default location where to paint the mouse coordinates + int locationY = 160; if (mousePixelX == null || mousePixelY == null) { return; // no pixel position yet } - if (mouseX_nm < 0 || mouseY_nm < 0) { - return; // do not show negative nm coordinates - } - if (mousePixelX < 0 || mousePixelX > panelWidth || mousePixelY < 0 || mousePixelY > panelHeight) { + if (mousePixelX < 0 || mousePixelY < 0 || mouseInsidePanel == false) { return; // do not show if outside panel bounds } + if(visibleViewport != null) { // this should always be the case + locationX = visibleViewport.x + 15; + locationY = visibleViewport.y + visibleViewport.height - 20; + } + double mouseY_nm = screenToNmY(mousePixelY); + double mouseX_nm = screenToNmX(mousePixelX); + if(mouseY_nm <= 0 || mouseX_nm <= 0) { + return; // do not show if the mouse would be outside shape area bounds + } Graphics2D g2 = (Graphics2D) g; Color colorOld = g2.getColor(); @@ -348,18 +351,23 @@ private void paintCoordinates(Graphics g) { RenderingHints hintsOld = g2.getRenderingHints(); Stroke strokeOld = g2.getStroke(); - int startX = 15; // coordinates for the arrow line (Y-axis) - int startY = 160; - g2.setColor(Color.darkGray); - setFontForZoom(g); - - // snap to nearest 0.1 nm - double y_snapped = snapToTenth(mouseY_nm); + double y_snapped = snapToTenth(mouseY_nm); // snap to nearest 0.1 nm double z_snapped = snapToTenth(mouseX_nm); // note that screen x coordinate corresponds to z coordinate in the model, // and screen y coordinate corresponds to y coordinate in the model + g2.setColor(Color.darkGray); + setFontForZoom(g); String text = String.format("Y = %.1f nm, Z = %.1f nm", y_snapped, z_snapped); - g2.drawString(text, startX, startY); + g2.drawString(text, locationX, locationY); + + // draw crosshair at snapped location + int snapPixelY = nmToScreenY(y_snapped); + int snapPixelX = nmToScreenX(z_snapped); + g2.setColor(Color.black); + g2.setStroke(new BasicStroke(1f)); + int arm = 8; // length of the crosshair arms in pixels + g2.drawLine(snapPixelX - arm, snapPixelY, snapPixelX + arm, snapPixelY); + g2.drawLine(snapPixelX, snapPixelY - arm, snapPixelX, snapPixelY + arm); g2.setStroke(strokeOld); g2.setRenderingHints(hintsOld); @@ -446,9 +454,9 @@ public void paintSelf(Graphics g, boolean bPaintContour) { // } paintCompartments(g); paintAxes(g); - paintCoordinates(g); if(mtp == null || mtp.getComponentPatternList().size() == 0) { // paint empty dummy paintDummy(g); + paintCoordinates(g); return; } Map merged = scs.getAllSiteAttributes(); @@ -545,6 +553,7 @@ public void paintSelf(Graphics g, boolean bPaintContour) { lineToMilsMap.put(newLine, mils); } } + paintCoordinates(g); } @@ -587,6 +596,12 @@ public double screenToNmX(int pixelX) { public double screenToNmY(int pixelY) { return (pixelY / nmToPixelRatio) - y_offset; } + public int nmToScreenX(double nmX) { + return (int) Math.round((nmX + x_offset) * nmToPixelRatio); + } + public int nmToScreenY(double nmY) { + return (int) Math.round((nmY + y_offset) * nmToPixelRatio); + } private static double snapToTenth(double v) { return Math.round(v * 10.0) / 10.0; } @@ -595,14 +610,12 @@ private static double snapToTenth(double v) { public void paintSelf(Graphics g) { paintSelf(g, true); } - public void paintSelf(Graphics g, Double mouseX_nm, Double mouseY_nm, Integer mousePixelX, Integer mousePixelY, - int panelWidth, int panelHeight) { - this.mouseX_nm = mouseX_nm; - this.mouseY_nm = mouseY_nm; + public void paintSelf(Graphics g, Integer mousePixelX, Integer mousePixelY, boolean mouseInsidePanel, + Rectangle visibleViewport) { this.mousePixelX = mousePixelX; this.mousePixelY = mousePixelY; - this.panelWidth = panelWidth; - this.panelHeight = panelHeight; + this.mouseInsidePanel = mouseInsidePanel; + this.visibleViewport = visibleViewport; paintSelf(g); } diff --git a/vcell-core/src/main/java/cbit/vcell/xml/XMLTags.java b/vcell-core/src/main/java/cbit/vcell/xml/XMLTags.java index a8693e1dce..03f80a7559 100644 --- a/vcell-core/src/main/java/cbit/vcell/xml/XMLTags.java +++ b/vcell-core/src/main/java/cbit/vcell/xml/XMLTags.java @@ -887,7 +887,8 @@ public class XMLTags { // SpringSaLaD tags public final static String SiteAttributesMapTag = "SiteAttributesMap"; - public final static String SiteAttributesSpecTag = "SiteAttributesSpec"; + public final static String SiteAttributesSpecTag = "SiteAttributesSpec"; + public final static String StructuralSiteAttributesSpecTag = "StructuralSiteAttributesSpec"; public final static String SiteRefAttrTag = "SiteRef"; public final static String MoleculeRefAttrTag = "MoleculeRef"; public final static String SiteLocationRefAttrTag = "SiteLocationRef"; From 0a31fb81e93492296168c8a5897b03755ac58994 Mon Sep 17 00:00:00 2001 From: Dan Vasilescu Date: Thu, 30 Apr 2026 17:41:53 -0400 Subject: [PATCH 08/16] Structural sites. Added to the LangevinMathMapping --- .../vcell/mapping/LangevinMathMapping.java | 24 ++++++++++++++++++- .../vcell/mapping/SpeciesContextSpec.java | 16 ++++++++++++- .../LangevinParticleMolecularComponent.java | 10 ++++++-- .../java/cbit/vcell/math/MathDescription.java | 24 ++++++++++++++++++- 4 files changed, 69 insertions(+), 5 deletions(-) diff --git a/vcell-core/src/main/java/cbit/vcell/mapping/LangevinMathMapping.java b/vcell-core/src/main/java/cbit/vcell/mapping/LangevinMathMapping.java index b2bc208cbd..6256b39efd 100644 --- a/vcell-core/src/main/java/cbit/vcell/mapping/LangevinMathMapping.java +++ b/vcell-core/src/main/java/cbit/vcell/mapping/LangevinMathMapping.java @@ -1143,8 +1143,9 @@ private HashMap addSpeciesPatterns Model model = getSimulationContext().getModel(); List observableList = model.getRbmModelContainer().getObservableList(); List molecularTypeList = model.getRbmModelContainer().getMolecularTypeList(); + int siteIndex = 1; for (MolecularType molecularType : molecularTypeList) { - Map mcpToLpmc = new LinkedHashMap<> (); + Map mcpToLpmc = new LinkedHashMap<> (); LangevinParticleMolecularType particleMolecularType = new LangevinParticleMolecularType(molecularType.getName()); SpeciesContextSpec scs = molecularTypeToSpeciesContextSpecMap.get(molecularType); // scs may be null for Sink and Source if(scs != null) { @@ -1171,6 +1172,7 @@ private HashMap addSpeciesPatterns // pairs of mcp / sas are being added to the siteAttributesMap SiteAttributesSpec sas = siteAttributesMap.get(mcp); // TODO: perhaps move this to constructor so that object will be complete from the start + particleMolecularComponent.setIndex(siteIndex); particleMolecularComponent.setColor(sas.getColor()); particleMolecularComponent.setLocation(sas.getLocation().getName()); particleMolecularComponent.setCoordinate(sas.getCoordinate()); @@ -1179,6 +1181,26 @@ private HashMap addSpeciesPatterns mcpToLpmc.put(mcp, particleMolecularComponent); } particleMolecularType.addMolecularComponent(particleMolecularComponent); + siteIndex++; + } + if(scs != null) { + Map structuralSiteAttributesMap = scs.getStructuralSiteAttributesMap(); + for(Map.Entry entry : structuralSiteAttributesMap.entrySet()) { + StructuralSite structuralSite = entry.getKey(); + SiteAttributesSpec sas = entry.getValue(); + String pmcName = structuralSite.getName(); + String pmcId = particleMolecularType.getName() + "_" + structuralSite.getName(); + LangevinParticleMolecularComponent particleMolecularComponent = new LangevinParticleMolecularComponent(pmcId, pmcName); + particleMolecularComponent.setIndex(siteIndex); + particleMolecularComponent.setColor(sas.getColor()); + particleMolecularComponent.setLocation(sas.getLocation().getName()); + particleMolecularComponent.setCoordinate(sas.getCoordinate()); + particleMolecularComponent.setRadius(sas.getRadius()); + particleMolecularComponent.setDiffusionRate(sas.getDiffusionRate()); + mcpToLpmc.put(structuralSite, particleMolecularComponent); + particleMolecularType.addMolecularComponent(particleMolecularComponent); + siteIndex++; + } } if(!molecularType.isAnchorAll()) { List anchorList = new ArrayList<>(); diff --git a/vcell-core/src/main/java/cbit/vcell/mapping/SpeciesContextSpec.java b/vcell-core/src/main/java/cbit/vcell/mapping/SpeciesContextSpec.java index 070bef2967..6689cce832 100644 --- a/vcell-core/src/main/java/cbit/vcell/mapping/SpeciesContextSpec.java +++ b/vcell-core/src/main/java/cbit/vcell/mapping/SpeciesContextSpec.java @@ -1355,7 +1355,21 @@ public void gatherIssues(IssueContext issueContext, List issueVector){ } } } - + // alternate way to check for duplicate links, we use the string representation of the links + Set linkStringSet = new LinkedHashSet<> (); + for(MolecularInternalLinkSpec mils : getInternalLinkSet()) { + String one = mils.getLinkNodeOne().getName(); + String two = mils.getLinkNodeTwo().getName(); + boolean ret0 = linkStringSet.add(one + "::" + two); // returns false if already exists + boolean ret1 = linkStringSet.add(two + "::" + one); + if(ret0 == false || ret1 == false) { + String msg = "Duplicate link: " + one + " :: " + two + ". Alternate method failed to detect the duplicate."; + String tip = msg; + // we escalate to error to also emphasize that previous method failed to detect the duplicate + issueVector.add(new Issue(this, issueContext, IssueCategory.Identifiers, msg, tip, Issue.Severity.ERROR)); + return; + } + } // 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 Structure struct = sc.getStructure(); MolecularComponentPattern mcpAnchor = null; diff --git a/vcell-core/src/main/java/cbit/vcell/math/LangevinParticleMolecularComponent.java b/vcell-core/src/main/java/cbit/vcell/math/LangevinParticleMolecularComponent.java index 46600ba08c..d21b761a5c 100644 --- a/vcell-core/src/main/java/cbit/vcell/math/LangevinParticleMolecularComponent.java +++ b/vcell-core/src/main/java/cbit/vcell/math/LangevinParticleMolecularComponent.java @@ -19,7 +19,8 @@ @SuppressWarnings("serial") public class LangevinParticleMolecularComponent extends ParticleMolecularComponent { - + + private int fieldIndex = 0; // index of the component in the molecule, used for ordering components in the molecule private double fieldRadius = 1.0; private double fieldDiffusionRate = 1.0; private String fieldLocation = null; // feature or membrane name, identical to subdomain name @@ -185,5 +186,10 @@ public NamedColor getColor() { public void setColor(NamedColor fieldColor) { this.fieldColor = fieldColor; } - + public int getIndex() { + return fieldIndex; + } + public void setIndex(int siteIndex) { + this.fieldIndex = siteIndex; + } } diff --git a/vcell-core/src/main/java/cbit/vcell/math/MathDescription.java b/vcell-core/src/main/java/cbit/vcell/math/MathDescription.java index 7342c725f5..2f94a3e5b9 100644 --- a/vcell-core/src/main/java/cbit/vcell/math/MathDescription.java +++ b/vcell-core/src/main/java/cbit/vcell/math/MathDescription.java @@ -2248,7 +2248,29 @@ public void gatherIssues(IssueContext issueContext, List issueList){ setWarning(ex.getMessage()); } } - } else if(isRuleBased()){ + } else if(isRuleBased()) { + + } else if(isLangevin()) { + List pmtList = getParticleMolecularTypes(); + for(ParticleMolecularType pmt : pmtList) { + if(!(pmt instanceof LangevinParticleMolecularType)) { + Issue issue = new Issue(this, issueContext, IssueCategory.MathDescription_CompartmentalModel, + "LangevinParticleMolecularType expected", Issue.SEVERITY_WARNING); + issueList.add(issue); + } + for(ParticleMolecularComponent pmc : pmt.getComponentList()) { + if(!(pmc instanceof LangevinParticleMolecularComponent)) { + Issue issue = new Issue(this, issueContext, IssueCategory.MathDescription_CompartmentalModel, + "LangevinParticleMolecularComponent expected", Issue.SEVERITY_WARNING); + } + LangevinParticleMolecularComponent lpmc = (LangevinParticleMolecularComponent) pmc; + // this is very useful test to catch math-related roundtrip errors and missing initializations + if(lpmc.getIndex() == 0) { + Issue issue = new Issue(this, issueContext, IssueCategory.MathDescription_CompartmentalModel, + "LangevinParticleMolecularComponent index should start with 1", Issue.SEVERITY_WARNING); + } + } + } } else { // ODE model // From 47f4d55b67a03bdfcf7bad122607847d4d332d3c Mon Sep 17 00:00:00 2001 From: Dan Vasilescu Date: Fri, 1 May 2026 17:36:51 -0400 Subject: [PATCH 09/16] Structural sites: solver input file creation --- .../gui/MolecularStructuresPanel.java | 6 ++- .../vcell/mapping/LangevinMathMapping.java | 3 -- .../LangevinParticleMolecularComponent.java | 7 --- .../java/cbit/vcell/math/MathDescription.java | 6 --- .../math/ParticleMolecularTypePattern.java | 2 +- .../solver/langevin/LangevinLngvWriter.java | 50 ++++++++++++++----- 6 files changed, 42 insertions(+), 32 deletions(-) diff --git a/vcell-client/src/main/java/org/vcell/model/springsalad/gui/MolecularStructuresPanel.java b/vcell-client/src/main/java/org/vcell/model/springsalad/gui/MolecularStructuresPanel.java index cb61a5f57a..d4df83b8ee 100644 --- a/vcell-client/src/main/java/org/vcell/model/springsalad/gui/MolecularStructuresPanel.java +++ b/vcell-client/src/main/java/org/vcell/model/springsalad/gui/MolecularStructuresPanel.java @@ -34,6 +34,7 @@ import org.vcell.util.gui.*; import org.vcell.util.gui.ScrollTable.ScrollTableBooleanCellRenderer; import org.vcell.util.gui.sorttable.SortTableModel; +import org.vcell.util.springsalad.Colors; import org.vcell.util.springsalad.NamedColor; import org.vcell.util.ColorUtil; @@ -918,8 +919,9 @@ private void addStructuralSiteActionPerformed() { int displacement = componentCount*4 + structuralSiteCount*3; Coordinate coordinate = new Coordinate(0, SpeciesContextSpec.INITIAL_YZ_SITE_OFFSET, SpeciesContextSpec.INITIAL_YZ_SITE_OFFSET + displacement); sas.setCoordinate(coordinate); - NamedColor color = new NamedColor("siteColor" + structuralSiteCount, ColorUtil.LIGHT20[structuralSiteCount % ColorUtil.LIGHT20.length]); - sas.setColor(color); +// NamedColor nextColor = Colors.COLORARRAY[(componentCount+structuralSiteCount) % Colors.COLORARRAY.length]; +// NamedColor color = new NamedColor("siteColor" + structuralSiteCount, ColorUtil.LIGHT20[structuralSiteCount % ColorUtil.LIGHT20.length]); + sas.setColor(Colors.GOLD); double radius = SiteAttributesSpec.DEFAULT_STRUCTURAL_SITE_RADIUS; sas.setRadius(radius); // for now we stay with the default // sas.setDiffusionRate(diffusion); diff --git a/vcell-core/src/main/java/cbit/vcell/mapping/LangevinMathMapping.java b/vcell-core/src/main/java/cbit/vcell/mapping/LangevinMathMapping.java index 6256b39efd..f8136c44ae 100644 --- a/vcell-core/src/main/java/cbit/vcell/mapping/LangevinMathMapping.java +++ b/vcell-core/src/main/java/cbit/vcell/mapping/LangevinMathMapping.java @@ -1171,8 +1171,6 @@ private HashMap addSpeciesPatterns // need to clean up and update the scs -> ils, scs -> siteAttributesMap at the point where the new // pairs of mcp / sas are being added to the siteAttributesMap SiteAttributesSpec sas = siteAttributesMap.get(mcp); - // TODO: perhaps move this to constructor so that object will be complete from the start - particleMolecularComponent.setIndex(siteIndex); particleMolecularComponent.setColor(sas.getColor()); particleMolecularComponent.setLocation(sas.getLocation().getName()); particleMolecularComponent.setCoordinate(sas.getCoordinate()); @@ -1191,7 +1189,6 @@ private HashMap addSpeciesPatterns String pmcName = structuralSite.getName(); String pmcId = particleMolecularType.getName() + "_" + structuralSite.getName(); LangevinParticleMolecularComponent particleMolecularComponent = new LangevinParticleMolecularComponent(pmcId, pmcName); - particleMolecularComponent.setIndex(siteIndex); particleMolecularComponent.setColor(sas.getColor()); particleMolecularComponent.setLocation(sas.getLocation().getName()); particleMolecularComponent.setCoordinate(sas.getCoordinate()); diff --git a/vcell-core/src/main/java/cbit/vcell/math/LangevinParticleMolecularComponent.java b/vcell-core/src/main/java/cbit/vcell/math/LangevinParticleMolecularComponent.java index d21b761a5c..81d85bc86c 100644 --- a/vcell-core/src/main/java/cbit/vcell/math/LangevinParticleMolecularComponent.java +++ b/vcell-core/src/main/java/cbit/vcell/math/LangevinParticleMolecularComponent.java @@ -20,7 +20,6 @@ @SuppressWarnings("serial") public class LangevinParticleMolecularComponent extends ParticleMolecularComponent { - private int fieldIndex = 0; // index of the component in the molecule, used for ordering components in the molecule private double fieldRadius = 1.0; private double fieldDiffusionRate = 1.0; private String fieldLocation = null; // feature or membrane name, identical to subdomain name @@ -186,10 +185,4 @@ public NamedColor getColor() { public void setColor(NamedColor fieldColor) { this.fieldColor = fieldColor; } - public int getIndex() { - return fieldIndex; - } - public void setIndex(int siteIndex) { - this.fieldIndex = siteIndex; - } } diff --git a/vcell-core/src/main/java/cbit/vcell/math/MathDescription.java b/vcell-core/src/main/java/cbit/vcell/math/MathDescription.java index 2f94a3e5b9..2f763611c4 100644 --- a/vcell-core/src/main/java/cbit/vcell/math/MathDescription.java +++ b/vcell-core/src/main/java/cbit/vcell/math/MathDescription.java @@ -2263,12 +2263,6 @@ public void gatherIssues(IssueContext issueContext, List issueList){ Issue issue = new Issue(this, issueContext, IssueCategory.MathDescription_CompartmentalModel, "LangevinParticleMolecularComponent expected", Issue.SEVERITY_WARNING); } - LangevinParticleMolecularComponent lpmc = (LangevinParticleMolecularComponent) pmc; - // this is very useful test to catch math-related roundtrip errors and missing initializations - if(lpmc.getIndex() == 0) { - Issue issue = new Issue(this, issueContext, IssueCategory.MathDescription_CompartmentalModel, - "LangevinParticleMolecularComponent index should start with 1", Issue.SEVERITY_WARNING); - } } } } else { diff --git a/vcell-core/src/main/java/cbit/vcell/math/ParticleMolecularTypePattern.java b/vcell-core/src/main/java/cbit/vcell/math/ParticleMolecularTypePattern.java index 173213fb89..f2d7a18c77 100644 --- a/vcell-core/src/main/java/cbit/vcell/math/ParticleMolecularTypePattern.java +++ b/vcell-core/src/main/java/cbit/vcell/math/ParticleMolecularTypePattern.java @@ -36,7 +36,7 @@ public ParticleMolecularComponentPattern getMolecularComponentPattern(ParticleMo return mcp; } } - throw new RuntimeException("All components are added in the constructor, so here it can never be null"); + return null; // may be a structural site } public void removeMolecularComponentPattern(ParticleMolecularComponentPattern molecularComponentPattern) { diff --git a/vcell-core/src/main/java/org/vcell/solver/langevin/LangevinLngvWriter.java b/vcell-core/src/main/java/org/vcell/solver/langevin/LangevinLngvWriter.java index 703a2e9424..df33d1ac4b 100644 --- a/vcell-core/src/main/java/org/vcell/solver/langevin/LangevinLngvWriter.java +++ b/vcell-core/src/main/java/org/vcell/solver/langevin/LangevinLngvWriter.java @@ -8,6 +8,7 @@ import cbit.vcell.math.*; import cbit.vcell.model.Structure; import cbit.vcell.solver.*; +import org.vcell.model.rbm.MolecularComponent; import org.vcell.util.Pair; import cbit.vcell.geometry.Geometry; @@ -66,6 +67,7 @@ public class LangevinLngvWriter { private static Map particlePropertiesMap = new LinkedHashMap<> (); // initial conditions for seed species private static Map particleJumpProcessMap = new LinkedHashMap<> (); // list of reactions private static Set particleMolecularTytpeSet = new LinkedHashSet<> (); // molecular types + private static Set structuralSiteSet = new LinkedHashSet<> (); // the components that are structural sites private static MathDescription mathDescription = null; // static ArrayList currentMappingOfReactionParticipants = new ArrayList(); @@ -853,6 +855,8 @@ private static void writeSpeciesFileInfo(StringBuilder sb) { } private static void writeSpeciesInfo(StringBuilder sb) { + structuralSiteSet.clear(); // we will populate this map as we write the species info, and then use it + // to exclude them from tracking for( Map.Entry entry : particlePropertiesMap.entrySet()) { ParticleProperties pp = entry.getKey(); SubDomain subDomain = entry.getValue(); @@ -924,23 +928,37 @@ private static void writeSpeciesInfo(StringBuilder sb) { lpmc.writeType(sb); } sb.append("\n"); - for(ParticleMolecularComponent pmc : lpmt.getComponentList()) { + for(int siteIndex = 0; siteIndex < lpmt.getComponentList().size(); siteIndex++) { + ParticleMolecularComponent pmc = lpmt.getComponentList().get(siteIndex); // a few lines that follow are needed to extract the initial state from the ParticleMolecularComponentPattern ParticleMolecularComponentPattern pmcp = particleMolecularTypePattern.getMolecularComponentPattern(pmc); - ParticleComponentStatePattern pcsp = pmcp.getComponentStatePattern(); - ParticleComponentStateDefinition pcsd = null; - if(pcsp != null) { - pcsd = pcsp.getParticleComponentStateDefinition(); - } - String initialState; - if(pcsp == null || pcsd == null) { - initialState = StateZero; + if(pmcp != null) { // if there is a pattern for the component, we can extract the initial state + // from it. If not, we assume it's a structural site + ParticleComponentStatePattern pcsp = pmcp.getComponentStatePattern(); + ParticleComponentStateDefinition pcsd = null; + if (pcsp != null) { + pcsd = pcsp.getParticleComponentStateDefinition(); + } + String initialState; + if (pcsp == null || pcsd == null) { + initialState = StateZero; + } else { + initialState = pcsd.getName(); + } + LangevinParticleMolecularComponent lpmc = (LangevinParticleMolecularComponent) pmc; + sb.append(" "); + lpmc.writeSite(sb, lpmt.getComponentList().indexOf(lpmc), initialState); } else { - initialState = pcsd.getName(); + structuralSiteSet.add(pmc); + // if there is no pattern for the component, we assume it's a structural site with no state, + // and we set the initial state to StateZero by default + String initialState = StateZero; + LangevinParticleMolecularComponent lpmc = (LangevinParticleMolecularComponent) pmc; + sb.append(" "); + // for structural sites, the index is just the position in the component list, starting with 0, + // which is exactly what we have here with siteIndex + lpmc.writeSite(sb, siteIndex, initialState); } - LangevinParticleMolecularComponent lpmc = (LangevinParticleMolecularComponent)pmc; - sb.append(" "); - lpmc.writeSite(sb, lpmt.getComponentList().indexOf(lpmc), initialState); } sb.append("\n"); Set> internalLinkSpec = lpmt.getInternalLinkSpec(); @@ -1099,6 +1117,12 @@ public static void writeStateCounters(StringBuilder sb) { } List pmcList = pmt.getComponentList(); for(ParticleMolecularComponent pmc : pmcList) { + if(structuralSiteSet.contains(pmc)) { + continue; // skip structural sites, we don't want to track them with counters + } + if(pmc.getName().equals(SpeciesContextSpec.AnchorSiteString)) { + continue; // skip anchor component, we don't want to track it with counters + } List pcsdList = pmc.getComponentStateDefinitions(); if(pcsdList == null || pcsdList.isEmpty()) { sb.append("'").append(pmt.getName()).append("' : ") From 76ae1b932e1ba1a866a7a083e9fa1cb4df961bf3 Mon Sep 17 00:00:00 2001 From: Dan Vasilescu Date: Mon, 4 May 2026 19:05:33 -0400 Subject: [PATCH 10/16] Structural sites: import / export from / to ssld file. --- .../vcell/mapping/SiteAttributesSpec.java | 6 +- .../vcell/mapping/SpeciesContextSpec.java | 10 +- .../math/LangevinParticleMolecularType.java | 3 +- .../java/org/vcell/model/ssld/SsldUtils.java | 109 ++++++++++++++++-- .../solver/langevin/LangevinLngvWriter.java | 2 - 5 files changed, 109 insertions(+), 21 deletions(-) diff --git a/vcell-core/src/main/java/cbit/vcell/mapping/SiteAttributesSpec.java b/vcell-core/src/main/java/cbit/vcell/mapping/SiteAttributesSpec.java index 6a0897d360..58a12d9fdd 100644 --- a/vcell-core/src/main/java/cbit/vcell/mapping/SiteAttributesSpec.java +++ b/vcell-core/src/main/java/cbit/vcell/mapping/SiteAttributesSpec.java @@ -119,8 +119,9 @@ public int getIndex() { return index; } else if(ln instanceof StructuralSite ss) { int saMapSize = getSpeciesContextSpec().getSiteAttributesMap().size(); + int lastMolecularComponentSiteIndex = saMapSize + 1; // +1 because site indices are 1-based in the .vcell file int idx = SsldUtils.indexOfKey(getSpeciesContextSpec().getStructuralSiteAttributesMap(), ss); - return saMapSize + idx; // structural sites are indexed after the molecular component sites + return lastMolecularComponentSiteIndex + idx; // structural sites are indexed after the molecular component sites } else { throw new RuntimeException("getIndex(): unknown LinkNode type"); } @@ -166,7 +167,8 @@ public void writeSite(StringBuilder sb) { csd = new ComponentStateDefinition(StateZero); } String initialState = csd.getName(); - sb.append("SITE " + (this.getIndex()-1) + " : " + getLocation().getName() + " : Initial State '" + initialState + "'"); + int index = this.getIndex() - 1; + sb.append("SITE " + index + " : " + getLocation().getName() + " : Initial State '" + initialState + "'"); sb.append("\n"); sb.append(" "); this.writeType(sb); // ex: TYPE: Name "Type2" Radius 1.00000 D 1.000 Color LIME STATES "State0" "State1" diff --git a/vcell-core/src/main/java/cbit/vcell/mapping/SpeciesContextSpec.java b/vcell-core/src/main/java/cbit/vcell/mapping/SpeciesContextSpec.java index 6689cce832..b0fa6a9ced 100644 --- a/vcell-core/src/main/java/cbit/vcell/mapping/SpeciesContextSpec.java +++ b/vcell-core/src/main/java/cbit/vcell/mapping/SpeciesContextSpec.java @@ -2585,21 +2585,24 @@ public void writeData(StringBuilder sb){ // SpringSaLaD exporting throw new RuntimeException("Initial concentration must be a number"); } + int siteTypes = componentList.size() + structuralSiteAttributesMap.size(); + int totalSites = siteAttributesMap.size() + structuralSiteAttributesMap.size(); sb.append("MOLECULE: \"" + getSpeciesContext().getName() + "\" " + getSpeciesContext().getStructure().getName() + " Number " + scount + - " Site_Types " + componentList.size() + " Total" + "_Sites " + siteAttributesMap.size() + + " Site_Types " + siteTypes + " Total" + "_Sites " + totalSites + " Total_Links " + internalLinkSet.size() + " is2D " + (dimension == 2 ? true : false)); // TODO: molecule is flat, unrelated to geometry sb.append("\n"); sb.append("{"); sb.append("\n"); - for(Map.Entry entry : siteAttributesMap.entrySet()){ + Map map = getAllSiteAttributes(); + for(Map.Entry entry : map.entrySet()){ SiteAttributesSpec sas = entry.getValue(); sb.append(" "); sas.writeType(sb); // writeMolecularComponent } sb.append("\n"); - for(Map.Entry entry : siteAttributesMap.entrySet()){ + for(Map.Entry entry : map.entrySet()){ SiteAttributesSpec sas = entry.getValue(); sb.append(" "); sas.writeSite(sb); @@ -2628,7 +2631,6 @@ public void writeData(StringBuilder sb){ // SpringSaLaD exporting sb.append("}"); sb.append("\n"); sb.append("\n"); - return; } public String getFilename(){ // SpringSaLaD specific, external file with molecule information diff --git a/vcell-core/src/main/java/cbit/vcell/math/LangevinParticleMolecularType.java b/vcell-core/src/main/java/cbit/vcell/math/LangevinParticleMolecularType.java index c4ce1eaca4..f45a23cf70 100644 --- a/vcell-core/src/main/java/cbit/vcell/math/LangevinParticleMolecularType.java +++ b/vcell-core/src/main/java/cbit/vcell/math/LangevinParticleMolecularType.java @@ -64,7 +64,8 @@ public String getVCML() { } buffer.append("\n"+VCML.EndBlock+"\n"); } - return buffer.toString(); + String ret = buffer.toString(); + return ret; } public void read(CommentStringTokenizer tokens) throws MathFormatException { diff --git a/vcell-core/src/main/java/org/vcell/model/ssld/SsldUtils.java b/vcell-core/src/main/java/org/vcell/model/ssld/SsldUtils.java index 70707e2a73..ae88445581 100644 --- a/vcell-core/src/main/java/org/vcell/model/ssld/SsldUtils.java +++ b/vcell-core/src/main/java/org/vcell/model/ssld/SsldUtils.java @@ -140,6 +140,10 @@ private class Mapping { Map conditionToReactantPattern = new LinkedHashMap<>(); Map conditionToProductPattern = new LinkedHashMap<>(); + // we need lists with all the active sites (that participate in a reaction) + // all the other sites will be considered StructuralSites + Map activeSites = new LinkedHashMap<>(); + Map structuralSites = new LinkedHashMap<>(); public Mapping(SsldModel ssldModel, BioModel bioModel) { this.ssldModel = ssldModel; @@ -328,7 +332,7 @@ ProductPattern getConditionProductPattern(Molecule ssldMolecule) { return conditionToProductPattern.get(ssldMolecule); } - // creation / decay reactions have special needs + // --------------------------------- creation / decay reactions have special needs Map ssldMoleculeToCreationReactions = new LinkedHashMap<>(); // creation only Map ssldMoleculeToDecayReactions = new LinkedHashMap<>(); // destruction only Map moleculeToSourceMap = new LinkedHashMap<>(); // molecules being created need a source @@ -572,6 +576,7 @@ private void importReactionRuleSpecForSsld(ReactionRuleSpec rrs, Mapping m) thro private void importSpeciesContextSpecForSsld(SpeciesContextSpec scs, Mapping m) throws Exception { Map siteAttributesMap = new LinkedHashMap<>(); + Map structuralSiteAttributesMap = new LinkedHashMap<>(); Set internalLinkSet = new LinkedHashSet<>(); SpeciesPattern sp = scs.getSpeciesContext().getSpeciesPattern(); @@ -587,37 +592,67 @@ private void importSpeciesContextSpecForSsld(SpeciesContextSpec scs, Mapping m) MolecularComponent mc = mcp.getMolecularComponent(); SiteType ssldType = m.get(mc); Site ssldSite = m.getSite(ssldType); - String ssldStructure = ssldSite.getLocation(); // structure of site, may differ from molecule structure Structure structure = m.getBioModel().getModel().getStructure(ssldStructure); String colorName = ssldType.getColorName(); NamedColor namedColor = Colors.getColorByName(colorName); Coordinate coordinate = new Coordinate(ssldSite.x, ssldSite.y, ssldSite.z); - SiteAttributesSpec sas = new SiteAttributesSpec(scs, mcp, structure); sas.setCoordinate(coordinate); sas.setColor(namedColor); sas.setRadius(ssldType.getRadius()); sas.setDiffusionRate(ssldType.getD()); - siteAttributesMap.put(mcp, sas); } + Map ssldTypeToStructuralSiteMap = new LinkedHashMap<>(); + for(Map.Entry structuralSitesEntry : m.structuralSites.entrySet()) { + SiteType ssldType = structuralSitesEntry.getKey(); + Molecule ssldMoleculeCandidate = structuralSitesEntry.getValue(); + if(ssldMoleculeCandidate != ssldMolecule) { + continue; // this structural site belongs to a different molecule, skip + } + StructuralSite ss = new StructuralSite(ssldType.getName()); + ssldTypeToStructuralSiteMap.put(ssldType, ss); + Site ssldSite = m.getSite(ssldType); + String ssldStructure = ssldSite.getLocation(); // structure of site, may differ from molecule structure + Structure structure = m.getBioModel().getModel().getStructure(ssldStructure); + String colorName = ssldType.getColorName(); + NamedColor namedColor = Colors.getColorByName(colorName); + Coordinate coordinate = new Coordinate(ssldSite.x, ssldSite.y, ssldSite.z); + SiteAttributesSpec sas = new SiteAttributesSpec(scs, ss, structure); // no component pattern for structural sites + sas.setCoordinate(coordinate); + sas.setColor(namedColor); + sas.setRadius(ssldType.getRadius()); + sas.setDiffusionRate(ssldType.getD()); + structuralSiteAttributesMap.put(ss, sas); // we use null as the key for structural sites + } + for(Link ssldLink : ssldMolecule.getLinkArray()) { // ssldLink -> ssldSite1 -> ssldType1 -> mc1 -> mcp1 -> mils // -> ssldSite2 -> ssldType2 -> mc2 -> mcp2 -> Site ssldSite1 = ssldLink.getSite1(); Site ssldSite2 = ssldLink.getSite2(); SiteType ssldType1 = m.getType(ssldSite1); + LinkNode ln1; + if(m.structuralSites.containsKey(ssldType1)) { + ln1 = ssldTypeToStructuralSiteMap.get(ssldType1); + } else { + MolecularComponent mc1 = m.get(ssldType1); + ln1 = mtp.getMolecularComponentPattern(mc1); + } SiteType ssldType2 = m.getType(ssldSite2); - MolecularComponent mc1 = m.get(ssldType1); - MolecularComponent mc2 = m.get(ssldType2); - MolecularComponentPattern mcp1 = mtp.getMolecularComponentPattern(mc1); - MolecularComponentPattern mcp2 = mtp.getMolecularComponentPattern(mc2); - - MolecularInternalLinkSpec mils = new MolecularInternalLinkSpec(scs, mcp1, mcp2); + LinkNode ln2; + if(m.structuralSites.containsKey(ssldType2)) { + ln2 = ssldTypeToStructuralSiteMap.get(ssldType2); + } else { + MolecularComponent mc2 = m.get(ssldType2); + ln2 = mtp.getMolecularComponentPattern(mc2); + } + MolecularInternalLinkSpec mils = new MolecularInternalLinkSpec(scs, ln1, ln2); internalLinkSet.add(mils); } scs.setSiteAttributesMap(siteAttributesMap); + scs.setStructuralSiteAttributesMap(structuralSiteAttributesMap); scs.setInternalLinkSet(internalLinkSet); SpeciesContextSpec.SpeciesContextSpecParameter countParam = scs.getInitialCountParameter(); @@ -657,25 +692,72 @@ public Mapping importPhysiologyFromSsld(SsldModel ssldModel) throws Exception { newstructures.toArray(structarray); model.setStructures(structarray); + // ----------- Active sites (that participate in some reaction) - any other site will become a StructureSite + for(AllostericReaction ssldAllostericReaction : ssldModel.getAllostericReactions()) { + Molecule ssldTransitionMolecule = ssldAllostericReaction.getMolecule(); + Site ssldAllostericSite = ssldAllostericReaction.getAllostericSite(); // allosteric condition + Site ssldTransitionSite = ssldAllostericReaction.getSite(); // transitioning site + m.activeSites.put(ssldAllostericSite.getType(), ssldTransitionMolecule); + m.activeSites.put(ssldTransitionSite.getType(), ssldTransitionMolecule); + } + for(BindingReaction ssldBindingReaction : ssldModel.getBindingReactions()) { + Molecule[] ssldMolecules = ssldBindingReaction.getMolecules(); + SiteType[] siteTypes = ssldBindingReaction.getTypes(); + for(int i=0; i ssldSiteList = ssldMolecule.getSiteArray(); for(Site ssldSite : ssldSiteList) { SiteType ssldType = ssldSite.getType(); + if(m.structuralSites.containsKey(ssldType)) { + continue; // this is a structural site, we won't make a component for it + } MolecularComponent mc = m.get(ssldType); State ssldState = ssldSite.getInitialState(); ComponentStateDefinition csd = mc.getComponentStateDefinition(ssldState.getName()); diff --git a/vcell-core/src/main/java/org/vcell/solver/langevin/LangevinLngvWriter.java b/vcell-core/src/main/java/org/vcell/solver/langevin/LangevinLngvWriter.java index df33d1ac4b..5224dedcd1 100644 --- a/vcell-core/src/main/java/org/vcell/solver/langevin/LangevinLngvWriter.java +++ b/vcell-core/src/main/java/org/vcell/solver/langevin/LangevinLngvWriter.java @@ -987,8 +987,6 @@ private static void writeSpeciesInfo(StringBuilder sb) { sb.append("\n"); sb.append("\n"); } - System.out.println(sb.toString()); - return; } /* From 87f666df030f8b0a2f9acc3df5e936d8ed0b18e9 Mon Sep 17 00:00:00 2001 From: Dan Vasilescu Date: Wed, 6 May 2026 15:45:13 -0400 Subject: [PATCH 11/16] Structural sites: XmlReader, XMLProducer --- .../vcell/mapping/SpeciesContextSpec.java | 48 --------- .../src/main/java/cbit/vcell/xml/XMLTags.java | 1 - .../main/java/cbit/vcell/xml/XmlReader.java | 98 ++++++++++--------- .../main/java/cbit/vcell/xml/Xmlproducer.java | 30 +++++- .../cbit/vcell/modeldb/SimContextTable.java | 2 +- 5 files changed, 79 insertions(+), 100 deletions(-) diff --git a/vcell-core/src/main/java/cbit/vcell/mapping/SpeciesContextSpec.java b/vcell-core/src/main/java/cbit/vcell/mapping/SpeciesContextSpec.java index b0fa6a9ced..62e62b6a76 100644 --- a/vcell-core/src/main/java/cbit/vcell/mapping/SpeciesContextSpec.java +++ b/vcell-core/src/main/java/cbit/vcell/mapping/SpeciesContextSpec.java @@ -1853,54 +1853,6 @@ public SpeciesContext getSpeciesContext(){ return speciesContext; } - - /** - * This method was created by a SmartGuide. - * - * @return java.lang.String - */ - public String getVCML(){ - StringBuffer buffer = new StringBuffer(); - buffer.append("\t" + VCMODL.SpeciesContextSpec + " " + getSpeciesContext().getName() + " {\n"); - buffer.append("\t\t" + VCMODL.ForceConstant + " " + isConstant() + "\n"); - buffer.append("\t\t" + VCMODL.EnableDiffusion + " " + isDiffusing() + "\n"); - Expression init = getInitialConditionParameter().getExpression(); - if(init != null){ - buffer.append("\t\t" + VCMODL.InitialConcentration + " " + init.toString() + ";\n"); - } - Expression diffRate = getDiffusionParameter().getExpression(); - if(diffRate != null){ - buffer.append("\t\t" + VCMODL.DiffusionRate + " " + diffRate.toString() + ";\n"); - } - // - // write BoundaryConditions - // - for(int i = BoundaryLocation.FIRST; i <= BoundaryLocation.LAST; i++){ - BoundaryLocation bl = BoundaryLocation.fromDirection(i); - Expression boundExp = getParameterFromRole(getRole(bl)).getExpression(); - if(boundExp != null){ - buffer.append("\t\t" + VCMODL.BoundaryCondition + " " + bl.toString() + " " + boundExp.toString() + "\n"); - } - } - // write velocity x,y,z expressions - Expression vel_x = getVelocityXParameter().getExpression(); - if(vel_x != null){ - buffer.append("\t\t" + VCMODL.VelocityX + " " + vel_x.toString() + ";\n"); - } - Expression vel_y = getVelocityYParameter().getExpression(); - if(vel_y != null){ - buffer.append("\t\t" + VCMODL.VelocityY + " " + vel_y.toString() + ";\n"); - } - Expression vel_z = getVelocityZParameter().getExpression(); - if(vel_z != null){ - buffer.append("\t\t" + VCMODL.VelocityZ + " " + vel_z.toString() + ";\n"); - } - - buffer.append("\t}\n"); - return buffer.toString(); - } - - /** * Accessor for the vetoPropertyChange field. */ diff --git a/vcell-core/src/main/java/cbit/vcell/xml/XMLTags.java b/vcell-core/src/main/java/cbit/vcell/xml/XMLTags.java index 03f80a7559..44f1e9d6bf 100644 --- a/vcell-core/src/main/java/cbit/vcell/xml/XMLTags.java +++ b/vcell-core/src/main/java/cbit/vcell/xml/XMLTags.java @@ -886,7 +886,6 @@ public class XMLTags { public final static String ExtrapolationMethodTag = "ExtrapolationMethod"; // SpringSaLaD tags - public final static String SiteAttributesMapTag = "SiteAttributesMap"; public final static String SiteAttributesSpecTag = "SiteAttributesSpec"; public final static String StructuralSiteAttributesSpecTag = "StructuralSiteAttributesSpec"; public final static String SiteRefAttrTag = "SiteRef"; diff --git a/vcell-core/src/main/java/cbit/vcell/xml/XmlReader.java b/vcell-core/src/main/java/cbit/vcell/xml/XmlReader.java index 4268115224..bacb99ac5f 100644 --- a/vcell-core/src/main/java/cbit/vcell/xml/XmlReader.java +++ b/vcell-core/src/main/java/cbit/vcell/xml/XmlReader.java @@ -16,6 +16,7 @@ import java.util.*; import java.util.function.Consumer; +import cbit.vcell.mapping.*; import cbit.vcell.model.*; import cbit.vcell.solver.*; import org.jdom2.Attribute; @@ -26,15 +27,8 @@ import org.vcell.chombo.RefinementRoi; import org.vcell.chombo.RefinementRoi.RoiType; import org.vcell.chombo.TimeInterval; -import org.vcell.model.rbm.ComponentStateDefinition; -import org.vcell.model.rbm.ComponentStatePattern; -import org.vcell.model.rbm.MolecularComponent; -import org.vcell.model.rbm.MolecularComponentPattern; +import org.vcell.model.rbm.*; import org.vcell.model.rbm.MolecularComponentPattern.BondType; -import org.vcell.model.rbm.MolecularType; -import org.vcell.model.rbm.MolecularTypePattern; -import org.vcell.model.rbm.NetworkConstraints; -import org.vcell.model.rbm.SpeciesPattern; import org.vcell.model.rbm.SpeciesPattern.Bond; import org.vcell.pathway.PathwayModel; import org.vcell.pathway.persistence.PathwayReaderBiopax3; @@ -116,39 +110,18 @@ import cbit.vcell.geometry.surface.GeometrySurfaceDescription; import cbit.vcell.geometry.surface.SurfaceGeometricRegion; import cbit.vcell.geometry.surface.VolumeGeometricRegion; -import cbit.vcell.mapping.AssignmentRule; -import cbit.vcell.mapping.BioEvent; import cbit.vcell.mapping.BioEvent.BioEventParameterType; import cbit.vcell.mapping.BioEvent.TriggerType; -import cbit.vcell.mapping.CurrentDensityClampStimulus; -import cbit.vcell.mapping.ElectricalStimulus; -import cbit.vcell.mapping.Electrode; -import cbit.vcell.mapping.FeatureMapping; -import cbit.vcell.mapping.MappingException; -import cbit.vcell.mapping.MembraneMapping; -import cbit.vcell.mapping.MicroscopeMeasurement; import cbit.vcell.mapping.MicroscopeMeasurement.ConvolutionKernel; import cbit.vcell.mapping.MicroscopeMeasurement.GaussianConvolutionKernel; import cbit.vcell.mapping.MicroscopeMeasurement.ProjectionZKernel; -import cbit.vcell.mapping.MolecularInternalLinkSpec; -import cbit.vcell.mapping.ParameterContext; import cbit.vcell.mapping.ParameterContext.LocalParameter; import cbit.vcell.mapping.ParameterContext.ParameterRoleEnum; -import cbit.vcell.mapping.RateRule; -import cbit.vcell.mapping.ReactionContext; -import cbit.vcell.mapping.ReactionRuleSpec; import cbit.vcell.mapping.ReactionRuleSpec.ReactionRuleMappingType; import cbit.vcell.mapping.ReactionRuleSpec.Subtype; import cbit.vcell.mapping.ReactionRuleSpec.TransitionCondition; -import cbit.vcell.mapping.ReactionSpec; -import cbit.vcell.mapping.SimulationContext; -import cbit.vcell.mapping.SiteAttributesSpec; import cbit.vcell.mapping.SimulationContext.Application; import cbit.vcell.mapping.SimulationContext.SimulationContextParameter; -import cbit.vcell.mapping.SpeciesContextSpec; -import cbit.vcell.mapping.StructureMapping; -import cbit.vcell.mapping.TotalCurrentClampStimulus; -import cbit.vcell.mapping.VoltageClampStimulus; import cbit.vcell.mapping.spatial.PointObject; import cbit.vcell.mapping.spatial.SpatialObject; import cbit.vcell.mapping.spatial.SpatialObject.QuantityCategory; @@ -7178,25 +7151,18 @@ private void getSpeciesContextSpecs(List scsChildren, ReactionContext r } MolecularTypePattern mtp = sp.getMolecularTypePatterns().get(0); - // all SpeciesContextSpec objects now have an internalLinkSet, if the app is not Springsalad it will be empty - Set internalLinkSet = new LinkedHashSet<>(); - List linkSpecs = scsElement.getChildren(XMLTags.InternalLinkSpecTag, vcNamespace); - for(Element linkSpec : linkSpecs){ // should map to Math's ParticleMolecularType (n -> 1) - String oneName = unMangle(linkSpec.getAttributeValue(XMLTags.SiteOneRefAttrTag)); - String twoName = unMangle(linkSpec.getAttributeValue(XMLTags.SiteTwoRefAttrTag)); - MolecularComponentPattern one = mtp.getMolecularComponentPattern(oneName); - MolecularComponentPattern two = mtp.getMolecularComponentPattern(twoName); - MolecularInternalLinkSpec internalLink = new MolecularInternalLinkSpec(specspec, one, two); - internalLinkSet.add(internalLink); - } - specspec.setInternalLinkSet(internalLinkSet); - + // we maintain a map between the names of the LinkNodes (siteRef) which may be MolecularComponentPatterns or + // StructuralSites, so that we could get the right instances once we'll start parsing the InternalLinkSpecs. + Map refToLinkNodeMap = new LinkedHashMap<>(); // all SpeciesContextSpec objects now have a siteAttributesMap, if the app is not Springsalad it will be empty Map siteAttributesMap = new LinkedHashMap<>(); - List attributeSpecs = scsElement.getChildren(XMLTags.SiteAttributesSpecTag, vcNamespace); - for(Element attributeSpec : attributeSpecs){ // should map to Math's ParticleJumpProcess (1 -> 1) - String siteRef = attributeSpec.getAttributeValue(XMLTags.SiteRefAttrTag); + List saSpecs = scsElement.getChildren(XMLTags.SiteAttributesSpecTag, vcNamespace); + for(Element attributeSpec : saSpecs){ // should map to Math's ParticleJumpProcess (1 -> 1) String moleculeRef = attributeSpec.getAttributeValue(XMLTags.MoleculeRefAttrTag); + if(!mtp.getMolecularType().getName().equals(moleculeRef)) { // sanity check, we need the right mtp for the mcp instance + throw new XmlParseException("Bad SiteAttributeSpec molecular type for " + specspec.getDisplayName()); + } + String siteRef = attributeSpec.getAttributeValue(XMLTags.SiteRefAttrTag); double radius = Double.parseDouble(attributeSpec.getAttributeValue(XMLTags.SiteRadiusAttrTag)); double diff = Double.parseDouble(attributeSpec.getAttributeValue(XMLTags.SiteDiffusionAttrTag)); String locationName = attributeSpec.getAttributeValue(XMLTags.SiteLocationRefAttrTag); @@ -7206,14 +7172,50 @@ private void getSpeciesContextSpecs(List scsChildren, ReactionContext r double z = Double.parseDouble(attributeSpec.getAttributeValue(XMLTags.SiteCoordZAttrTag)); Coordinate coordinate = new Coordinate(x, y, z); MolecularComponentPattern mcp = mtp.getMolecularComponentPattern(siteRef); - if(!mtp.getMolecularType().getName().equals(moleculeRef)){ - throw new XmlParseException("Bad SiteAttributeSpec molecular type for " + specspec.getDisplayName()); - } + refToLinkNodeMap.put(siteRef, mcp); Structure structure = model.getStructure(locationName); SiteAttributesSpec sas = new SiteAttributesSpec(specspec, mcp, radius, diff, structure, coordinate, color); siteAttributesMap.put(mcp, sas); } specspec.setSiteAttributesMap(siteAttributesMap); + + // all SpeciesContextSpec objects now have a structuralSiteAttributesMap, if the app is not Springsalad it will be empty + Map structuralSiteAttributesMap = new LinkedHashMap<>(); + List ssaSpecs = scsElement.getChildren(XMLTags.StructuralSiteAttributesSpecTag, vcNamespace); + for(Element attributeSpec : ssaSpecs){ // should map to Math's ParticleJumpProcess (1 -> 1) + String moleculeRef = attributeSpec.getAttributeValue(XMLTags.MoleculeRefAttrTag); + if(!mtp.getMolecularType().getName().equals(moleculeRef)) { // sanity check, we don't need it for StructuralSites + throw new XmlParseException("Bad SiteAttributeSpec molecular type for " + specspec.getDisplayName()); + } + String siteRef = attributeSpec.getAttributeValue(XMLTags.SiteRefAttrTag); + double radius = Double.parseDouble(attributeSpec.getAttributeValue(XMLTags.SiteRadiusAttrTag)); + double diff = Double.parseDouble(attributeSpec.getAttributeValue(XMLTags.SiteDiffusionAttrTag)); + String locationName = attributeSpec.getAttributeValue(XMLTags.SiteLocationRefAttrTag); + NamedColor color = Colors.getColorByName(attributeSpec.getAttributeValue(XMLTags.SiteColorAttrTag)); + double x = Double.parseDouble(attributeSpec.getAttributeValue(XMLTags.SiteCoordXAttrTag)); + double y = Double.parseDouble(attributeSpec.getAttributeValue(XMLTags.SiteCoordYAttrTag)); + double z = Double.parseDouble(attributeSpec.getAttributeValue(XMLTags.SiteCoordZAttrTag)); + Coordinate coordinate = new Coordinate(x, y, z); + StructuralSite mcp = new StructuralSite(siteRef); + refToLinkNodeMap.put(siteRef, mcp); + Structure structure = model.getStructure(locationName); + SiteAttributesSpec sas = new SiteAttributesSpec(specspec, mcp, radius, diff, structure, coordinate, color); + structuralSiteAttributesMap.put(mcp, sas); + } + specspec.setStructuralSiteAttributesMap(structuralSiteAttributesMap); + + // all SpeciesContextSpec objects now have an internalLinkSet, if the app is not Springsalad it will be empty + Set internalLinkSet = new LinkedHashSet<>(); + List linkSpecs = scsElement.getChildren(XMLTags.InternalLinkSpecTag, vcNamespace); + for(Element linkSpec : linkSpecs){ // should map to Math's ParticleMolecularType (n -> 1) + String oneName = unMangle(linkSpec.getAttributeValue(XMLTags.SiteOneRefAttrTag)); + String twoName = unMangle(linkSpec.getAttributeValue(XMLTags.SiteTwoRefAttrTag)); + LinkNode one = refToLinkNodeMap.get(oneName); + LinkNode two = refToLinkNodeMap.get(twoName); + MolecularInternalLinkSpec internalLink = new MolecularInternalLinkSpec(specspec, one, two); + internalLinkSet.add(internalLink); + } + specspec.setInternalLinkSet(internalLinkSet); } } diff --git a/vcell-core/src/main/java/cbit/vcell/xml/Xmlproducer.java b/vcell-core/src/main/java/cbit/vcell/xml/Xmlproducer.java index bd76f73483..46bc8fa984 100644 --- a/vcell-core/src/main/java/cbit/vcell/xml/Xmlproducer.java +++ b/vcell-core/src/main/java/cbit/vcell/xml/Xmlproducer.java @@ -1748,6 +1748,30 @@ else if(initAmt != null) sasElement.setAttribute(XMLTags.SiteColorAttrTag, sas.getColor().getName()); speciesContextSpecElement.addContent(sasElement); } + for (Entry entry : param.getStructuralSiteAttributesMap().entrySet()) { + SpeciesContext sc = param.getSpeciesContext(); + SpeciesPattern sp = sc.getSpeciesPattern(); + if (sp == null || sp.getMolecularTypePatterns().size() != 1) { + break; // the species pattern must refer to exactly one molecule, links are intramollecular only + // throw new IllegalArgumentException("The species pattern must contain exactly one molecule."); + } + MolecularTypePattern mtp = sp.getMolecularTypePatterns().get(0); // the one and only + MolecularType mt = mtp.getMolecularType(); + + StructuralSite ss = entry.getKey(); + SiteAttributesSpec sas = entry.getValue(); + Element sasElement = new Element(XMLTags.StructuralSiteAttributesSpecTag); + sasElement.setAttribute(XMLTags.SiteRefAttrTag, ss.getName()); + sasElement.setAttribute(XMLTags.MoleculeRefAttrTag, mt.getName()); + sasElement.setAttribute(XMLTags.SiteLocationRefAttrTag, sas.getLocation().getName()); + sasElement.setAttribute(XMLTags.SiteCoordXAttrTag, Double.toString(sas.getCoordinate().getX())); + sasElement.setAttribute(XMLTags.SiteCoordYAttrTag, Double.toString(sas.getCoordinate().getY())); + sasElement.setAttribute(XMLTags.SiteCoordZAttrTag, Double.toString(sas.getCoordinate().getZ())); + sasElement.setAttribute(XMLTags.SiteRadiusAttrTag, Double.toString(sas.getRadius())); + sasElement.setAttribute(XMLTags.SiteDiffusionAttrTag, Double.toString(sas.getDiffusionRate())); + sasElement.setAttribute(XMLTags.SiteColorAttrTag, sas.getColor().getName()); + speciesContextSpecElement.addContent(sasElement); + } } // XMLOutputter outp = new XMLOutputter(Format.getPrettyFormat()); // String sout = outp.outputString(speciesContextSpecElement); @@ -3066,8 +3090,10 @@ private Element getXML(ParticleMolecularComponent param) { e.setAttribute(XMLTags.ParticleMolecularComponentCoordZAttrTag, Double.toString(lParam.getCoordinate().getZ())); e.setAttribute(XMLTags.ParticleMolecularComponentColorTag, lParam.getColor().getName()); } - for (ParticleComponentStateDefinition pp : param.getComponentStateDefinitions()){ - e.addContent(getXML(pp)); + if(param.getComponentStateDefinitions() != null) { + for (ParticleComponentStateDefinition pp : param.getComponentStateDefinitions()) { + e.addContent(getXML(pp)); + } } return e; } diff --git a/vcell-server/src/main/java/cbit/vcell/modeldb/SimContextTable.java b/vcell-server/src/main/java/cbit/vcell/modeldb/SimContextTable.java index 5796405b79..e1141aa6bf 100644 --- a/vcell-server/src/main/java/cbit/vcell/modeldb/SimContextTable.java +++ b/vcell-server/src/main/java/cbit/vcell/modeldb/SimContextTable.java @@ -146,7 +146,7 @@ public SimulationContext getSimContext(QueryHashtable dbc, Connection con, Datab Double characteristicSize = null; BigDecimal size = rset.getBigDecimal(charSize.toString()); if (!rset.wasNull() && size!=null){ - characteristicSize = new Double(size.doubleValue()); + characteristicSize = size.doubleValue(); } // From 0d2ec6a7068f805482b32d5da12e5352df524c20 Mon Sep 17 00:00:00 2001 From: Dan Vasilescu Date: Wed, 6 May 2026 18:13:30 -0400 Subject: [PATCH 12/16] Structural sites: read from, write to the DB --- .../vcell/mapping/SiteAttributesSpec.java | 32 ++++++ .../vcell/mapping/SpeciesContextSpec.java | 47 ++++++++- .../vcell/util/springsalad/NamedColor.java | 28 +++++- .../modeldb/SimulationContextDbDriver.java | 20 +++- .../modeldb/SpeciesContextSpecTable.java | 97 +++++++++++++++++-- 5 files changed, 208 insertions(+), 16 deletions(-) diff --git a/vcell-core/src/main/java/cbit/vcell/mapping/SiteAttributesSpec.java b/vcell-core/src/main/java/cbit/vcell/mapping/SiteAttributesSpec.java index 58a12d9fdd..f0cfa18a7f 100644 --- a/vcell-core/src/main/java/cbit/vcell/mapping/SiteAttributesSpec.java +++ b/vcell-core/src/main/java/cbit/vcell/mapping/SiteAttributesSpec.java @@ -210,6 +210,38 @@ public boolean compareEqual(Matchable obj) { } return true; } + public boolean compareShallow(Matchable obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof SiteAttributesSpec)) { + return false; + } + SiteAttributesSpec candidate = (SiteAttributesSpec) obj; + + if(!fieldSpeciesContextSpec.getSpeciesContext().getName().contentEquals(candidate.getSpeciesContextSpec().getSpeciesContext().getName())) { + return false; + } + if(!fieldLinkNode.getName().contentEquals(candidate.getLinkNode().getName())) { + return false; + } + if(fieldRadius != candidate.getRadius()) { + return false; + } + if(fieldDiffusionRate != candidate.getDiffusionRate()) { + return false; + } + if(!fieldLocation.compareEqual(candidate.getLocation())) { + return false; + } + if(!fieldCoordinate.compareEqual(candidate.getCoordinate())) { + return false; + } + if(fieldColor != candidate.getColor()) { + return false; + } + return true; + } public double computeReactionRadius() { // in langevin solver notation is R // assumes radius in nanometers diff --git a/vcell-core/src/main/java/cbit/vcell/mapping/SpeciesContextSpec.java b/vcell-core/src/main/java/cbit/vcell/mapping/SpeciesContextSpec.java index 62e62b6a76..0b67b63103 100644 --- a/vcell-core/src/main/java/cbit/vcell/mapping/SpeciesContextSpec.java +++ b/vcell-core/src/main/java/cbit/vcell/mapping/SpeciesContextSpec.java @@ -1071,7 +1071,16 @@ public boolean compareEqual(Matchable object){ return false; } for(Map.Entry entry : siteAttributesMap.entrySet()) { - if (!containsIsomorph(entry, siteAttributesMap)) { + if (!containsIsomorph(entry, scs.siteAttributesMap)) { + return false; + } + } + + if(structuralSiteAttributesMap.size() != scs.structuralSiteAttributesMap.size()) { // springsalad SiteAttributesSpec map + return false; + } + for(Map.Entry entry : structuralSiteAttributesMap.entrySet()) { + if (!containsIsomorph1(entry, scs.structuralSiteAttributesMap)) { return false; } } @@ -1079,6 +1088,27 @@ public boolean compareEqual(Matchable object){ return true; } + private static boolean containsIsomorph1(Map.Entry ourEntry, Map theirSsaMap) { + StructuralSite ourSs = ourEntry.getKey(); + SiteAttributesSpec ourSas = ourEntry.getValue(); + for(Map.Entry theirEntry : theirSsaMap.entrySet()) { + StructuralSite theirSs = theirEntry.getKey(); + SiteAttributesSpec theirSas = theirEntry.getValue(); + boolean foundss = false; + boolean foundSas = false; + if (ourSs.compareEqual(theirSs)) { + foundss = true; // note that for the ss we only compare the name + } + if (ourSas.compareShallow(theirSas)) { + foundSas = true; + } + if(foundss && foundSas) { + return true; + } + } + return false; + } + private static boolean containsIsomorph(MolecularInternalLinkSpec ourMils, Set theirMilsSet) { for(MolecularInternalLinkSpec theirMils : theirMilsSet) { if(ourMils.compareEqual(theirMils)) { @@ -1087,15 +1117,24 @@ private static boolean containsIsomorph(MolecularInternalLinkSpec ourMils, Set ourEntry, Map theirSasMap) { + private static boolean containsIsomorph(Map.Entry ourEntry, Map theirSaMap) { MolecularComponentPattern ourMcp = ourEntry.getKey(); SiteAttributesSpec ourSas = ourEntry.getValue(); - for(Map.Entry theirEntry : theirSasMap.entrySet()) { + for(Map.Entry theirEntry : theirSaMap.entrySet()) { MolecularComponentPattern theirMcp = theirEntry.getKey(); SiteAttributesSpec theirSas = theirEntry.getValue(); - // TODO: infinite loop if we compare the sas here, because that will compare the scs, which will again compare the sas, aso + boolean foundMcp = false; + boolean foundSas = false; if(ourMcp.compareEqual(theirMcp)/* && ourSas.compareEqual(theirSas)*/) { // note that for the mcp we only compare the bond type, not the bond (which is mtp attribute) + foundMcp = true; + } + // infinite loop if we compare the sas here, because that will compare the scs, which will again compare the sas, aso + // use compareShallow instead + if(ourSas.compareShallow(theirSas)) { + foundSas = true; + } + if(foundMcp && foundSas) { return true; } } diff --git a/vcell-core/src/main/java/org/vcell/util/springsalad/NamedColor.java b/vcell-core/src/main/java/org/vcell/util/springsalad/NamedColor.java index 2a30a296c0..a28287b43d 100644 --- a/vcell-core/src/main/java/org/vcell/util/springsalad/NamedColor.java +++ b/vcell-core/src/main/java/org/vcell/util/springsalad/NamedColor.java @@ -11,6 +11,7 @@ package org.vcell.util.springsalad; import cbit.vcell.graph.GraphConstants; +import org.vcell.util.Matchable; import java.awt.Color; import java.io.Serializable; @@ -21,7 +22,7 @@ * For vcell-style colors see GraphConstants */ @SuppressWarnings("serial") -public class NamedColor implements Serializable { +public class NamedColor implements Serializable, Matchable { private final Color color; private final String name; @@ -66,6 +67,31 @@ public static String getHex(Color c) { return hex; } + public boolean compareEqual(Matchable obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof NamedColor)) { + return false; + } + NamedColor other = (NamedColor) obj; + if (name == null) { + if (other.name != null) { + return false; + } + } else if (!name.equals(other.name)) { + return false; + } + if (color == null) { + if (other.color != null) { + return false; + } + } else if (!color.equals(other.color)) { + return false; + } + return true; + } + public static void main(String[] args) { Color red = Color.decode("#FF0000"); diff --git a/vcell-server/src/main/java/cbit/vcell/modeldb/SimulationContextDbDriver.java b/vcell-server/src/main/java/cbit/vcell/modeldb/SimulationContextDbDriver.java index ee508258e3..93a1ae1b6a 100644 --- a/vcell-server/src/main/java/cbit/vcell/modeldb/SimulationContextDbDriver.java +++ b/vcell-server/src/main/java/cbit/vcell/modeldb/SimulationContextDbDriver.java @@ -263,11 +263,14 @@ private void assignSpeciesContextSpecsSQL(Connection con, KeyValue simContextKey if (rset.wasNull()){ internalLinkSetString = null; } - String siteAttributesMapString = rset.getString((speciesContextSpecTable.siteAttributesSpecs.toString())); if (rset.wasNull()){ siteAttributesMapString = null; } + String structuralSiteAttributesMapString = rset.getString((speciesContextSpecTable.structuralSiteAttributesSpecs.toString())); + if (rset.wasNull()){ + structuralSiteAttributesMapString = null; + } // SpeciesContextSpec speciesContextSpecs[] = simContext.getReactionContext().getSpeciesContextSpecs(); @@ -346,14 +349,21 @@ private void assignSpeciesContextSpecsSQL(Connection con, KeyValue simContextKey boolean bForceContinuous = (value == 1) ? true : false; scs.setForceContinuous(bForceContinuous); } - if(internalLinkSetString != null) { - Set ilSet = SpeciesContextSpecTable.readInternalLinksSQL(scs, internalLinkSetString); - scs.setInternalLinkSet(ilSet); - } if(siteAttributesMapString != null) { Map saMap = SpeciesContextSpecTable.readSiteAttributesSQL(scs, siteAttributesMapString); scs.setSiteAttributesMap(saMap); } + if(structuralSiteAttributesMapString != null) { + Map ssaMap = SpeciesContextSpecTable.readStructuralSiteAttributesSQL(scs, structuralSiteAttributesMapString); + scs.setStructuralSiteAttributesMap(ssaMap); + } + // WARNING: the order is critical here, the structural sites must be created first + // because the internal link spec needs the structural sites instances to be already + // created and set in the SpeciesContextSpec + if(internalLinkSetString != null) { + Set ilSet = SpeciesContextSpecTable.readInternalLinksSQL(scs, internalLinkSetString); + scs.setInternalLinkSet(ilSet); + } } catch(Exception e){ throw new DataAccessException("Error setting SpeciesContextSpec info for SimulationContext:" + simContext.getVersion().getName() + " id=" + simContextKey); } diff --git a/vcell-server/src/main/java/cbit/vcell/modeldb/SpeciesContextSpecTable.java b/vcell-server/src/main/java/cbit/vcell/modeldb/SpeciesContextSpecTable.java index b1872e93c4..d61ab9ee69 100644 --- a/vcell-server/src/main/java/cbit/vcell/modeldb/SpeciesContextSpecTable.java +++ b/vcell-server/src/main/java/cbit/vcell/modeldb/SpeciesContextSpecTable.java @@ -12,8 +12,10 @@ import cbit.vcell.mapping.MolecularInternalLinkSpec; import cbit.vcell.mapping.SiteAttributesSpec; +import cbit.vcell.mapping.StructuralSite; import cbit.vcell.model.SpeciesContext; import cbit.vcell.model.Structure; +import org.vcell.model.rbm.LinkNode; import org.vcell.model.rbm.MolecularComponentPattern; import org.vcell.model.rbm.MolecularTypePattern; import org.vcell.model.rbm.SpeciesPattern; @@ -62,10 +64,12 @@ public class SpeciesContextSpecTable extends cbit.sql.Table { public final Field bForceContinuous = new Field("bForceContinuous", SQLDataType.integer, ""); public final Field internalLinks = new Field("internalLinks", SQLDataType.varchar_4000, ""); public final Field siteAttributesSpecs = new Field("siteAttributesSpecs",SQLDataType.varchar_4000, ""); + public final Field structuralSiteAttributesSpecs = new Field("structuralSiteAttributesSpecs",SQLDataType.varchar_4000, ""); private final Field fields[] = {specContextRef,simContextRef,bEnableDif,bForceConst,bForceIndep,initCondExp,diffRateExp, boundaryXmExp,boundaryXpExp,boundaryYmExp,boundaryYpExp,boundaryZmExp,boundaryZpExp,initCondCountExp, - velocityXExp, velocityYExp, velocityZExp, bWellMixed, bForceContinuous, internalLinks, siteAttributesSpecs}; + velocityXExp, velocityYExp, velocityZExp, bWellMixed, bForceContinuous, + internalLinks, siteAttributesSpecs, structuralSiteAttributesSpecs}; public static final SpeciesContextSpecTable table = new SpeciesContextSpecTable(); /** @@ -176,9 +180,15 @@ public String getSQLValueList(KeyValue Key, KeyValue simContextKey, SpeciesConte buffer.append("'" + getInternalLinksSQL(speciesContextSpec) + "'" + ","); } if(speciesContextSpec.getSiteAttributesMap() == null || speciesContextSpec.getSiteAttributesMap().size() == 0) { + buffer.append(" NULL " + ","); + } else { + buffer.append("'" + getSiteAttributesSQL(speciesContextSpec) + "'" + ","); + } + // ATTENTION: the last entry ends with a ")" instead of a "," because it needs to close the parenthesis opened at the beginning of this method + if(speciesContextSpec.getStructuralSiteAttributesMap() == null || speciesContextSpec.getStructuralSiteAttributesMap().size() == 0) { buffer.append(" NULL " + ")"); } else { - buffer.append("'" + getSiteAttributesSQL(speciesContextSpec) + "'" + ")"); + buffer.append("'" + getStructuralSiteAttributesSQL(speciesContextSpec) + "'" + ")"); } return buffer.toString(); } @@ -199,6 +209,22 @@ static String getSiteAttributesSQL(SpeciesContextSpec scs) { } return sb.toString(); } + static String getStructuralSiteAttributesSQL(SpeciesContextSpec scs) { + StringBuilder sb = new StringBuilder(); + for(Map.Entry entry : scs.getStructuralSiteAttributesMap().entrySet()) { + SiteAttributesSpec sas = entry.getValue(); + sb.append(sas.getLinkNode().getName() + ","); + sb.append(sas.getLocation().getName() + ","); + sb.append(sas.getRadius() + ","); + sb.append(sas.getDiffusionRate() +","); + sb.append(sas.getCoordinate().getX() + ","); + sb.append(sas.getCoordinate().getY() + ","); + sb.append(sas.getCoordinate().getZ() + ","); + sb.append(sas.getColor().getName()); + sb.append(";"); + } + return sb.toString(); + } static Map readSiteAttributesSQL(SpeciesContextSpec scs, String siteAttributesMapString) { Map saMap = new LinkedHashMap<>(); @@ -248,6 +274,47 @@ static Map readSiteAttributesSQL( } return saMap; // may be empty but not null } + static Map readStructuralSiteAttributesSQL(SpeciesContextSpec scs, String structuralSiteAttributesMapString) { + Map ssaMap = new LinkedHashMap<>(); + if (structuralSiteAttributesMapString.isEmpty()) { + return ssaMap; + } + StringTokenizer sat = new StringTokenizer(structuralSiteAttributesMapString, ";"); + if (sat.countTokens() == 0) { + return ssaMap; + } + + while(sat.hasMoreTokens()) { + String saString = sat.nextToken(); + StringTokenizer tokenizer = new StringTokenizer(saString, ","); + if (tokenizer.countTokens() != 8) { + return ssaMap; + } + while(tokenizer.hasMoreTokens()) { + String attribute = tokenizer.nextToken(); + StructuralSite ss = new StructuralSite(attribute); + attribute = tokenizer.nextToken(); + Structure structure = scs.getSimulationContext().getModel().getStructure(attribute); + attribute = tokenizer.nextToken(); + double radius = Double.parseDouble(attribute); + attribute = tokenizer.nextToken(); + double diffRate = Double.parseDouble(attribute); + attribute = tokenizer.nextToken(); + double x = Double.parseDouble(attribute); + attribute = tokenizer.nextToken(); + double y = Double.parseDouble(attribute); + attribute = tokenizer.nextToken(); + double z = Double.parseDouble(attribute); + Coordinate coordinate = new Coordinate(x,y,z); + attribute = tokenizer.nextToken(); + NamedColor color = Colors.getColorByName(attribute); + SiteAttributesSpec sas = new SiteAttributesSpec(scs, ss, radius, diffRate, structure, coordinate, color); + ssaMap.put(ss, sas); + } + } + return ssaMap; // may be empty but not null + } + static String getInternalLinksSQL(SpeciesContextSpec scs) { StringBuffer sb = new StringBuffer(); for( MolecularInternalLinkSpec mils : scs.internalLinkSet) { @@ -268,6 +335,10 @@ static Set readInternalLinksSQL(SpeciesContextSpec sc if(sat.countTokens() == 0) { return ilSet; } + Map nameToStructuralSiteMap = new LinkedHashMap<>(); + for (Map.Entry entry : scs.getStructuralSiteAttributesMap().entrySet()) { + nameToStructuralSiteMap.put(entry.getKey().getName(), entry.getKey()); + } SpeciesContext sc = scs.getSpeciesContext(); SpeciesPattern sp = sc.getSpeciesPattern(); @@ -285,11 +356,25 @@ static Set readInternalLinksSQL(SpeciesContextSpec sc } while (tokenizer.hasMoreTokens()) { String attribute = tokenizer.nextToken(); - MolecularComponentPattern mcp1 = mtp.getMolecularComponentPattern(attribute); + LinkNode ln1; + if(nameToStructuralSiteMap.containsKey(attribute)) { + ln1 = nameToStructuralSiteMap.get(attribute); + } else { + MolecularComponentPattern mcp = mtp.getMolecularComponentPattern(attribute); + ln1 = mcp; + } attribute = tokenizer.nextToken(); - MolecularComponentPattern mcp2 = mtp.getMolecularComponentPattern(attribute); - - MolecularInternalLinkSpec ils = new MolecularInternalLinkSpec(scs, mcp1, mcp2); + LinkNode ln2; + if(nameToStructuralSiteMap.containsKey(attribute)) { + // if the attribute name matches a structural site, then treat it as a structural site and recover + // the correct instance from the map + // otherwise treat it as a molecular component pattern and recover its instance from the physiology + ln2 = nameToStructuralSiteMap.get(attribute); + } else { + MolecularComponentPattern mcp = mtp.getMolecularComponentPattern(attribute); + ln2 = mcp; + } + MolecularInternalLinkSpec ils = new MolecularInternalLinkSpec(scs, ln1, ln2); ilSet.add(ils); } } From 213304cbd4807f880c280f9f506251978d42c11a Mon Sep 17 00:00:00 2001 From: Dan Vasilescu Date: Thu, 7 May 2026 15:50:20 -0400 Subject: [PATCH 13/16] Structural sites: read from, write to the DB --- .../main/java/cbit/vcell/modeldb/SpeciesContextSpecTable.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vcell-server/src/main/java/cbit/vcell/modeldb/SpeciesContextSpecTable.java b/vcell-server/src/main/java/cbit/vcell/modeldb/SpeciesContextSpecTable.java index d61ab9ee69..83bcc51527 100644 --- a/vcell-server/src/main/java/cbit/vcell/modeldb/SpeciesContextSpecTable.java +++ b/vcell-server/src/main/java/cbit/vcell/modeldb/SpeciesContextSpecTable.java @@ -70,7 +70,8 @@ public class SpeciesContextSpecTable extends cbit.sql.Table { boundaryXmExp,boundaryXpExp,boundaryYmExp,boundaryYpExp,boundaryZmExp,boundaryZpExp,initCondCountExp, velocityXExp, velocityYExp, velocityZExp, bWellMixed, bForceContinuous, internalLinks, siteAttributesSpecs, structuralSiteAttributesSpecs}; - + // ATTENTION: if adding fields, remember to also add the column to the table schema in the database + // ex: ALTER TABLE VC_SPECIESCONTEXTSPEC ADD (STRUCTURALSITEATTRIBUTESSPECS VARCHAR2(4000)); public static final SpeciesContextSpecTable table = new SpeciesContextSpecTable(); /** * ModelTable constructor comment. From 43b0ac0d79c5f86f49a6746bbc7dea28c97f81a4 Mon Sep 17 00:00:00 2001 From: Dan Vasilescu Date: Thu, 7 May 2026 19:26:13 -0400 Subject: [PATCH 14/16] Structural sites: database write / read roundtrip test --- .../modeldb/SpeciesContextSpecTableTest.java | 17 +- .../vcell/modeldb/Spring_application2.vcml | 1098 +++++++++++++++++ 2 files changed, 1111 insertions(+), 4 deletions(-) create mode 100644 vcell-server/src/test/resources/cbit/vcell/modeldb/Spring_application2.vcml diff --git a/vcell-server/src/test/java/cbit/vcell/modeldb/SpeciesContextSpecTableTest.java b/vcell-server/src/test/java/cbit/vcell/modeldb/SpeciesContextSpecTableTest.java index 53c535fa15..426ff587d6 100644 --- a/vcell-server/src/test/java/cbit/vcell/modeldb/SpeciesContextSpecTableTest.java +++ b/vcell-server/src/test/java/cbit/vcell/modeldb/SpeciesContextSpecTableTest.java @@ -56,7 +56,7 @@ public class SpeciesContextSpecTableTest { public void test_springsalad_application() throws IOException, XmlParseException, PropertyVetoException, ExpressionException, GeometryException, ImageException, IllegalMappingException, MappingException, SolverException { - BioModel bioModel = getBioModelFromResource("Spring_application.vcml"); + BioModel bioModel = getBioModelFromResource("Spring_application2.vcml"); SimulationContext simContext = bioModel.getSimulationContext(0); // WARNING!! Debug configuration for this JUnit test required System property "vcell.installDir" @@ -65,11 +65,14 @@ public void test_springsalad_application() throws IOException, XmlParseException assertTrue((mathDescription.getMathType() != null && mathDescription.getMathType() == SpringSaLaD) ? true : false, "expecting SpringSaLaD math type"); SpeciesContextSpec[] speciesContextSpecs = simContext.getReactionContext().getSpeciesContextSpecs(); - SpeciesContextSpec scs = speciesContextSpecs[0]; // we test roundtrip for just one SpeciesContextSpec + SpeciesContextSpec scs = speciesContextSpecs[1]; // we test roundtrip for just one SpeciesContextSpec String internalLinkSetSQL = SpeciesContextSpecTable.getInternalLinksSQL(scs); String siteAttributesMapSQL = SpeciesContextSpecTable.getSiteAttributesSQL(scs); - Set internalLinkSet = SpeciesContextSpecTable.readInternalLinksSQL(scs, internalLinkSetSQL); + String structuralSiteAttributesMapSQL = SpeciesContextSpecTable.getStructuralSiteAttributesSQL(scs); + Map siteAttributesMap = SpeciesContextSpecTable.readSiteAttributesSQL(scs, siteAttributesMapSQL); + Map structuralSiteAttributesMap = SpeciesContextSpecTable.readStructuralSiteAttributesSQL(scs, structuralSiteAttributesMapSQL); + Set internalLinkSet = SpeciesContextSpecTable.readInternalLinksSQL(scs, internalLinkSetSQL); double bondLength = 1; Map subTypeMap = new LinkedHashMap<>(); @@ -111,8 +114,14 @@ public void test_springsalad_application() throws IOException, XmlParseException SiteAttributesSpec sasThis = siteAttributesMap.get(mcpThis); SiteAttributesSpec sasThat = scs.getSiteAttributesMap().get(mcpThis); assertTrue(sasThis.compareEqual(sasThat) ? true : false, "SiteAttributesSpec element not found in siteAttributesMap after roundtrip"); - } + // verify roundtrip for structuralSiteAttributesMap (through sampling) + assertTrue(structuralSiteAttributesMap.size() == scs.getStructuralSiteAttributesMap().size() ? true : false, "structuralSiteAttributesMap size different after roundtrip"); + StructuralSite ssThis = structuralSiteAttributesMap.keySet().iterator().next(); + SiteAttributesSpec ssasThis = structuralSiteAttributesMap.get(ssThis); + SiteAttributesSpec ssasThat = scs.getStructuralSiteAttributesMap().get(ssThis); + assertTrue(ssasThis.compareEqual(ssasThat) ? true : false, "SiteAttributesSpec element not found in structuralSiteAttributesMap after roundtrip"); + } // ========================================================================================================================== diff --git a/vcell-server/src/test/resources/cbit/vcell/modeldb/Spring_application2.vcml b/vcell-server/src/test/resources/cbit/vcell/modeldb/Spring_application2.vcml new file mode 100644 index 0000000000..0489267dcc --- /dev/null +++ b/vcell-server/src/test/resources/cbit/vcell/modeldb/Spring_application2.vcml @@ -0,0 +1,1098 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2.0 + 0.0 + + + + + + + + + + + + + + + + + + + + + + + + + + 1.0 + 0.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0.4 + 0.1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0.5 + 0.1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0.7 + 0.2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0.6 + 0.2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0.3 + 0.2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0.0 + 0.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0.0 + 0.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + (z < 0.09) + + + 1.0 + + + + + + + + + + + + + + + + + + + + + + 40.0 + + + + + + + + 40.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 96485.3321 + 9.64853321E-5 + 6.02214179E11 + 3.141592653589793 + 8314.46261815 + 300.0 + 0.6 + 0.7 + 2.0 + 1.0 + 0.5 + 0.0 + 0.0 + 0.3 + 0.4 + 0.001660538783162726 + 0.2 + 0.0 + 0.0 + 1.0499999999999999E-4 + 8.949999999999999E-4 + 0.009999999999999827 + (1.0 * pow(KMOLE,1.0)) + 0.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ((Molecule0_Count * UnitFactor_uM_um3_molecules_neg_1) / Size_Intracellular) + ((Molecule1_Count * UnitFactor_uM_um3_molecules_neg_1) / Size_Intracellular) + ((O0_Molecule0_tot_Count * UnitFactor_uM_um3_molecules_neg_1) / Size_Intracellular) + ((O0_Molecule1_tot_Count * UnitFactor_uM_um3_molecules_neg_1) / Size_Intracellular) + ((O0_Sink_tot_Count * UnitFactor_uM_um3_molecules_neg_1) / Size_Intracellular) + ((O0_Source_tot_Count * UnitFactor_uM_um3_molecules_neg_1) / Size_Intracellular) + Kf_allosteric + Kf_binding + Kr_binding + Kf_creation + Kf_decay + Kf_transition_bound + Kf_transition_bound_same_0 + Kf_transition_bound_same_1 + Kf_transition_free + Kf_transition_none + + + + + + + + + + P_creation_probabilityRate + 1.0 + 1.0 + + + + P_decay_probabilityRate + 1.0 + 1.0 + + + + P_transition_none_probabilityRate + 1.0 + 1.0 + + + + P_transition_bound_probabilityRate + 1.0 + 1.0 + + + + + P_binding_probabilityRate + 1.0 + 1.0 + 1.0 + + + + P_binding_reverse_probabilityRate + 1.0 + 1.0 + 1.0 + + + + P_allosteric_probabilityRate + 1.0 + 1.0 + + + + P_transition_free_probabilityRate + 1.0 + 1.0 + + + + P_transition_bound_same_0_probabilityRate + 1.0 + 1.0 + + + + P_transition_bound_same_1_probabilityRate + 1.0 + 1.0 + + + + Molecule0_Count_initCount + 0.0 + 0.0 + 0.0 + + 0.0 + 0.0 + 0.0 + 0.0 + + + + Molecule1_Count_initCount + 0.0 + 0.0 + 0.0 + + 0.0 + 0.0 + 0.0 + 0.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + 1.0E-8 + 0.001 + 10 + 10 + 10 + 1 + 1 + + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <html> + <head> + + </head> + <body> + <p style="margin-top: 0"> + Transition bound reactions always show, by convention, 2 molecules. One + depicts the binding condition, the other depicts the state transition + </p> + </body> +</html> + + + <html> + <head> + + </head> + <body> + <p style="margin-top: 0"> + Note that there is no conflict here, the meaning is that there must + really be 2 molecules of the same tipe, one with the binding condition + site in state 1, the other one transitioning from state 0 to state 1 + </p> + </body> +</html> + + + <html> + <head> + + </head> + <body> + <p style="margin-top: 0"> + For transition bound reaction, we separately must show the binding + condition molecule and the transition molecule (as a convention) even + when it actually is the same molecule + </p> + <p style="margin-top: 0"> + The reason is that it may result in a confusion with allosteric + representation + </p> + </body> +</html> + + + + + + From ea36c435c8f6cb0a6d75b479162ab10d4ca6ac6c Mon Sep 17 00:00:00 2001 From: Dan Vasilescu Date: Fri, 8 May 2026 14:12:25 -0400 Subject: [PATCH 15/16] Structural sites: database write / read roundtrip test --- vcell-rest/src/main/resources/scripts/init.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vcell-rest/src/main/resources/scripts/init.sql b/vcell-rest/src/main/resources/scripts/init.sql index d08f5d3033..2732eb8128 100644 --- a/vcell-rest/src/main/resources/scripts/init.sql +++ b/vcell-rest/src/main/resources/scripts/init.sql @@ -28,7 +28,7 @@ CREATE TABLE vc_subvolume(id bigint PRIMARY KEY,name varchar(255) NOT NULL,image CREATE TABLE vc_surfaceclass(id bigint PRIMARY KEY,name varchar(255) NOT NULL,geometryRef bigint NOT NULL REFERENCES vc_geometry(id) ON DELETE CASCADE,subVolumeRef1 bigint REFERENCES vc_subvolume(id),subVolumeRef2 bigint REFERENCES vc_subvolume(id)); CREATE TABLE vc_math(id bigint PRIMARY KEY,name varchar(255) NOT NULL,ownerRef bigint NOT NULL REFERENCES vc_userinfo(id),privacy bigint NOT NULL,versionPRef bigint REFERENCES vc_math(id),versionDate timestamp NOT NULL,versionFlag bigint NOT NULL,versionAnnot varchar(4000) ,versionBranchID bigint NOT NULL,geometryRef bigint NOT NULL REFERENCES vc_geometry(id),language text NOT NULL); CREATE TABLE vc_simcontext(id bigint PRIMARY KEY,name varchar(255) NOT NULL,ownerRef bigint NOT NULL REFERENCES vc_userinfo(id),privacy bigint NOT NULL,versionPRef bigint REFERENCES vc_simcontext(id),versionDate timestamp NOT NULL,versionFlag bigint NOT NULL,versionAnnot varchar(4000) ,versionBranchID bigint NOT NULL,mathRef bigint REFERENCES vc_math(id),modelRef bigint NOT NULL REFERENCES vc_model(id),geometryRef bigint NOT NULL REFERENCES vc_geometry(id),charSize numeric ,appComponentsLRG text ,appComponentsSML varchar(4000) ); -CREATE TABLE vc_speciescontextspec(id bigint PRIMARY KEY,specContextRef bigint NOT NULL REFERENCES vc_modelsc(id),simContextRef bigint NOT NULL REFERENCES vc_simcontext(id) ON DELETE CASCADE,bEnableDif bigint NOT NULL,bForceConst bigint NOT NULL,bForceIndep bigint NOT NULL,initCondExp varchar(2048) ,diffRateExp varchar(1024) NOT NULL,boundaryXmExp varchar(255) ,boundaryXpExp varchar(255) ,boundaryYmExp varchar(255) ,boundaryYpExp varchar(255) ,boundaryZmExp varchar(255) ,boundaryZpExp varchar(255) ,initCondCountExp varchar(1024) ,velocityXExp varchar(1024) ,velocityYExp varchar(1024) ,velocityZExp varchar(1024) ,bWellMixed bigint ,bForceContinuous bigint ,internalLinks varchar(4000) ,siteAttributesSpecs varchar(4000) ); +CREATE TABLE vc_speciescontextspec(id bigint PRIMARY KEY,specContextRef bigint NOT NULL REFERENCES vc_modelsc(id),simContextRef bigint NOT NULL REFERENCES vc_simcontext(id) ON DELETE CASCADE,bEnableDif bigint NOT NULL,bForceConst bigint NOT NULL,bForceIndep bigint NOT NULL,initCondExp varchar(2048) ,diffRateExp varchar(1024) NOT NULL,boundaryXmExp varchar(255) ,boundaryXpExp varchar(255) ,boundaryYmExp varchar(255) ,boundaryYpExp varchar(255) ,boundaryZmExp varchar(255) ,boundaryZpExp varchar(255) ,initCondCountExp varchar(1024) ,velocityXExp varchar(1024) ,velocityYExp varchar(1024) ,velocityZExp varchar(1024) ,bWellMixed bigint ,bForceContinuous bigint ,internalLinks varchar(4000) ,siteAttributesSpecs varchar(4000) ,structuralSiteAttributesSpecs varchar(4000) ); CREATE TABLE vc_structmapping(id bigint PRIMARY KEY,subVolumeRef bigint REFERENCES vc_subvolume(id),structRef bigint NOT NULL REFERENCES vc_struct(id),simContextRef bigint NOT NULL REFERENCES vc_simcontext(id) ON DELETE CASCADE,bResolved bigint NOT NULL,surfToVolExp varchar(1024) ,volFractExp varchar(1024) ,boundaryTypeXm varchar(10) ,boundaryTypeXp varchar(10) ,boundaryTypeYm varchar(10) ,boundaryTypeYp varchar(10) ,boundaryTypeZm varchar(10) ,boundaryTypeZp varchar(10) ,bCalculateV bigint ,specificCap numeric ,initialV varchar(1024) ,sizeExp varchar(1024) ,volPerUnitAreaExp varchar(1024) ,volPerUnitVolExp varchar(1024) ,areaPerUnitAreaExp varchar(1024) ,areaPerUnitVolExp varchar(1024) ,surfaceClassRef bigint REFERENCES vc_surfaceclass(id)); CREATE TABLE vc_reactionspec(id bigint PRIMARY KEY,reactStepRef bigint NOT NULL REFERENCES vc_reactstep(id),simContextRef bigint NOT NULL REFERENCES vc_simcontext(id) ON DELETE CASCADE,mapping bigint NOT NULL); CREATE TABLE vc_stimulus(id bigint PRIMARY KEY,structRef bigint NOT NULL REFERENCES vc_struct(id),simContextRef bigint NOT NULL REFERENCES vc_simcontext(id) ON DELETE CASCADE,name varchar(255) ,stimulusType bigint NOT NULL,expression varchar(4000) ,posX numeric NOT NULL,posY numeric NOT NULL,posZ numeric NOT NULL,params varchar(4000) ); From 6e36db5833ac04e1310ab5ea6ee93948e54a74b6 Mon Sep 17 00:00:00 2001 From: Dan Vasilescu Date: Fri, 8 May 2026 15:43:07 -0400 Subject: [PATCH 16/16] Structural sites: database write / read roundtrip test --- .../cbit/vcell/modeldb/SpeciesContextSpecTableTest.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/vcell-server/src/test/java/cbit/vcell/modeldb/SpeciesContextSpecTableTest.java b/vcell-server/src/test/java/cbit/vcell/modeldb/SpeciesContextSpecTableTest.java index 426ff587d6..064cc38f18 100644 --- a/vcell-server/src/test/java/cbit/vcell/modeldb/SpeciesContextSpecTableTest.java +++ b/vcell-server/src/test/java/cbit/vcell/modeldb/SpeciesContextSpecTableTest.java @@ -119,7 +119,13 @@ public void test_springsalad_application() throws IOException, XmlParseException assertTrue(structuralSiteAttributesMap.size() == scs.getStructuralSiteAttributesMap().size() ? true : false, "structuralSiteAttributesMap size different after roundtrip"); StructuralSite ssThis = structuralSiteAttributesMap.keySet().iterator().next(); SiteAttributesSpec ssasThis = structuralSiteAttributesMap.get(ssThis); - SiteAttributesSpec ssasThat = scs.getStructuralSiteAttributesMap().get(ssThis); + SiteAttributesSpec ssasThat = null; // we can't use ssThis as key to get the value from scs because + // the instances are different after round tripping, so we have to loop through the keys and compare by name + for (StructuralSite ss : scs.getStructuralSiteAttributesMap().keySet()) { + if (ss.getName().equals(ssThis.getName())) { + ssasThat = scs.getStructuralSiteAttributesMap().get(ss); + } + } assertTrue(ssasThis.compareEqual(ssasThat) ? true : false, "SiteAttributesSpec element not found in structuralSiteAttributesMap after roundtrip"); }