Skip to content

Commit

Permalink
Merge pull request #1345 from virtualcell/sedml-path-or-uri
Browse files Browse the repository at this point in the history
allow URI or Path refs in SedML, add MathML xor support
  • Loading branch information
jcschaff authored Aug 30, 2024
2 parents e5878e8 + fc58fb2 commit ec25940
Show file tree
Hide file tree
Showing 10 changed files with 73 additions and 53 deletions.
42 changes: 15 additions & 27 deletions vcell-core/src/main/java/org/jlibsedml/Model.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

import org.jlibsedml.modelsupport.SUPPORTED_LANGUAGE;

import static org.jlibsedml.execution.ArchiveModelResolver.SPACE_URI_ESCAPE_SEQUENCE;

/**
* Encapsulates a SED-ML Model element. Note that this object is not the
* computational model itself, but holds information about the model within
Expand All @@ -19,7 +21,7 @@
public final class Model extends AbstractIdentifiableElement {

private String language = null;
private String source = null;
private final String source_path_or_URI_string;

/**
* Sets the model language that this element refers to. This should ideally be a URN,
Expand All @@ -45,21 +47,21 @@ public void setLanguage(String language) {
* This should ideally be a URN, which may already be defined in
* {@link SUPPORTED_LANGUAGE}. If no language describes this
* model, the argument may be <code>null</code>.
* @param source
* A mandatory URI to the model source.
* @param source_path_or_URI_string
* A mandatory URI or local path to the model source.
* @throws IllegalArgumentException
* if any argument except <code>name</code> is null or an empty
* string.
*/
public Model(String id, String name, String language, String source) {
public Model(String id, String name, String language, String source_path_or_URI_string) {
super(id, name);
if (SEDMLElementFactory.getInstance().isStrictCreation()) {
Assert.checkNoNullArgs(source);
Assert.stringsNotEmpty(source);
Assert.checkNoNullArgs(source_path_or_URI_string);
Assert.stringsNotEmpty(source_path_or_URI_string);
}

this.language = language;
this.source = source;
this.source_path_or_URI_string = source_path_or_URI_string;
}

/**
Expand All @@ -76,7 +78,7 @@ public Model(String id, String name, String language, String source) {
* if any argument is null
*/
public Model(Model toCopy, String id) {
this(id, toCopy.getName(), toCopy.getLanguage(), toCopy.getSource());
this(id, toCopy.getName(), toCopy.getLanguage(), toCopy.getSourcePathOrURIString());
}

/**
Expand Down Expand Up @@ -139,9 +141,8 @@ public String getLanguage() {
*
* @return A <code>String</code>
*/
public String getSource() {

return source;
public String getSourcePathOrURIString() {
return this.source_path_or_URI_string;
}

/**
Expand Down Expand Up @@ -231,9 +232,7 @@ public String getSource() {
* cannot be converted to a URI.
*/
public URI getSourceURI() throws URISyntaxException {

return new URI(source);

return new URI(this.source_path_or_URI_string.replace(" ", SPACE_URI_ESCAPE_SEQUENCE));
}

/**
Expand Down Expand Up @@ -269,31 +268,21 @@ public boolean isSourceURIRelative() {
*/
public boolean isSourceValidURI() {
try {
new URI(getSource());
getSourceURI();
} catch (URISyntaxException e) {
return false;
}
return true;
}

/**
*
* @param srcString
* A URI of where this model can be retrieved.
* Attention: if the string is a model reference, it should be prefixed with "#"
*/
public void setSource(String srcString) {
source = srcString;
}

/**
* @see Object#toString()
*/
public String toString() {
return new StringBuffer().append("Model [").append("id=").append(
getId()).append(", ").append("name=").append(getName())
.append(", ").append("language=").append(language).append(
", ").append("src=").append(source).append("]").toString();
", ").append("src=").append(this.source_path_or_URI_string).append("]").toString();
}

@Override
Expand All @@ -311,6 +300,5 @@ public boolean accept(SEDMLVisitor visitor){
}
}
return true;

}
}
2 changes: 1 addition & 1 deletion vcell-core/src/main/java/org/jlibsedml/SEDMLWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ Element getXML(Model sedmlModel) {
node.setAttribute(SEDMLTags.MODEL_ATTR_LANGUAGE, s); // insert
// 'type'
// attribute
s = sedmlModel.getSource();
s = sedmlModel.getSourcePathOrURIString();
if (s != null)
node.setAttribute(SEDMLTags.MODEL_ATTR_SOURCE, s); // insert
// 'source'
Expand Down
2 changes: 1 addition & 1 deletion vcell-core/src/main/java/org/jlibsedml/SedML.java
Original file line number Diff line number Diff line change
Expand Up @@ -733,7 +733,7 @@ public final boolean addIdentifiersAsDataGenerators(AbstractTask task,
public List<Model> getBaseModels() {
Set<Model> set = new HashSet<Model>();
for (Model m : getModels()) {
if (getModelWithId(m.getSource()) == null) {
if (getModelWithId(m.getSourcePathOrURIString()) == null) {
set.add(m);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,16 @@
import org.jlibsedml.IModelContent;

public class ArchiveModelResolver implements IModelResolver {
public final static String SPACE_URI_ESCAPE_SEQUENCE = "%20";

private ArchiveComponents ac;
private String sedmlPath = "";
public ArchiveModelResolver(ArchiveComponents ac) {
this.ac=ac;
}
public String getModelXMLFor(URI modelURI) {
List<IModelContent> children = ac.getModelFiles();
String modelStr = modelURI.toString();
String modelStr = modelURI.toString().replace(SPACE_URI_ESCAPE_SEQUENCE, " ");
// try direct match first
for (IModelContent imc: children) {
String modelElementStr = imc.getName();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ public String getModelString(Model m) {
srcURI = sedml.getModelWithId(modelRef).getSourceURI();
// If we need to recurse to the next level:
modelRef = sedml.getModelWithId(modelRef.startsWith("#") ?
modelRef.substring(1): modelRef).getSource();
modelRef.substring(1): modelRef).getSourcePathOrURIString();
} while (srcURI != null && srcURI.toString().contains("#"));
} catch (URISyntaxException e) {
logger.error(MODEL_SRC_NOT_VALID_URI+": "+e.getMessage(), e);
Expand Down Expand Up @@ -187,7 +187,7 @@ final String getBaseModel(URI modelSrc) {
}

void getModelModificationTree(Model m, List<String> modelRefs2) {
String modelSrcRef = m.getSource();
String modelSrcRef = m.getSourcePathOrURIString();

modelRefs2.add(m.getId());
if (sedml.getModelWithId(modelSrcRef) != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public List<SedMLError> validate() throws XMLException {
List<SedMLError> errs = new ArrayList<SedMLError>();
List<Model> models = sedml.getModels();
for (Model model : models) {
String src = model.getSource();
String src = model.getSourcePathOrURIString();
String id = model.getId();
Set<String> ids = new HashSet<String>();
ids.add(id);
Expand All @@ -50,12 +50,12 @@ public List<SedMLError> validate() throws XMLException {
errs.add(new SedMLError(line,
"Cycles detected in source references for model "
+ newID + " and "
+ sedml.getModelWithId(newID).getSource(),
+ sedml.getModelWithId(newID).getSourcePathOrURIString(),
ERROR_SEVERITY.ERROR));
return errs;
} else {
ids.add(newID);
src = sedml.getModelWithId(src).getSource();
src = sedml.getModelWithId(src).getSourcePathOrURIString();
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ public List<SedMLError> validate() {
for (Model model: models){

try {
URI uri = new URI(model.getSource());
URI uri = model.getSourceURI();
} catch (URISyntaxException e) {
errs.add(new SedMLError(0,"ErrMessageRoot[" + model.getSource() +"]", ERROR_SEVERITY.WARNING ));
errs.add(new SedMLError(0,"ErrMessageRoot[" + model.getSourcePathOrURIString() +"]", ERROR_SEVERITY.WARNING ));
}


Expand Down
6 changes: 3 additions & 3 deletions vcell-core/src/main/java/org/vcell/sedml/SEDMLImporter.java
Original file line number Diff line number Diff line change
Expand Up @@ -807,7 +807,7 @@ private Map<String, BioModel> createBioModels(List<Model> models) throws SEDMLIm

// Group models by type for processing order
for (Model model : models){
if (model.getSource().startsWith("#")){
if (model.getSourcePathOrURIString().startsWith("#")){
advancedModelsList.get(ADVANCED_MODEL_TYPES.REFERENCING_MODELS.ordinal()).add(model);
} else if (!model.getListOfChanges().isEmpty()) {
advancedModelsList.get(ADVANCED_MODEL_TYPES.CHANGED_MODELS.ordinal()).add(model);
Expand Down Expand Up @@ -866,13 +866,13 @@ private Map<String, BioModel> createBioModels(List<Model> models) throws SEDMLIm
}

private String getReferenceId(Model model){
String referenceId = model.getSource();
String referenceId = model.getSourcePathOrURIString();
return referenceId.startsWith("#") ? referenceId.substring(1) : referenceId;
}

private BioModel getModelReference(String referenceId, Model model, Map<String, BioModel> idToBiomodelMap) throws SEDMLImportException {
// Were we given a reference ID? We need to check if the parent was processed yet.
if (referenceId != null && !model.getSource().equals(referenceId)){
if (referenceId != null && !model.getSourcePathOrURIString().equals(referenceId)){
boolean canTranslate;
BioModel parentBiomodel = idToBiomodelMap.get(referenceId);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,27 @@ private SimpleNode getRootNode(Element nodeMathML, String timeSymbol) throws Exp
vcellOperationNode = new ASTAndNode();
} else if (operation.getName().equals(MathMLTags.OR)){
vcellOperationNode = new ASTOrNode();
} else if (operation.getName().equals(MathMLTags.XOR)){
// XOR(A,B) --> OR(AND(A,NOT(B)),AND(NOT(A),B))
// NOT(B)
ASTNotNode notNode = new ASTNotNode();
notNode.jjtAddChild(getRootNode((Element)children.get(2), timeSymbol));
// AND(A,NOT(B))
ASTAndNode andNode1 = new ASTAndNode();
andNode1.jjtAddChild(getRootNode((Element)children.get(1), timeSymbol));
andNode1.jjtAddChild(notNode);
// NOT(A)
ASTNotNode notNode2 = new ASTNotNode();
notNode2.jjtAddChild(getRootNode((Element)children.get(1), timeSymbol));
// AND(NOT(A),B)
ASTAndNode andNode2 = new ASTAndNode();
andNode2.jjtAddChild(notNode2);
andNode2.jjtAddChild(getRootNode((Element)children.get(2), timeSymbol));
// OR(AND(A,NOT(B)),AND(NOT(A),B))
ASTOrNode orNode = new ASTOrNode();
orNode.jjtAddChild(andNode1);
orNode.jjtAddChild(andNode2);
return orNode;
} else if (operation.getName().equals(MathMLTags.NOT)){
vcellOperationNode = new ASTNotNode();
} else if (operation.getName().equals(MathMLTags.PLUS)){
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import org.jmathml.ASTNode;
import org.jmathml.MathMLReader;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;

Expand All @@ -25,13 +26,7 @@
import static org.junit.jupiter.api.Assertions.fail;

@Tag("Fast")
public class MathMLTester {

private Expression expression;

public MathMLTester(Expression exp) {
this.expression = exp;
}
public class MathMLTest {

public static Collection<Expression> testCases() throws ExpressionException {
int depthOfExpressionTree = 2;
Expand Down Expand Up @@ -72,9 +67,23 @@ public static Collection<Expression> testCases() throws ExpressionException {
return expressions;
}

@Test
public void testMathMLParsing_XOR() throws IOException, ExpressionException {
// XOR is supported as an SBML operator for importing only
Expression exp = new Expression("(x && !y) || (!x && y)");
String temp_mathMLStr_with_and = ExpressionMathMLPrinter.getMathML(new Expression("x && y"), true,
ExpressionMathMLPrinter.MathType.REAL, ExpressionMathMLPrinter.Dialect.SBML_SUBSET);
String mathMLStr_with_xor = temp_mathMLStr_with_and.replace("and", "xor");
Expression xor_from_MathML = new ExpressionMathMLParser(null).fromMathML(mathMLStr_with_xor, "t");
boolean equiv = ExpressionUtils.functionallyEquivalent(exp, xor_from_MathML, true);
String msg = "not equivalent: origExp='"+exp.infix()+"', expMathML='"+xor_from_MathML.infix()+"'";
assertTrue(equiv, msg);
}


@ParameterizedTest
@MethodSource("testCases")
public void testMathML_SBMLSubset() throws IOException, ExpressionException {
public void testMathML_SBMLSubset(Expression expression) throws IOException, ExpressionException {
if (expression.infix().contains("atan")){
return;
}
Expand All @@ -88,7 +97,7 @@ public void testMathML_SBMLSubset() throws IOException, ExpressionException {

@ParameterizedTest
@MethodSource("testCases")
public void test_vcell_mathml_jMathML_mathml_vcell() throws IOException, ExpressionException {
public void test_vcell_mathml_jMathML_mathml_vcell(Expression expression) throws IOException, ExpressionException {
List<String> tokensToAvoid = Arrays.asList(
"atan",
"sech", "csch", "coth",
Expand Down Expand Up @@ -119,7 +128,7 @@ public void test_vcell_mathml_jMathML_mathml_vcell() throws IOException, Express

@ParameterizedTest
@MethodSource("testCases")
public void testMathML_GeneralSubset() throws IOException, ExpressionException {
public void testMathML_GeneralSubset(Expression expression) throws IOException, ExpressionException {
if (expression.infix().contains("atan")){
return;
}
Expand All @@ -133,14 +142,14 @@ public void testMathML_GeneralSubset() throws IOException, ExpressionException {

@ParameterizedTest
@MethodSource("testCases")
public void testFlatten() throws ExpressionException {
public void testFlatten(Expression expression) throws ExpressionException {
Expression expFlattened = expression.flatten();
assertTrue(functionallyEquivalent(expression, expFlattened, false), "not equivalent: origExp='" + expression.infix() + "', expFlattened='" + expFlattened.infix() + "'");
}

@ParameterizedTest
@MethodSource("testCases")
public void testJSCLParseInfix() throws ExpressionException, ParseException {
public void testJSCLParseInfix(Expression expression) throws ExpressionException, ParseException {
List<String> tokensToAvoid = Arrays.asList(
"<",">","=","&","|","!"
);
Expand Down Expand Up @@ -168,7 +177,7 @@ public void testJSCLParseInfix() throws ExpressionException, ParseException {

@ParameterizedTest
@MethodSource("testCases")
public void testJSCLSimplifyInfix() {
public void testJSCLSimplifyInfix(Expression expression) {
Expression expSimplified = null;
try {
expSimplified = expression.simplifyJSCL(20000, true);
Expand Down

0 comments on commit ec25940

Please sign in to comment.