diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 3d3f474..fb27175 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -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 diff --git a/linkml_owl/dumpers/owl_dumper.py b/linkml_owl/dumpers/owl_dumper.py index b211c42..60b4464 100644 --- a/linkml_owl/dumpers/owl_dumper.py +++ b/linkml_owl/dumpers/owl_dumper.py @@ -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 @@ -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() @@ -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) @@ -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__: @@ -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 @@ -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__': diff --git a/tests/inputs/owl_dumper_test.yaml b/tests/inputs/owl_dumper_test.yaml index df575c0..5068eb3 100644 --- a/tests/inputs/owl_dumper_test.yaml +++ b/tests/inputs/owl_dumper_test.yaml @@ -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 @@ -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 diff --git a/tests/inputs/recipe.data.yaml b/tests/inputs/recipe.data.yaml new file mode 100644 index 0000000..0d7f4af --- /dev/null +++ b/tests/inputs/recipe.data.yaml @@ -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 diff --git a/tests/inputs/recipe.expected.ofn b/tests/inputs/recipe.expected.ofn new file mode 100644 index 0000000..7f9772b --- /dev/null +++ b/tests/inputs/recipe.expected.ofn @@ -0,0 +1,210 @@ +Prefix( owl: = ) +Prefix( rdf: = ) +Prefix( rdfs: = ) +Prefix( xsd: = ) +Prefix( xml: = ) +Prefix( linkml: = ) +Prefix( recipe: = ) +Prefix( FOODON: = ) +Prefix( UO: = ) +Prefix( dcterms: = ) +Prefix( HANCESTRO: = ) +Prefix( BFO: = ) +Prefix( AUTO: = ) +Prefix( RO: = ) + +Ontology( + EquivalentClasses( + + ObjectIntersectionOf( + recipe:Recipe + ObjectSomeValuesFrom( recipe:steps ObjectIntersectionOf( + AUTO:melt%3B%20saut%C3%A9 + ObjectSomeValuesFrom( ObjectSomeValuesFrom( ) ) + ObjectSomeValuesFrom( ObjectSomeValuesFrom( ) ) + ObjectSomeValuesFrom( ObjectIntersectionOf( + ObjectSomeValuesFrom( AUTO:bell%20peppers ) + DataHasValue( recipe:state "chopped" ) + ) ) + ObjectSomeValuesFrom( AUTO:pan ) + ) ) + ObjectSomeValuesFrom( recipe:steps ObjectIntersectionOf( + AUTO:add%3B%20cook + ObjectSomeValuesFrom( ObjectIntersectionOf( + ObjectSomeValuesFrom( ) + DataHasValue( recipe:state "chopped/diced" ) + ) ) + ObjectSomeValuesFrom( ObjectIntersectionOf( + ObjectSomeValuesFrom( AUTO:meat ) + DataHasValue( recipe:state "well done" ) + ) ) + ObjectSomeValuesFrom( AUTO:pan ) + ) ) + ObjectSomeValuesFrom( recipe:steps ObjectIntersectionOf( + AUTO:add%3B + ObjectSomeValuesFrom( ObjectIntersectionOf( + ObjectSomeValuesFrom( ) + DataHasValue( recipe:state "sauce" ) + ) ) + ObjectSomeValuesFrom( ObjectSomeValuesFrom( AUTO:salt ) ) + ObjectSomeValuesFrom( ObjectSomeValuesFrom( ) ) + ObjectSomeValuesFrom( ObjectIntersectionOf( + ObjectSomeValuesFrom( ) + DataHasValue( recipe:state "powder" ) + ) ) + ObjectSomeValuesFrom( ) + ) ) + ObjectSomeValuesFrom( recipe:steps ObjectIntersectionOf( + AUTO:adjust%3B + ObjectSomeValuesFrom( ObjectSomeValuesFrom( AUTO:salt ) ) + ObjectSomeValuesFrom( ObjectSomeValuesFrom( ) ) + ObjectSomeValuesFrom( ObjectIntersectionOf( + ObjectSomeValuesFrom( ) + DataHasValue( recipe:state "powder" ) + ) ) + ObjectSomeValuesFrom( ObjectIntersectionOf( + ObjectSomeValuesFrom( AUTO:adjusted ) + DataHasValue( recipe:state "to own tastes" ) + ) ) + ObjectSomeValuesFrom( AUTO:none ) + ) ) + ObjectSomeValuesFrom( recipe:steps ObjectIntersectionOf( + AUTO:cook + ObjectSomeValuesFrom( ObjectSomeValuesFrom( AUTO:noodles ) ) + ObjectSomeValuesFrom( ObjectSomeValuesFrom( AUTO:noodles ) ) + ObjectSomeValuesFrom( AUTO:pan ) + ) ) + ObjectSomeValuesFrom( recipe:steps ObjectIntersectionOf( + AUTO:mix%3B + ObjectSomeValuesFrom( ObjectSomeValuesFrom( ) ) + ObjectSomeValuesFrom( ObjectSomeValuesFrom( AUTO:noodles ) ) + ObjectSomeValuesFrom( ObjectSomeValuesFrom( ) ) + ObjectSomeValuesFrom( ObjectSomeValuesFrom( AUTO:noodles ) ) + ObjectSomeValuesFrom( AUTO:none ) + ) ) + ObjectSomeValuesFrom( ObjectSomeValuesFrom( recipe:food_item ObjectIntersectionOf( + ObjectSomeValuesFrom( AUTO:Small%20onion ) + DataHasValue( recipe:state "chopped" ) + ) ) ) + ObjectSomeValuesFrom( ObjectSomeValuesFrom( recipe:food_item ObjectIntersectionOf( + ObjectSomeValuesFrom( ) + DataHasValue( recipe:state "chopped" ) + ) ) ) + ObjectSomeValuesFrom( ObjectSomeValuesFrom( recipe:food_item ObjectIntersectionOf( + ObjectSomeValuesFrom( ) + DataHasValue( recipe:state "powder" ) + ) ) ) + ObjectSomeValuesFrom( ObjectSomeValuesFrom( recipe:food_item ObjectSomeValuesFrom( ) ) ) + ObjectSomeValuesFrom( ObjectSomeValuesFrom( recipe:food_item ObjectSomeValuesFrom( AUTO:salt ) ) ) + ObjectSomeValuesFrom( ObjectSomeValuesFrom( recipe:food_item ObjectSomeValuesFrom( ) ) ) + ObjectSomeValuesFrom( ObjectSomeValuesFrom( recipe:food_item ObjectIntersectionOf( + ObjectSomeValuesFrom( ) + DataHasValue( recipe:state "canned" ) + ) ) ) + ObjectSomeValuesFrom( ObjectSomeValuesFrom( recipe:food_item ObjectIntersectionOf( + ObjectSomeValuesFrom( AUTO:16-ounce%20box%20spaghetti%20noodles ) + DataHasValue( recipe:state "chopped, diced" ) + ) ) ) + ObjectSomeValuesFrom( ObjectSomeValuesFrom( recipe:food_item ObjectIntersectionOf( + ObjectSomeValuesFrom( ) + DataHasValue( recipe:state "chopped/diced" ) + ) ) ) + ) + ) + Declaration( Class( ) ) + AnnotationAssertion( rdfs:label "Simple Spaghetti" ) + AnnotationAssertion( dcterms:description "A classic spaghetti recipe made with onion, bell peppers, garlic powder, butter, salt, pepper, tomato sauce, and hamburger meat." ) + AnnotationAssertion( dcterms:subject AUTO:Main%20dish ) + AnnotationAssertion( dcterms:subject ) + SubClassOf( ObjectSomeValuesFrom( ObjectSomeValuesFrom( recipe:food_item ObjectIntersectionOf( + ObjectSomeValuesFrom( AUTO:Small%20onion ) + DataHasValue( recipe:state "chopped" ) + ) ) ) ) + SubClassOf( ObjectSomeValuesFrom( ObjectSomeValuesFrom( recipe:food_item ObjectIntersectionOf( + ObjectSomeValuesFrom( ) + DataHasValue( recipe:state "chopped" ) + ) ) ) ) + SubClassOf( ObjectSomeValuesFrom( ObjectSomeValuesFrom( recipe:food_item ObjectIntersectionOf( + ObjectSomeValuesFrom( ) + DataHasValue( recipe:state "powder" ) + ) ) ) ) + SubClassOf( ObjectSomeValuesFrom( ObjectSomeValuesFrom( recipe:food_item ObjectSomeValuesFrom( ) ) ) ) + SubClassOf( ObjectSomeValuesFrom( ObjectSomeValuesFrom( recipe:food_item ObjectSomeValuesFrom( AUTO:salt ) ) ) ) + SubClassOf( ObjectSomeValuesFrom( ObjectSomeValuesFrom( recipe:food_item ObjectSomeValuesFrom( ) ) ) ) + SubClassOf( ObjectSomeValuesFrom( ObjectSomeValuesFrom( recipe:food_item ObjectIntersectionOf( + ObjectSomeValuesFrom( ) + DataHasValue( recipe:state "canned" ) + ) ) ) ) + SubClassOf( ObjectSomeValuesFrom( ObjectSomeValuesFrom( recipe:food_item ObjectIntersectionOf( + ObjectSomeValuesFrom( AUTO:16-ounce%20box%20spaghetti%20noodles ) + DataHasValue( recipe:state "chopped, diced" ) + ) ) ) ) + SubClassOf( ObjectSomeValuesFrom( ObjectSomeValuesFrom( recipe:food_item ObjectIntersectionOf( + ObjectSomeValuesFrom( ) + DataHasValue( recipe:state "chopped/diced" ) + ) ) ) ) + SubClassOf( ObjectSomeValuesFrom( recipe:steps ObjectIntersectionOf( + AUTO:melt%3B%20saut%C3%A9 + ObjectSomeValuesFrom( ObjectSomeValuesFrom( ) ) + ObjectSomeValuesFrom( ObjectSomeValuesFrom( ) ) + ObjectSomeValuesFrom( ObjectIntersectionOf( + ObjectSomeValuesFrom( AUTO:bell%20peppers ) + DataHasValue( recipe:state "chopped" ) + ) ) + ObjectSomeValuesFrom( AUTO:pan ) + ) ) ) + SubClassOf( ObjectSomeValuesFrom( recipe:steps ObjectIntersectionOf( + AUTO:add%3B%20cook + ObjectSomeValuesFrom( ObjectIntersectionOf( + ObjectSomeValuesFrom( ) + DataHasValue( recipe:state "chopped/diced" ) + ) ) + ObjectSomeValuesFrom( ObjectIntersectionOf( + ObjectSomeValuesFrom( AUTO:meat ) + DataHasValue( recipe:state "well done" ) + ) ) + ObjectSomeValuesFrom( AUTO:pan ) + ) ) ) + SubClassOf( ObjectSomeValuesFrom( recipe:steps ObjectIntersectionOf( + AUTO:add%3B + ObjectSomeValuesFrom( ObjectIntersectionOf( + ObjectSomeValuesFrom( ) + DataHasValue( recipe:state "sauce" ) + ) ) + ObjectSomeValuesFrom( ObjectSomeValuesFrom( AUTO:salt ) ) + ObjectSomeValuesFrom( ObjectSomeValuesFrom( ) ) + ObjectSomeValuesFrom( ObjectIntersectionOf( + ObjectSomeValuesFrom( ) + DataHasValue( recipe:state "powder" ) + ) ) + ObjectSomeValuesFrom( ) + ) ) ) + SubClassOf( ObjectSomeValuesFrom( recipe:steps ObjectIntersectionOf( + AUTO:adjust%3B + ObjectSomeValuesFrom( ObjectSomeValuesFrom( AUTO:salt ) ) + ObjectSomeValuesFrom( ObjectSomeValuesFrom( ) ) + ObjectSomeValuesFrom( ObjectIntersectionOf( + ObjectSomeValuesFrom( ) + DataHasValue( recipe:state "powder" ) + ) ) + ObjectSomeValuesFrom( ObjectIntersectionOf( + ObjectSomeValuesFrom( AUTO:adjusted ) + DataHasValue( recipe:state "to own tastes" ) + ) ) + ObjectSomeValuesFrom( AUTO:none ) + ) ) ) + SubClassOf( ObjectSomeValuesFrom( recipe:steps ObjectIntersectionOf( + AUTO:cook + ObjectSomeValuesFrom( ObjectSomeValuesFrom( AUTO:noodles ) ) + ObjectSomeValuesFrom( ObjectSomeValuesFrom( AUTO:noodles ) ) + ObjectSomeValuesFrom( AUTO:pan ) + ) ) ) + SubClassOf( ObjectSomeValuesFrom( recipe:steps ObjectIntersectionOf( + AUTO:mix%3B + ObjectSomeValuesFrom( ObjectSomeValuesFrom( ) ) + ObjectSomeValuesFrom( ObjectSomeValuesFrom( AUTO:noodles ) ) + ObjectSomeValuesFrom( ObjectSomeValuesFrom( ) ) + ObjectSomeValuesFrom( ObjectSomeValuesFrom( AUTO:noodles ) ) + ObjectSomeValuesFrom( AUTO:none ) + ) ) ) +) \ No newline at end of file diff --git a/tests/inputs/recipe.yaml b/tests/inputs/recipe.yaml new file mode 100644 index 0000000..792a513 --- /dev/null +++ b/tests/inputs/recipe.yaml @@ -0,0 +1,535 @@ +name: recipe-template +description: A template for food recipes +title: Food Recipe Template +id: https://w3id.org/ontogpt/recipe +imports: +- linkml:types +- recipe_core +license: https://creativecommons.org/publicdomain/zero/1.0/ +prefixes: + linkml: + prefix_prefix: linkml + prefix_reference: https://w3id.org/linkml/ + recipe: + prefix_prefix: recipe + prefix_reference: http://w3id.org/ontogpt/recipe/ + FOODON: + prefix_prefix: FOODON + prefix_reference: http://purl.obolibrary.org/obo/FOODON_ + UO: + prefix_prefix: UO + prefix_reference: http://purl.obolibrary.org/obo/UO_ + dcterms: + prefix_prefix: dcterms + prefix_reference: http://purl.org/dc/terms/ + HANCESTRO: + prefix_prefix: HANCESTRO + prefix_reference: http://purl.obolibrary.org/obo/HANCESTRO_ + BFO: + prefix_prefix: BFO + prefix_reference: http://purl.obolibrary.org/obo/BFO_ + AUTO: + prefix_prefix: AUTO + prefix_reference: http://example.org/auto/ + RO: + prefix_prefix: RO + prefix_reference: http://purl.obolibrary.org/obo/RO_ +default_prefix: recipe +default_range: string +classes: + Recipe: + name: Recipe + annotations: + owl: + tag: owl + value: Class + owl.template: + tag: owl.template + value: "EquivalentClasses(\n {{url}}\n ObjectIntersectionOf(\n recipe:Recipe\n\ + \ \n {% for step in steps %}\n ObjectSomeValuesFrom(\n recipe:steps\n\ + \ {{tr(step)}}\n )\n {% endfor %}\n {% for ingredient in ingredients\ + \ %}\n ObjectSomeValuesFrom(\n FOODON:00002420\n {{tr(ingredient)}}\n\ + \ )\n {% endfor %}\n )\n)\n" + from_schema: https://w3id.org/ontogpt/recipe + close_mappings: + - FOODON:00004081 + attributes: + url: + name: url + annotations: + prompt.ignore: + tag: prompt.ignore + value: 'True' + from_schema: https://w3id.org/ontogpt/recipe + slot_uri: rdf:Resource + identifier: true + alias: url + owner: Recipe + domain_of: + - Recipe + range: uriorcurie + label: + name: label + annotations: + owl: + tag: owl + value: AnnotationProperty, AnnotationAssertion + description: the name of the recipe + from_schema: https://w3id.org/ontogpt/recipe + slot_uri: rdfs:label + alias: label + owner: Recipe + domain_of: + - Recipe + - NamedEntity + range: string + description: + name: description + annotations: + owl: + tag: owl + value: AnnotationProperty, AnnotationAssertion + description: a brief textual description of the recipe + from_schema: https://w3id.org/ontogpt/recipe + slot_uri: dcterms:description + alias: description + owner: Recipe + domain_of: + - Recipe + range: string + categories: + name: categories + annotations: + owl: + tag: owl + value: AnnotationAssertion + description: a semicolon separated list of the categories to which this recipe + belongs + from_schema: https://w3id.org/ontogpt/recipe + slot_uri: dcterms:subject + multivalued: true + alias: categories + owner: Recipe + domain_of: + - Recipe + range: RecipeCategory + ingredients: + name: ingredients + annotations: + owl: + tag: owl + value: ObjectProperty, ObjectSomeValuesFrom + description: a semicolon separated list of the ingredients plus quantities + of the recipe + from_schema: https://w3id.org/ontogpt/recipe + slot_uri: FOODON:00002420 + multivalued: true + alias: ingredients + owner: Recipe + domain_of: + - Recipe + range: Ingredient + steps: + name: steps + annotations: + owl: + tag: owl + value: ObjectProperty, ObjectSomeValuesFrom + description: a semicolon separated list of the individual steps involved in + this recipe + from_schema: https://w3id.org/ontogpt/recipe + multivalued: true + alias: steps + owner: Recipe + domain_of: + - Recipe + range: Step + tree_root: true + Ingredient: + name: Ingredient + annotations: + owl: + tag: owl + value: IntersectionOf + from_schema: https://w3id.org/ontogpt/recipe + is_a: CompoundExpression + attributes: + food_item: + name: food_item + annotations: + owl: + tag: owl + value: ObjectProperty, ObjectSomeValuesFrom + description: the food item + from_schema: https://w3id.org/ontogpt/recipe + alias: food_item + owner: Ingredient + domain_of: + - Ingredient + range: FoodItem + amount: + name: amount + annotations: + owl: + tag: owl + value: ObjectProperty, ObjectSomeValuesFrom + description: the quantity of the ingredient, e.g. 2 lbs + from_schema: https://w3id.org/ontogpt/recipe + alias: amount + owner: Ingredient + domain_of: + - Ingredient + range: Quantity + class_uri: FOODON:00004085 + Quantity: + name: Quantity + from_schema: https://w3id.org/ontogpt/recipe + is_a: CompoundExpression + attributes: + value: + name: value + description: the value of the quantity + from_schema: https://w3id.org/ontogpt/recipe + alias: value + owner: Quantity + domain_of: + - Quantity + range: string + unit: + name: unit + description: the unit of the quantity, e.g. grams, cups, etc. + from_schema: https://w3id.org/ontogpt/recipe + alias: unit + owner: Quantity + domain_of: + - Quantity + range: Unit + Step: + name: Step + annotations: + owl: + tag: owl + value: IntersectionOf + from_schema: https://w3id.org/ontogpt/recipe + is_a: CompoundExpression + attributes: + action: + name: action + description: the action taken in this step (e.g. mix, add) + from_schema: https://w3id.org/ontogpt/recipe + alias: action + owner: Step + domain_of: + - Step + range: Action + inputs: + name: inputs + annotations: + owl: + tag: owl + value: ObjectProperty, ObjectSomeValuesFrom + description: a semicolon separated list of the inputs of this step + from_schema: https://w3id.org/ontogpt/recipe + slot_uri: RO:0002233 + multivalued: true + alias: inputs + owner: Step + domain_of: + - Step + range: FoodItem + outputs: + name: outputs + annotations: + owl: + tag: owl + value: ObjectProperty, ObjectSomeValuesFrom + description: a semicolon separated list of the outputs of this step + from_schema: https://w3id.org/ontogpt/recipe + slot_uri: RO:0002234 + multivalued: true + alias: outputs + owner: Step + domain_of: + - Step + range: FoodItem + utensils: + name: utensils + annotations: + owl: + tag: owl + value: ObjectProperty, ObjectSomeValuesFrom + description: the kitchen utensil used in this step (e.g. pan, bowl) + from_schema: https://w3id.org/ontogpt/recipe + slot_uri: RO:0002500 + multivalued: true + alias: utensils + owner: Step + domain_of: + - Step + range: UtensilType + class_uri: FOODON:00004087 + FoodItem: + name: FoodItem + annotations: + owl: + tag: owl + value: IntersectionOf + from_schema: https://w3id.org/ontogpt/recipe + is_a: CompoundExpression + attributes: + food: + name: food + annotations: + owl: + tag: owl + value: ObjectProperty, ObjectSomeValuesFrom + description: the food item + from_schema: https://w3id.org/ontogpt/recipe + slot_uri: BFO:0000051 + alias: food + owner: FoodItem + domain_of: + - FoodItem + range: FoodType + state: + name: state + annotations: + owl: + tag: owl + value: DataProperty, DataHasValue + description: the state of the food item (e.g. chopped, diced) + from_schema: https://w3id.org/ontogpt/recipe + alias: state + owner: FoodItem + domain_of: + - FoodItem + range: string + FoodType: + name: FoodType + id_prefixes: + - dbpediaont + - FOODON + annotations: + annotators: + tag: annotators + value: sqlite:obo:foodon, sqlite:obo:dbpediaont + from_schema: https://w3id.org/ontogpt/recipe + is_a: NamedEntity + attributes: + id: + name: id + annotations: + prompt.skip: + tag: prompt.skip + value: 'true' + description: A unique identifier for the named entity + comments: + - this is populated during the grounding and normalization step + from_schema: http://w3id.org/ontogpt/core + identifier: true + alias: id + owner: FoodType + domain_of: + - NamedEntity + - Publication + range: string + label: + name: label + description: The label (name) of the named thing + from_schema: http://w3id.org/ontogpt/core + aliases: + - name + alias: label + owner: FoodType + domain_of: + - Recipe + - NamedEntity + range: string + RecipeCategory: + name: RecipeCategory + id_prefixes: + - dbpediaont + - FOODON + - HANCESTRO + annotations: + annotators: + tag: annotators + value: sqlite:obo:foodon, sqlite:obo:dbpediaont + from_schema: https://w3id.org/ontogpt/recipe + is_a: NamedEntity + attributes: + id: + name: id + annotations: + prompt.skip: + tag: prompt.skip + value: 'true' + description: A unique identifier for the named entity + comments: + - this is populated during the grounding and normalization step + from_schema: http://w3id.org/ontogpt/core + identifier: true + alias: id + owner: RecipeCategory + domain_of: + - FoodType + - NamedEntity + - Publication + range: string + label: + name: label + description: The label (name) of the named thing + from_schema: http://w3id.org/ontogpt/core + aliases: + - name + alias: label + owner: RecipeCategory + domain_of: + - Recipe + - FoodType + - NamedEntity + range: string + Action: + name: Action + id_prefixes: + - dbpediaont + - FOODON + annotations: + annotators: + tag: annotators + value: sqlite:obo:dbpediaont, sqlite:obo:foodon + from_schema: https://w3id.org/ontogpt/recipe + is_a: NamedEntity + attributes: + id: + name: id + annotations: + prompt.skip: + tag: prompt.skip + value: 'true' + description: A unique identifier for the named entity + comments: + - this is populated during the grounding and normalization step + from_schema: http://w3id.org/ontogpt/core + identifier: true + alias: id + owner: Action + domain_of: + - FoodType + - RecipeCategory + - NamedEntity + - Publication + range: string + label: + name: label + description: The label (name) of the named thing + from_schema: http://w3id.org/ontogpt/core + aliases: + - name + alias: label + owner: Action + domain_of: + - Recipe + - FoodType + - RecipeCategory + - NamedEntity + range: string + UtensilType: + name: UtensilType + id_prefixes: + - dbpediaont + - FOODON + annotations: + annotators: + tag: annotators + value: sqlite:obo:dbpediaont, sqlite:obo:foodon + from_schema: https://w3id.org/ontogpt/recipe + is_a: NamedEntity + attributes: + id: + name: id + annotations: + prompt.skip: + tag: prompt.skip + value: 'true' + description: A unique identifier for the named entity + comments: + - this is populated during the grounding and normalization step + from_schema: http://w3id.org/ontogpt/core + identifier: true + alias: id + owner: UtensilType + domain_of: + - FoodType + - RecipeCategory + - Action + - NamedEntity + - Publication + range: string + label: + name: label + description: The label (name) of the named thing + from_schema: http://w3id.org/ontogpt/core + aliases: + - name + alias: label + owner: UtensilType + domain_of: + - Recipe + - FoodType + - RecipeCategory + - Action + - NamedEntity + range: string + Unit: + name: Unit + id_prefixes: + - UO + - NCIT + - dbpediaont + annotations: + annotators: + tag: annotators + value: sqlite:obo:uo, sqlite:obo:dbpediaont, sqlite:obo:foodon + from_schema: https://w3id.org/ontogpt/recipe + is_a: NamedEntity + attributes: + id: + name: id + annotations: + prompt.skip: + tag: prompt.skip + value: 'true' + description: A unique identifier for the named entity + comments: + - this is populated during the grounding and normalization step + from_schema: http://w3id.org/ontogpt/core + identifier: true + alias: id + owner: Unit + domain_of: + - FoodType + - RecipeCategory + - Action + - UtensilType + - NamedEntity + - Publication + range: string + label: + name: label + description: The label (name) of the named thing + from_schema: http://w3id.org/ontogpt/core + aliases: + - name + alias: label + owner: Unit + domain_of: + - Recipe + - FoodType + - RecipeCategory + - Action + - UtensilType + - NamedEntity + range: string +source_file: ../ontogpt/src/ontogpt/templates/recipe.yaml + diff --git a/tests/inputs/recipe_core.yaml b/tests/inputs/recipe_core.yaml new file mode 100644 index 0000000..bfea369 --- /dev/null +++ b/tests/inputs/recipe_core.yaml @@ -0,0 +1,126 @@ +id: http://w3id.org/ontogpt/core +name: core +title: AI core Template +license: https://creativecommons.org/publicdomain/zero/1.0/ +prefixes: + linkml: https://w3id.org/linkml/ + core: http://w3id.org/ontogpt/core/ +description: Core upper level + +default_prefix: core +default_range: string + +imports: + - linkml:types + +classes: + Any: + class_uri: linkml:Any + + ExtractionResult: + description: >- + A result of extracting knowledge on text + attributes: + input_id: + input_title: + input_text: + raw_completion_output: + prompt: + extracted_object: + description: >- + The complex objects extracted from the text + range: Any + inlined: true + named_entities: + description: >- + Named entities extracted from the text + range: Any + multivalued: true + inlined: true + inlined_as_list: true + + NamedEntity: + abstract: true + attributes: + id: + description: >- + A unique identifier for the named entity + comments: + - this is populated during the grounding and normalization step + identifier: true + annotations: + prompt.skip: "true" + label: + aliases: + - name + description: >- + The label (name) of the named thing + range: string + + CompoundExpression: + abstract: true + + Triple: + abstract: true + description: Abstract parent for Relation Extraction tasks + is_a: CompoundExpression + attributes: + subject: + range: NamedEntity + predicate: + range: RelationshipType + object: + range: NamedEntity + qualifier: + range: string + description: >- + A qualifier for the statements, e.g. "NOT" for negation + subject_qualifier: + range: NamedEntity + description: >- + An optional qualifier or modifier for the subject of the statement, e.g. "high dose" or "intravenously administered" + object_qualifier: + range: NamedEntity + description: >- + An optional qualifier or modifier for the object of the statement, e.g. "severe" or "with additional complications" + + TextWithTriples: + attributes: + publication: + range: Publication + inlined: true + annotations: + prompt.skip: "true" + triples: + range: Triple + multivalued: true + inlined: true + inlined_as_list: true + + RelationshipType: + is_a: NamedEntity + id_prefixes: + - RO + - biolink + + Publication: + attributes: + id: + description: >- + The publication identifier + title: + description: >- + The title of the publication + abstract: + description: >- + The abstract of the publication + combined_text: + full_text: + description: >- + The full text of the publication + + AnnotatorResult: + attributes: + subject_text: + object_id: + object_text: diff --git a/tests/inputs/test-treatments.yaml b/tests/inputs/test-treatments.yaml new file mode 100644 index 0000000..64805b3 --- /dev/null +++ b/tests/inputs/test-treatments.yaml @@ -0,0 +1,28 @@ +papers: + title: >- + Stiff-Person Syndrome: A Treatment Update and New Directions + abstract: >- + Stiff-person syndrome (SPS) is a rare and disabling central nervous system disorder with no satisfactory treatment. Muscle rigidity, sporadic muscle spasms, and chronic muscle pain characterize SPS. SPS is strongly correlated with autoimmune diseases, and it is usual to find high titers of antibodies against acid decarboxylase (GAD65). Due to its highly disabling nature and complicated treatment, we aim to create a treatment protocol through a narrative review of currently available treatments that show efficacy. We expect to facilitate management based on treatment responses ranging from first-line medication to refractory medication. We conducted a medical subject heading (MeSH) strategy. We used the term SPS with the subheading treatment: "Stiff-Person Syndrome/Therapy" [MeSH]. An initial data gathering of 270 papers came out with the initial research. After using the inclusion criteria, we had 159 articles. We excluded 31 papers for being either systematic reviews, literature reviews, or meta-analysis. From the 128 remaining articles, we excluded another 104 papers because the extraction of the data was not possible or the study outcome did not meet our demands. There are two main treatments for SPS: GABAergic (gamma-aminobutyric acid) therapy and immunotherapy. For treatment, we suggest starting with benzodiazepines as first-line treatment. We recommend adding levetiracetam or pregabalin if symptoms persist. As second-line therapy, we recommend oral baclofen over rituximab and tacrolimus. We also suggest rituximab over tacrolimus. For patients with refractory treatment, we can use intrathecal baclofen, intravenous immunoglobulin (IVIG), or plasmapheresis. We conclude that intrathecal baclofen and IVIG are more effective than plasmapheresis in patients with refractory symptoms. Propofol may be used as a bridge - temporary therapy before initiating a permanent treatment. + diseases: + - Stiff-Person Syndrome + drugs: + - GABAergic therapy + - immunotherapy + - benzodiazepines + - levetiracetam + - pregabalin + - oral baclofen + - rituximab + - tacrolimus + - intrathecal baclofen + - intravenous immunoglobulin + - plasmapheresis + - propofol + relative_efficacy: + more_effective: + - intravenous immunoglobulin + - intrathecal baclofen + less_effective: + - plasmapheresis + symptoms: + - refractory symptoms \ No newline at end of file diff --git a/tests/output/chromo.schema.owl.ttl b/tests/output/chromo.schema.owl.ttl index 43bc363..a11a8e0 100644 --- a/tests/output/chromo.schema.owl.ttl +++ b/tests/output/chromo.schema.owl.ttl @@ -23,7 +23,7 @@ chromoschema:GenomeBuild, chromoschema:OrganismTaxon ; dcterms:license "https://creativecommons.org/publicdomain/zero/1.0/" ; - linkml:generation_date "2022-11-21T09:02:28" ; + linkml:generation_date "2023-01-05T17:07:03" ; linkml:metamodel_version "1.7.0" ; linkml:source_file "chromo.yaml" ; linkml:source_file_date "2022-01-12T21:11:50" ; @@ -44,18 +44,18 @@ chromoschema:ChromosomePartCollection a owl:Class, linkml:ClassDefinition ; rdfs:label "ChromosomePartCollection" ; rdfs:subClassOf [ a owl:Restriction ; - owl:allValuesFrom chromoschema:Genome ; + owl:allValuesFrom chromoschema:OrganismTaxon ; owl:onProperty dcterms:hasPart ], [ a owl:Restriction ; - owl:allValuesFrom chromoschema:OrganismTaxon ; + owl:allValuesFrom chromoschema:ChromosomePart ; + owl:onProperty dcterms:hasPart ], + [ a owl:Restriction ; + owl:allValuesFrom chromoschema:Genome ; owl:onProperty dcterms:hasPart ], [ a owl:Restriction ; owl:maxQualifiedCardinality 1 ; owl:onClass linkml:String ; - owl:onProperty chromoschema:name ], - [ a owl:Restriction ; - owl:allValuesFrom chromoschema:ChromosomePart ; - owl:onProperty dcterms:hasPart ] . + owl:onProperty chromoschema:name ] . chromoschema:StrandType a owl:Class, linkml:TypeDefinition ; @@ -206,24 +206,24 @@ chromoschema:Genome a owl:Class, linkml:ClassDefinition ; rdfs:label "Genome" ; rdfs:subClassOf [ a owl:Restriction ; - owl:maxQualifiedCardinality 1 ; - owl:onClass chromoschema:TaxonIdentifier ; - owl:onProperty RO:0002162 ], - [ a owl:Restriction ; - owl:allValuesFrom chromoschema:GenomeBuild ; - owl:onProperty chromoschema:previous_builds ], - [ a owl:Restriction ; owl:maxQualifiedCardinality 1 ; owl:onClass chromoschema:GenomeBuild ; owl:onProperty biolink:genome_build ], + [ a owl:Restriction ; + owl:onClass linkml:String ; + owl:onProperty chromoschema:id ; + owl:qualifiedCardinality 1 ], [ a owl:Restriction ; owl:onClass chromoschema:LabelType ; owl:onProperty rdfs:label ; owl:qualifiedCardinality 1 ], [ a owl:Restriction ; - owl:onClass linkml:String ; - owl:onProperty chromoschema:id ; - owl:qualifiedCardinality 1 ] ; + owl:allValuesFrom chromoschema:GenomeBuild ; + owl:onProperty chromoschema:previous_builds ], + [ a owl:Restriction ; + owl:maxQualifiedCardinality 1 ; + owl:onClass chromoschema:TaxonIdentifier ; + owl:onProperty RO:0002162 ] ; skos:definition """Represents a sequenced genome, one per species. Each genome can be associated with one or more builds""" . @@ -231,17 +231,17 @@ chromoschema:OrganismTaxon a owl:Class, linkml:ClassDefinition ; rdfs:label "OrganismTaxon" ; rdfs:subClassOf [ a owl:Restriction ; + owl:onClass linkml:String ; + owl:onProperty chromoschema:id ; + owl:qualifiedCardinality 1 ], + [ a owl:Restriction ; owl:maxQualifiedCardinality 1 ; owl:onClass chromoschema:LabelType ; owl:onProperty OIO:hasExactSynonym ], [ a owl:Restriction ; owl:maxQualifiedCardinality 1 ; owl:onClass chromoschema:LabelType ; - owl:onProperty rdfs:label ], - [ a owl:Restriction ; - owl:onClass linkml:String ; - owl:onProperty chromoschema:id ; - owl:qualifiedCardinality 1 ] ; + owl:onProperty rdfs:label ] ; skos:definition "Represents a species, e.g. Homo sapiens" ; skos:exactMatch biolink:OrganismTaxon . @@ -324,68 +324,68 @@ chromoschema:ChromosomePart a owl:Class, rdfs:label "ChromosomePart" ; rdfs:subClassOf [ a owl:Restriction ; owl:maxQualifiedCardinality 1 ; - owl:onClass chromoschema:TaxonIdentifier ; - owl:onProperty RO:0002162 ], - [ a owl:Restriction ; - owl:maxQualifiedCardinality 1 ; - owl:onClass chromoschema:BandDescriptor ; - owl:onProperty chromoschema:band_descriptor ], - [ a owl:Restriction ; - owl:onClass linkml:String ; - owl:onProperty chromoschema:id ; - owl:qualifiedCardinality 1 ], + owl:onClass chromoschema:ChromosomePart ; + owl:onProperty BFO:0000050 ], [ a owl:Restriction ; owl:maxQualifiedCardinality 1 ; - owl:onClass chromoschema:ChromosomeNameType ; - owl:onProperty chromoschema:chromosome_name ], + owl:onClass chromoschema:LabelType ; + owl:onProperty rdfs:label ], [ a owl:Restriction ; owl:allValuesFrom chromoschema:ChromosomePart ; owl:onProperty BFO:0000051 ], - [ a owl:Restriction ; - owl:maxQualifiedCardinality 1 ; - owl:onClass linkml:Integer ; - owl:onProperty gff:end ], [ a owl:Restriction ; owl:maxQualifiedCardinality 1 ; owl:onClass chromoschema:LocationType ; owl:onProperty BFO:0000050 ], - [ a owl:Restriction ; - owl:allValuesFrom linkml:String ; - owl:onProperty OIO:hasBroadSynonym ], [ a owl:Restriction ; owl:maxQualifiedCardinality 1 ; - owl:onClass linkml:Integer ; - owl:onProperty gff:start ], + owl:onClass chromoschema:GenomeBuild ; + owl:onProperty biolink:genome_build ], [ a owl:Restriction ; owl:maxQualifiedCardinality 1 ; - owl:onClass chromoschema:ChromosomePart ; - owl:onProperty BFO:0000050 ], + owl:onClass chromoschema:AutosomeVsSexChromosome ; + owl:onProperty chromoschema:somal_type ], + [ a owl:Restriction ; + owl:allValuesFrom linkml:Uriorcurie ; + owl:onProperty skos:exactMatch ], [ a owl:Restriction ; owl:maxQualifiedCardinality 1 ; owl:onClass chromoschema:SexChromosomeType ; owl:onProperty chromoschema:sex_chromosome_type ], [ a owl:Restriction ; owl:maxQualifiedCardinality 1 ; - owl:onClass chromoschema:GenomeBuild ; - owl:onProperty biolink:genome_build ], + owl:onClass chromoschema:ChromosomeNameType ; + owl:onProperty chromoschema:chromosome_name ], + [ a owl:Restriction ; + owl:allValuesFrom linkml:String ; + owl:onProperty OIO:hasBroadSynonym ], + [ a owl:Restriction ; + owl:onClass linkml:String ; + owl:onProperty chromoschema:id ; + owl:qualifiedCardinality 1 ], + [ a owl:Restriction ; + owl:maxQualifiedCardinality 1 ; + owl:onClass chromoschema:BandDescriptor ; + owl:onProperty chromoschema:band_descriptor ], + [ a owl:Restriction ; + owl:allValuesFrom linkml:String ; + owl:onProperty OIO:hasExactSynonym ], [ a owl:Restriction ; owl:maxQualifiedCardinality 1 ; owl:onClass chromoschema:EntityType ; owl:onProperty rdf:type ], - [ a owl:Restriction ; - owl:allValuesFrom linkml:Uriorcurie ; - owl:onProperty skos:exactMatch ], [ a owl:Restriction ; owl:maxQualifiedCardinality 1 ; - owl:onClass chromoschema:LabelType ; - owl:onProperty rdfs:label ], + owl:onClass linkml:Integer ; + owl:onProperty gff:start ], [ a owl:Restriction ; owl:maxQualifiedCardinality 1 ; - owl:onClass chromoschema:AutosomeVsSexChromosome ; - owl:onProperty chromoschema:somal_type ], + owl:onClass linkml:Integer ; + owl:onProperty gff:end ], [ a owl:Restriction ; - owl:allValuesFrom linkml:String ; - owl:onProperty OIO:hasExactSynonym ] ; + owl:maxQualifiedCardinality 1 ; + owl:onClass chromoschema:TaxonIdentifier ; + owl:onProperty RO:0002162 ] ; skos:definition "A Chromosome or a part of a chromosome (includes whole chromosomes, arms, and bands)" ; skos:note """OWL Notes: when translated to OWL, instances of this class will be treated as OWL classes, with the superclass determined by the type field""", @@ -401,13 +401,13 @@ chromoschema:GenomeBuild a owl:Class, linkml:ClassDefinition ; rdfs:label "GenomeBuild" ; rdfs:subClassOf [ a owl:Restriction ; - owl:maxQualifiedCardinality 1 ; - owl:onClass chromoschema:LabelType ; - owl:onProperty rdfs:label ], - [ a owl:Restriction ; owl:onClass linkml:String ; owl:onProperty chromoschema:id ; - owl:qualifiedCardinality 1 ] ; + owl:qualifiedCardinality 1 ], + [ a owl:Restriction ; + owl:maxQualifiedCardinality 1 ; + owl:onClass chromoschema:LabelType ; + owl:onProperty rdfs:label ] ; skos:closeMatch edam:operation_0525 ; skos:definition "Represents a specific build of a sequenced genome" . diff --git a/tests/test_compliance/test_owl_dumper.py b/tests/test_compliance/test_owl_dumper.py index e28d760..b03b9ce 100644 --- a/tests/test_compliance/test_owl_dumper.py +++ b/tests/test_compliance/test_owl_dumper.py @@ -20,7 +20,7 @@ from rdflib.namespace import Namespace, SKOS, DCTERMS from linkml_owl.dumpers.owl_dumper import OWLDumper from funowl import Axiom, AnnotationAssertion, Literal, SubClassOf, ObjectSomeValuesFrom, \ - ObjectAllValuesFrom, ObjectUnionOf, EquivalentClasses, ObjectIntersectionOf, Annotation + ObjectAllValuesFrom, ObjectUnionOf, EquivalentClasses, ObjectIntersectionOf, Annotation, DataHasValue from linkml_owl.util.trim_yaml import trim_yaml from tests import INPUT_DIR, OUTPUT_DIR @@ -88,6 +88,7 @@ def test_owl_dumper(self): X = Namespace("http://example.org/") BFO = Namespace("http://purl.obolibrary.org/obo/BFO_") IAO = Namespace("http://purl.obolibrary.org/obo/IAO_") + SCHEMA = Namespace("http://schema.org/") dumper = OWLDumper() sv = SchemaView(SCHEMA_IN) schema = sv.schema @@ -161,6 +162,11 @@ def add_check(*args, **kwargs): [py_mod.PartOnly('x:a', part_of='x:b')], [SubClassOf(X.a, ObjectAllValuesFrom(BFO['0000050'], X.b))], "As above, but with universal restrictions") + add_check("SubClassOf DataHasValue", + [py_mod.HasName('x:a', has_name='Violet')], + #[SubClassOf(X.a, DataHasValue(SCHEMA['has_name'], Literal('Violet')))], + [], + "SubClassOf DataHasValue") add_check("SubClassOf SomeValuesFrom plus label", [py_mod.Part('x:a', label='foo', part_of='x:b')], [AnnotationAssertion(RDFS.label, X.a, Literal("foo")), @@ -189,11 +195,6 @@ def add_check(*args, **kwargs): [EquivalentClasses(X.a, ObjectIntersectionOf(X.b, X.c))], """The slot is interpreted as a parent class, and all slot values with a IntersectionOf annotation are collected to make a IntersectionOf expression""") - add_check("EquivalentTo IntersectionOf with axiom annotation", - [py_mod.EquivIntersectionWithAxiomAnnotation('x:a', operands=['x:b', 'x:c'], logical_definition_source=["Me"])], - [EquivalentClasses(X.a, ObjectIntersectionOf(X.b, X.c), - annotations=[Annotation(DCTERMS.source, Literal("Me"))])], - """as above, with axiom annotation""") add_check("EquivalentTo Genus and SomeValuesFrom", [py_mod.EquivGenusAndPartOf('x:a', subclass_of=['X:genus'], @@ -212,6 +213,18 @@ def add_check(*args, **kwargs): EquivalentClasses(X.NewClass, ObjectIntersectionOf(X.IN, ObjectSomeValuesFrom(BFO['0000050'], X.H)))], """Label auto-added using string_serialization""") + add_check("EquivalentTo IntersectionOf with axiom annotation", + [py_mod.EquivIntersectionWithAxiomAnnotation('x:a', operands=['x:b', 'x:c'], + logical_definition_source=["Me"])], + [EquivalentClasses(X.a, ObjectIntersectionOf(X.b, X.c), + annotations=[Annotation(DCTERMS.source, Literal("Me"))])], + """as above, with axiom annotation""") + #add_check("EquivalentTo with Singleton IntersectionOf", + # [py_mod.EquivGenusAndPartOf('x:a', + # subclass_of=['X:genus'], + # part_of=[])], + # [EquivalentClasses(X.a, X.genus)], + # """An IntersectionOf with one element is converted to a singleton""") add_check("Hidden GCI", [py_mod.EquivGenusAndPartOf('x:a', subclass_of=['X:genus'], diff --git a/tests/test_examples/test_recipe.py b/tests/test_examples/test_recipe.py new file mode 100644 index 0000000..5398d67 --- /dev/null +++ b/tests/test_examples/test_recipe.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +import logging +import os +import unittest +from linkml.generators.pythongen import PythonGenerator +from linkml_runtime import SchemaView + +from linkml_owl.util.loader_wrapper import load_structured_file +from linkml_owl.dumpers.owl_dumper import OWLDumper +from funowl.converters.functional_converter import to_python + +from tests import INPUT_DIR, OUTPUT_DIR + +"""Test the module can be imported.""" + +SCHEMA_IN = os.path.join(INPUT_DIR, 'recipe.yaml') +DATA_IN = os.path.join(INPUT_DIR, 'recipe.data.yaml') +OWL_OUT = os.path.join(OUTPUT_DIR, 'recipe.ofn') +EXPECTED = os.path.join(INPUT_DIR, 'recipe.expected.ofn') + + +class TestRecipeExample(unittest.TestCase): + """Test case using a fantasy RPG example.""" + + def test_build(self): + """ + Test creation of an OWL TBox using Recipe template. + """ + sv = SchemaView(SCHEMA_IN) + python_module = PythonGenerator(SCHEMA_IN).compile_module() + data = load_structured_file(DATA_IN, schemaview=sv, python_module=python_module) + dumper = OWLDumper() + dumper.schemaview = sv + doc = dumper.to_ontology_document(data, schema=sv.schema) + with open(OWL_OUT, 'w') as stream: + stream.write(str(doc)) + doc_rt = to_python(str(doc)) + axioms = doc_rt.ontology.axioms + logging.info(f'AXIOMS={len(axioms)}') + assert len(axioms) > 5 + # compare with expected output + doc_expected = to_python(str(EXPECTED)) + assert len(axioms) == len(doc_expected.ontology.axioms) + self.assertCountEqual(axioms, doc_expected.ontology.axioms) +