Skip to content

Commit

Permalink
Merge pull request #31 from linkml/singleton-intersections
Browse files Browse the repository at this point in the history
Comvert singleton IntersectionOf(X) into X.
  • Loading branch information
cmungall authored Jan 6, 2023
2 parents 681874d + 2dc3fe8 commit 55784b8
Show file tree
Hide file tree
Showing 11 changed files with 1,172 additions and 78 deletions.
5 changes: 1 addition & 4 deletions .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,7 @@ jobs:
# install & configure poetry
#----------------------------------------------
- name: Install Poetry
uses: snok/install-poetry@v1.1.1
with:
virtualenvs-create: true
virtualenvs-in-project: true
uses: snok/install-poetry@v1.3.1

#----------------------------------------------
# load cached venv if cache exists
Expand Down
43 changes: 36 additions & 7 deletions linkml_owl/dumpers/owl_dumper.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import click
from funowl.converters.functional_converter import to_python
from funowl.writers.FunctionalWriter import FunctionalWriter
from jinja2 import Template
from linkml.generators.pythongen import PythonGenerator
from linkml_runtime import SchemaView
Expand Down Expand Up @@ -233,7 +234,7 @@ def transform(self, element: YAMLRoot, schema: SchemaDefinition, is_element_an_o
for fstr in self._get_inferred_class_annotations(c, 'owl.fstring'):
self.add_axioms_from_fstring(fstr, element)
for tmpl_str in self._get_inferred_class_annotations(c, 'owl.template'):
self.add_axioms_from_template(tmpl_str, element)
self.add_axioms_from_template(tmpl_str, element, schema=schema)
cls_interps = self._get_class_interpretations(c)
subj = None
eai = EntityAxiomIndex()
Expand Down Expand Up @@ -272,6 +273,8 @@ def transform(self, element: YAMLRoot, schema: SchemaDefinition, is_element_an_o
# the role of the identifier slot is to determine the IRI for the element;
# it generates no axioms of its own
continue
if 'owl.ignore' in slot.annotations:
continue
schema_level_slot = self._get_schema_level_slot(slot)
# lookup OWL settings on each slot
owl_templates = self._get_inferred_slot_annotations(slot, 'owl.template', linkml_class_name)
Expand Down Expand Up @@ -440,18 +443,30 @@ def transform(self, element: YAMLRoot, schema: SchemaDefinition, is_element_an_o
# all per-slot axioms have been processed; axioms that span
# multiple slots are now processed
if "IntersectionOf" in cls_interps:
if len(unprocessed_parents) == 0:
raise ValueError(f"Cannot process IntersectionOf with no parents for {element}")
if len(unprocessed_parents) == 1:
logging.debug(f"Simplifying IntersectionOf(...) to {unprocessed_parents[0]}")
return unprocessed_parents[0]
expr = ObjectIntersectionOf(*unprocessed_parents)
logging.debug(f"Returning expression {expr} // {eai.operand_list_index.items()}")
return expr
for op_key, operands in eai.operand_list_index.items():
_, interp, operator = op_key
logging.debug(f'EntityAxiomIndex {subj}: {interp} => {operator} over {operands}')
if len(operands) < 2:
if len(operands) == 0:
raise ValueError(f'Too few operands: {operands} for {operator} in {subj}')
if operator == ObjectUnionOf.__name__:
if len(operands) == 1:
logging.debug(f"Simplifying {operator}(...) to {operands[0]}")
return operands[0]
elif operator == ObjectUnionOf.__name__:
expr = ObjectUnionOf(*operands)
elif operator == ObjectIntersectionOf.__name__:
expr = ObjectIntersectionOf(*operands)
if len(operands) == 1:
logging.debug(f"Simplifying IntersectionOf(...) to {operands[0]}")
expr = operands[0]
else:
expr = ObjectIntersectionOf(*operands)
else:
raise ValueError(f'Cannot handle operator: {operator}')
if interp == EquivalentClasses.__name__:
Expand Down Expand Up @@ -652,13 +667,24 @@ def add_axioms_from_fstring(self, fstring: Union[str, meta.Annotation], element:
logging.debug(f'AXIOMS >> = {axioms}')
self.ontology.axioms += axioms

def add_axioms_from_template(self, template_ann: Union[str, meta.Annotation], element: YAMLRoot, val: Any = None):
def add_axioms_from_template(self, template_ann: Union[str, meta.Annotation], element: YAMLRoot, val: Any = None, schema: SchemaDefinition = None):
# TODO: simplify, change arg to str
d = self._element_to_template_dict(element, val)
if isinstance(template_ann, str):
tstr = template_ann
else:
tstr = template_ann.value
def tr(e: YAMLRoot):
expr = self.transform(e, schema=schema, is_element_an_object=False)
fw = FunctionalWriter()
logging.debug(f"template.transform({e}) DIRECT = {expr}")
owl_str = str(expr.to_functional(fw))
logging.debug(f"template.transform({e}) = {owl_str}")
return owl_str
if "tr" in d:
d["_tr"] = tr
else:
d["tr"] = tr
jt = Template(tstr)
owl_str = jt.render(**d)
axioms = self.parse_axioms_string(owl_str).ontology.axioms
Expand Down Expand Up @@ -750,8 +776,11 @@ def cli(inputfile: str, schema: str, target_class, module, output, format, autof
if autofill:
dumper.autofill = True
doc = dumper.dumps(element, schemaview=sv)
with open(output, 'w') as stream:
stream.write(str(doc))
if output is None:
print(str(doc))
else:
with open(output, 'w') as stream:
stream.write(str(doc))


if __name__ == '__main__':
Expand Down
10 changes: 10 additions & 0 deletions tests/inputs/owl_dumper_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ prefixes:
PATO: http://purl.obolibrary.org/obo/PATO_
skos: http://www.w3.org/2004/02/skos/core#
dcterms: http://purl.org/dc/terms/
schema: http://schema.org/
x: http://example.org/

default_prefix: test
Expand Down Expand Up @@ -299,6 +300,15 @@ classes:
owl: ObjectSomeValuesFrom
annotations:
owl: Class
HasName:
is_a: NamedThing
description: test metaclass illustrating data has value from
attributes:
has_name:
required: true
slot_uri: schema:name
annotations:
owl: DataHasValue
EquivGenusAndPartOf:
is_a: NamedThing
description: test metaclass illustrating basic simple genus-differentia style logical definition, including so-called hidden GCIs
Expand Down
101 changes: 101 additions & 0 deletions tests/inputs/recipe.data.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
url: http://example.org
label: Simple Spaghetti
description: A classic spaghetti recipe made with onion, bell peppers, garlic powder,
butter, salt, pepper, tomato sauce, and hamburger meat.
categories:
- AUTO:Main%20dish
- HANCESTRO:0307
ingredients:
- food_item:
food: AUTO:Small%20onion
state: chopped
amount:
value: '1'
unit: AUTO:cup
- food_item:
food: FOODON:00003485
state: chopped
amount:
value: chopped
unit: AUTO:N/A
- food_item:
food: FOODON:00003582
state: powder
- food_item:
food: FOODON:03310351
- food_item:
food: AUTO:salt
- food_item:
food: FOODON:00001649
- food_item:
food: FOODON:03301217
state: canned
amount:
value: '2'
unit: AUTO:cans
- food_item:
food: AUTO:16-ounce%20box%20spaghetti%20noodles
state: chopped, diced
amount:
value: '1'
- food_item:
food: FOODON:00001282
state: chopped/diced
amount:
value: "1-1 1\u20442"
unit: AUTO:lb
steps:
- action: AUTO:melt%3B%20saut%C3%A9
inputs:
- food: FOODON:03310351
- food: FOODON:03301704
- food: AUTO:bell%20peppers
state: chopped
utensils:
- AUTO:pan
- action: AUTO:add%3B%20cook
inputs:
- food: FOODON:00001282
state: chopped/diced
outputs:
- food: AUTO:meat
state: well done
utensils:
- AUTO:pan
- action: AUTO:add%3B
inputs:
- food: FOODON:03000227
state: sauce
- food: AUTO:salt
- food: FOODON:00001649
- food: FOODON:00003582
state: powder
utensils:
- AUTO:n/a
- action: AUTO:adjust%3B
inputs:
- food: AUTO:salt
- food: FOODON:00001649
- food: FOODON:00003582
state: powder
outputs:
- food: AUTO:adjusted
state: to own tastes
utensils:
- AUTO:none
- action: AUTO:cook
inputs:
- food: AUTO:noodles
outputs:
- food: AUTO:noodles
utensils:
- AUTO:pan
- action: AUTO:mix%3B
inputs:
- food: FOODON:03311146
- food: AUTO:noodles
outputs:
- food: FOODON:03311146
- food: AUTO:noodles
utensils:
- AUTO:none
Loading

0 comments on commit 55784b8

Please sign in to comment.