Skip to content

Commit

Permalink
Merge pull request #75 from phyloref/add-specimen-and-taxon-circumscr…
Browse files Browse the repository at this point in the history
…iption

This PR incorporates two phyloreferences from [Fisher *et al*, 2007](https://bioone.org/journals/the-bryologist/volume-110/issue-1/0007-2745(2007)110%5b46%3aPOTCWA%5d2.0.CO%3b2/Phylogeny-of-the-Calymperaceae-with-a-rank-free-systematic-treatment/10.1639/0007-2745(2007)110[46:POTCWA]2.0.CO;2.short), which include examples of specimens and a taxon circumscribed by a publication, as well as one of the phylogenies from that paper for testing. In doing this, I found a bug with how phylorefs with external specifiers were being generated when there are multiple external specifiers, and rewrote the algorithm to eliminate this bug. I also added a example file `example_five_external_specifiers.json`, which has a phyloreference with five external specifiers, just to make sure those logical expressions are being generated correctly.

It also includes a minor fix to error reporting in `phyx2owl.js`.
  • Loading branch information
gaurav authored Mar 9, 2021
2 parents 9625717 + 7354c6f commit e795b11
Show file tree
Hide file tree
Showing 12 changed files with 10,340 additions and 1,173 deletions.
3 changes: 2 additions & 1 deletion bin/phyx2owl.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,8 @@ function convertFileToOWL(filename, argOutputFilename = "") {

return true;
} catch(e) {
console.error(`Could not convert ${filename} to ${outputFilename}: ${e}`);
console.error(`Could not convert ${filename} to ${outputFilename}: ${e} at ${e.stack}`);
console.error(``)
}
return false;
}
Expand Down
1 change: 1 addition & 0 deletions docs/context/development/phyx.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
"@type": "xsd:string"
},

"basisOfRecord": "dwc:basisOfRecord",
"occurrenceID": "dwc:occurrenceID",
"catalogNumber": "dwc:catalogNumber",
"collectionCode": "dwc:collectionCode",
Expand Down
34 changes: 32 additions & 2 deletions docs/context/development/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,38 @@
},
"label": {
"type": "string",
"minLength": 1
"minLength": 1,
"description": "A label for this taxonomic unit."
},
"nameAccordingTo": {
"type": "string",
"minLength": 1,
"description": "For a taxon concept, provides the source in which the specific taxon concept circumscription is defined or implied (i.e. the 'sensu' or 'sec' of the name)."
},
"basisOfRecord": {
"type": "string",
"minLength": 1,
"description": "The basis of record for this specimen."
},
"occurrenceID": {
"type": "string",
"minLength": 1,
"description": "The occurrence ID of a specimen."
},
"institutionCode": {
"type": "string",
"minLength": 1,
"description": "The institution code of a specimen."
},
"collectionCode": {
"type": "string",
"minLength": 1,
"description": "The collection code of a specimen."
},
"catalogNumber": {
"type": "string",
"minLength": 1,
"description": "The catalog number of a specimen."
},
"hasName": {
"type": "object",
Expand Down Expand Up @@ -206,7 +237,6 @@
},
"required": [
"@type",
"label",
"nameComplete"
]
}
Expand Down
194 changes: 45 additions & 149 deletions src/wrappers/PhylorefWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -579,140 +579,6 @@ class PhylorefWrapper {
return classExprs;
}


/*
* Given an expression that evaluates to an included node and a taxonomic unit,
* return an expression for including it and excluding the TU. Note that this
* always returns an array.
*
* When the included expression includes a single taxonomic unit (i.e. is in the
* form `includes_TU some [TU]`), then the simple form is adequate. However, when
* it's a more complex expression, it's possible that the excluded TU isn't just
* sister to this clade but outside of it entirely. In that case, we add another
* class expression:
* [includesExpr] and (has_Ancestor some (excludes_TU some [TU]))
*/
getClassExpressionsForExprAndTU(includedExpr, tu) {
if (!includedExpr) throw new Error('Exclusions require an included expression');

const exprs = [{
'@type': 'owl:Class',
intersectionOf: [
includedExpr,
{
'@type': 'owl:Restriction',
onProperty: 'phyloref:excludes_TU',
someValuesFrom: new TaxonomicUnitWrapper(tu, this.defaultNomenCode).asOWLEquivClass,
},
],
}];

if (!Array.isArray(includedExpr) && has(includedExpr, 'onProperty') && includedExpr.onProperty === 'phyloref:includes_TU') {
// In this specific set of circumstances, we do NOT need to add the has_Ancestor check.
} else {
// Add the has_Ancestor check!
exprs.push({
'@type': 'owl:Class',
intersectionOf: [
includedExpr,
{
'@type': 'owl:Restriction',
onProperty: 'obo:CDAO_0000144', // has_Ancestor
someValuesFrom: {
'@type': 'owl:Restriction',
onProperty: 'phyloref:excludes_TU',
someValuesFrom: new TaxonomicUnitWrapper(
tu,
this.defaultNomenCode
).asOWLEquivClass,
},
},
],
});
}

return exprs;
}


/*
* Returns a list of class expressions for a phyloreference that has an expression
* for the MRCA of its internal specifiers, but also has one or more external specifiers.
* - jsonld: The JSON-LD form of the Phyloreference from Model 1.0. Mainly used
* for retrieving the '@id' and the specifiers.
* - accumulatedExpr: Initially, an expression that evaluates to the MRCA of all
* internal specifiers, calculated using createClassExpressionsForInternals().
* When we call this method recusively, this expression will incorporate
* representations of external references.
* - remainingExternals: External specifiers that are yet to be incorporated into the
* expressions we are building.
* - selected: External specifiers that have already been selected.
*
* Our overall algorithm here is:
* 1. If we need to add a single remaining external to the accumulated expression,
* we can do that by adding an `excludes_TU` to the expression (and possibly a
* `has_Ancestor` check, see getClassExpressionsForExprAndTU()).
* 2. If we need to add more than one remaining external, we select each external
* specifier one at a time. We add the selected specifier to the accumulated
* expression using getClassExpressionsForExprAndTU(), and then call ourselves
* recursively to add the remaining specifiers.
*
* The goal here is to create expressions for every possible sequence of external
* specifiers, so we can account for cases where some external specifiers are closer
* to the initial internal-specifier-only expression than others.
*/
createClassExpressionsForExternals(jsonld, accumulatedExpr, remainingExternals, selected) {
// process.stderr.write(`@id [${jsonld['@id']}] Remaining externals:
// ${remainingExternals.length}, selected: ${selected.length}\n`);

// Step 1. If we only have one external remaining, we can provide our two-case example
// to detect it.
const classExprs = [];
if (remainingExternals.length === 0) {
throw new Error('Cannot create class expression when no externals remain');
} else if (remainingExternals.length === 1) {
const remainingExternalsExprs = this.getClassExpressionsForExprAndTU(
accumulatedExpr,
remainingExternals[0],
selected.length > 0
);
remainingExternalsExprs.forEach(expr => classExprs.push(expr));
} else { // if(remainingExternals.length > 1)
// Recurse into remaining externals. Every time we select a single entry,
// we create a class expression for that.
remainingExternals.map((newlySelected) => {
// process.stderr.write(`Selecting new object, remaining now at:
// ${remainingExternals.filter(i => i !== newlySelected).length},
// selected: ${selected.concat([newlySelected]).length}\n`);

// Create a new component class for the accumulated expression plus the
// newly selected external specifier.
const newlyAccumulatedExpr = this.createComponentClass(
jsonld,
jsonld.internalSpecifiers,
selected.concat([newlySelected]),
this.getClassExpressionsForExprAndTU(
accumulatedExpr, newlySelected, selected.length > 0
)
);

// Call ourselves recursively to add the remaining externals.
return this.createClassExpressionsForExternals(
jsonld,
newlyAccumulatedExpr,
// The new remaining is the old remaining minus the selected TU.
remainingExternals.filter(i => i !== newlySelected),
// The new selected is the old selected plus the selected TU.
selected.concat([newlySelected])
);
})
.reduce((acc, val) => acc.concat(val), [])
.forEach(expr => classExprs.push(expr));
}

return classExprs;
}

/*
* Phyloref.asJSONLD(fallbackIRI)
*
Expand Down Expand Up @@ -789,24 +655,54 @@ class PhylorefWrapper {
// in the form:
// phyloref:includes_TU some [internal1] and
// phyloref:includes_TU some [internal2] and ...
// To which we can then add the external specifiers.
if (internalSpecifiers.length === 1) {
logicalExpressions = this.createClassExpressionsForExternals(
phylorefAsJSONLD,
this.getIncludesRestrictionForTU(internalSpecifiers[0]),
externalSpecifiers,
[]
// phyloref:excludes_TU some [exclusion1] and
// has_Ancestor some (phyloref:excludesTU some [exclusion2]) ...
//
// Since we don't know which of the external specifiers will actually
// be the one that will be used, we need to generate logical expressions
// for every possibility.

logicalExpressions = externalSpecifiers.map((selectedExternal) => {
// Add the internal specifiers.
const intersectionExprs = internalSpecifiers.map(
sp => this.getIncludesRestrictionForTU(sp)
);
} else {
const expressionForInternals = {

// Add the selected external specifier.
intersectionExprs.push({
'@type': 'owl:Restriction',
onProperty: 'phyloref:excludes_TU',
someValuesFrom: new TaxonomicUnitWrapper(
selectedExternal,
this.defaultNomenCode
).asOWLEquivClass,
});

// Collect all of the externals that are not selected.
const remainingExternals = externalSpecifiers.filter(ex => ex !== selectedExternal);

// Add the remaining externals, which we assume will resolve outside of
// this clade.
remainingExternals.forEach((externalTU) => {
intersectionExprs.push({
'@type': 'owl:Restriction',
onProperty: 'obo:CDAO_0000144', // has_Ancestor
someValuesFrom: {
'@type': 'owl:Restriction',
onProperty: 'phyloref:excludes_TU',
someValuesFrom: new TaxonomicUnitWrapper(
externalTU,
this.defaultNomenCode
).asOWLEquivClass,
},
});
});

return {
'@type': 'owl:Class',
intersectionOf: internalSpecifiers.map(sp => this.getIncludesRestrictionForTU(sp)),
intersectionOf: intersectionExprs,
};

logicalExpressions = this.createClassExpressionsForExternals(
phylorefAsJSONLD, expressionForInternals, externalSpecifiers, []
);
}
});
} else {
calculatedPhylorefType = 'phyloref:PhyloreferenceUsingMinimumClade';

Expand Down
Loading

0 comments on commit e795b11

Please sign in to comment.