From 8c26e8fd7b9333190f64eec3afd5f05de8e9fcee Mon Sep 17 00:00:00 2001 From: Chris Mungall Date: Fri, 21 Jul 2023 07:59:08 -0700 Subject: [PATCH] Tools to analyze logical definitions (#610) * Adding a notebook for diff-via-mappings. * Tools to analyze logical definitions * Adding matrix output for logical definitions * lint * reran * normalize ordering * Extending signature of logical_definitions method to allow additional filtering. Adding new docstrings and tests. --- docs/cli.rst | 2 +- docs/glossary.rst | 4 + docs/guide/logical-definitions.rst | 190 ++ docs/search-syntax.rst | 2 +- notebooks/Commands/DiffViaMappings.ipynb | 2121 +++++++++++++++++ notebooks/Commands/LogicalDefinitions.ipynb | 1002 ++++++++ src/oaklib/cli.py | 367 ++- src/oaklib/conf/obograph-style.json | 3 + src/oaklib/datamodels/search.py | 2 +- src/oaklib/datamodels/vocabulary.py | 3 + .../aggregator/aggregator_implementation.py | 19 +- .../obograph/obograph_implementation.py | 22 +- .../pronto/pronto_implementation.py | 39 +- .../simpleobo/simple_obo_implementation.py | 43 +- .../simpleobo/simple_obo_parser.py | 24 +- .../sqldb/sql_implementation.py | 31 +- .../ubergraph/ubergraph_implementation.py | 18 +- .../interfaces/basic_ontology_interface.py | 4 + src/oaklib/interfaces/obograph_interface.py | 35 +- src/oaklib/interfaces/patcher_interface.py | 8 +- src/oaklib/interfaces/subsetter_interface.py | 2 +- src/oaklib/io/streaming_obo_writer.py | 15 +- src/oaklib/io/streaming_writer.py | 14 +- src/oaklib/mappers/base_mapper.py | 8 +- .../mappers/ontology_metadata_mapper.py | 4 + src/oaklib/selector.py | 13 +- src/oaklib/utilities/axioms/__init__.py | 0 .../axioms/logical_definition_analyzer.py | 111 + .../axioms/logical_definition_summarizer.py | 224 ++ .../axioms/logical_definition_utilities.py | 59 + src/oaklib/utilities/lexical/patternizer.py | 284 +++ .../utilities/mapping/cross_ontology_diffs.py | 3 + src/oaklib/utilities/obograph_utils.py | 31 +- tests/test_implementations/__init__.py | 21 +- tests/test_implementations/test_aggregator.py | 12 + tests/test_implementations/test_pronto.py | 5 +- tests/test_implementations/test_simple_obo.py | 7 + .../test_logical_definition_summarizer.py | 104 + tests/test_utilities/test_obograph_utils.py | 19 + tests/test_utilities/test_patternizer.py | 95 + 40 files changed, 4823 insertions(+), 147 deletions(-) create mode 100644 docs/guide/logical-definitions.rst create mode 100644 notebooks/Commands/DiffViaMappings.ipynb create mode 100644 notebooks/Commands/LogicalDefinitions.ipynb create mode 100644 src/oaklib/utilities/axioms/__init__.py create mode 100644 src/oaklib/utilities/axioms/logical_definition_analyzer.py create mode 100644 src/oaklib/utilities/axioms/logical_definition_summarizer.py create mode 100644 src/oaklib/utilities/axioms/logical_definition_utilities.py create mode 100644 src/oaklib/utilities/lexical/patternizer.py create mode 100644 tests/test_utilities/test_logical_definition_summarizer.py create mode 100644 tests/test_utilities/test_patternizer.py diff --git a/docs/cli.rst b/docs/cli.rst index d49fe59ac..1adf0422d 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -52,7 +52,7 @@ Term Lists Many commands take a *term* or a *list of terms* as their primary argument. These are typically one of: - a :ref:`CURIE` such as :code:`UBERON:0000955` -- a :ref:`search-syntax` term, which is either: +- a :ref:`search_syntax` term, which is either: - an exact match to a label; for example "limb" or "plasma membrane" - a compound search term such as :code:`t~limb` which finds terms with partial matches to limb diff --git a/docs/glossary.rst b/docs/glossary.rst index 22d1d4e17..5721e9329 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -230,6 +230,10 @@ For a deeper dive into some of these concepts, see the :ref:`guide`. map to *multiple* triples when following the OWL RDF serialization. An example is the relationship "finger part-of hand", which in OWL is represented using a :term:`Existential Restriction` that maps to 4 triples. + Logical Definition + A :term:`Logical Definition` is a particular kind of :term:`Axiom` that is used to provide a + definition of a term that is *computable*. See :ref:`logical_definitions`. + Subset An :term:`Ontology Element` that represents a named collection of elements, typically grouped for some purpose. Subsets are commonly used in ontologies like the :term:`Gene Ontology`. diff --git a/docs/guide/logical-definitions.rst b/docs/guide/logical-definitions.rst new file mode 100644 index 000000000..7078d19cc --- /dev/null +++ b/docs/guide/logical-definitions.rst @@ -0,0 +1,190 @@ +.. _logical_definitions: + +Logical Definitions +=================== + +:term:`Logical Definitions` are special kinds of :term:`Axioms` that +define a term in terms of other terms, in a way that is *computable*. OAK provides ways of +operating structurally on logical definitions. Logical definitions may also be used by +:term:`Reasoners` to infer :term:`Entailed` axioms. + +Typically, logical definitions are not directly used by consumers of ontologies, so +this section can be skipped by many OAK users. However, if you are interested in +generating, analyzing, summarizing, or other kinds of operations on logical definitions, +this section is intended to provide an overview of the basic concepts. + +Logical Definitions in OWL +-------------------------- + +In OWL, logical definitions are represented using the +`EquivalentClasses axiom `_. +But note that not every equivalence axioms is a logical definition, according to how +we use the term in OAK. For example, an equivalence axiom connects two *named classes* +doesn't really function to define either class, as this would be circular. + +For purposes here, we consider any equivalence axiom between a *named class* on the left +hand side and an *anonymous class expression* on the right hand side to be a logical definition. + +These equivalence axioms can be viewed in ontology browsers such as OLS. For example, +the Uberon [fingernail](http://purl.obolibrary.org/obo/UBERON_0009565) class, has an +equivalence axiom to the expression: + +.. code-block:: + + Equivalent to + (nail and part of (RO) some manual digit) + + +Genus-differentia form logical definitions +------------------------------------------ + +OAK includes dedicated functionality for logical definitions that follow *genus-differentia* +form. These are sometimes known as *Aristotelian definitions*. A genus-differentia definition +takes the form: + +``a C is a G that D`` + +where ``C`` is the defined class, ``G`` is the genus, and ``D`` is the differentia or *differentiating +characteristics*. The differentiating characteristics are typically a list of :term:`Predicate` and "Filler" +class pairs. + +For example: + +``a fingernail is a nail that is part of a finger`` + +Here we are defining a fingernail (defined class) in terms of a specialization of a parent class (nail, the genus) +based on a differentiating characteristics (differentia) based on parthood (the predicate) and +a specific "filler" (finger). + +This seems trivial but in fact these kinds of definitions -- when provided in computable +form -- can be used to automate a large amount of ontology development. And they can +be useful for queries over an ontology too. + +It is generally considered good practice for textual definitions to be consistent with +the computable genus-differentia form. + +The Obo Graphs Model +-------------------- + +The :term:`OBO Graphs` data model includes a data structure / class for representing +logical definitions in genus-differentia form. It limits the differentiae to be a set +of existential restrictions, with no nesting. + +Currently most operations in OAK that deal with logical definitions expect them to be +in this form. This can sometimes be limiting if you wish to operate over more complicated +OWL axioms. We may provide support for this in the future, but for now the simple form +provided in the OBO Graphs data model works for a large number of ontologies. The simple +form is often recommended because mistakes are far more common when more complicated +structures are used. + +Querying for logical definitions in OAK +---------------------------------------- + +.. code-block:: bash + + $ runoak -i sqlite:obo:uberon logical-definitions .desc//p=i nail + +By default, results are returned in YAML: + +.. code-block:: yaml + + definedClassId: UBERON:0009565 + genusIds: + - UBERON:0001705 + restrictions: + - fillerId: UBERON:0002389 + propertyId: BFO:0000050 + ... + +You can specify `-O csv` to get it in tabular form: + +.. csv-table:: uberon nail logical definitions + :header: definedClassId,definedClassId_label,genusIds,genusIds_label,restrictions,restrictionsPropertyIds,restrictionsPropertyIds_label,restrictionsFillerIds,restrictionsFillerIds_label + + UBERON:0009565,nail of manual digit,UBERON:0001705,nail,BFO:0000050=UBERON:0002389,BFO:0000050,part of,UBERON:0002389,manual digit + UBERON:0009567,nail of pedal digit,UBERON:0001705,nail,BFO:0000050=UBERON:0001466,BFO:0000050,part of,UBERON:0001466,pedal digit + UBERON:0011273,nail of manual digit 1,UBERON:0001705,nail,BFO:0000050=UBERON:0001463,BFO:0000050,part of,UBERON:0001463,manual digit 1 + UBERON:0011274,nail of manual digit 2,UBERON:0001705,nail,BFO:0000050=UBERON:0003622,BFO:0000050,part of,UBERON:0003622,manual digit 2 + UBERON:0011275,nail of manual digit 3,UBERON:0001705,nail,BFO:0000050=UBERON:0003623,BFO:0000050,part of,UBERON:0003623,manual digit 3 + UBERON:0011276,nail of manual digit 4,UBERON:0001705,nail,BFO:0000050=UBERON:0003624,BFO:0000050,part of,UBERON:0003624,manual digit 4 + UBERON:0011277,nail of manual digit 5,UBERON:0001705,nail,BFO:0000050=UBERON:0003625,BFO:0000050,part of,UBERON:0003625,manual digit 5 + UBERON:0011278,nail of pedal digit 1,UBERON:0001705,nail,BFO:0000050=UBERON:0003631,BFO:0000050,part of,UBERON:0003631,pedal digit 1 + UBERON:0011279,nail of pedal digit 2,UBERON:0001705,nail,BFO:0000050=UBERON:0003632,BFO:0000050,part of,UBERON:0003632,pedal digit 2 + UBERON:0011280,nail of pedal digit 3,UBERON:0001705,nail,BFO:0000050=UBERON:0003633,BFO:0000050,part of,UBERON:0003633,pedal digit 3 + UBERON:0011281,nail of pedal digit 4,UBERON:0001705,nail,BFO:0000050=UBERON:0003634,BFO:0000050,part of,UBERON:0003634,pedal digit 4 + UBERON:0011282,nail of pedal digit 5,UBERON:0001705,nail,BFO:0000050=UBERON:0003635,BFO:0000050,part of,UBERON:0003635,pedal digit 5 + +The command has a number of options for transforming this to a matrix, customizing +where rows, columns, and values with be defined classes, genus terms, predicates, or fillers. + +:term:`OBO Format` also provides a compact way of showing logical definitions, these can be +seen in OAK using the ``-O obo`` option: + +.. code-block:: bash + + $ runoak -i sqlite:obo:uberon logical-definitions .desc//p=i nail -O obo + +.. code-block:: + + [Term] + id: UBERON:0009565 ! nail of manual digit + intersection_of: UBERON:0001705 ! nail + intersection_of: BFO:0000050 UBERON:0002389 ! manual digit + + [Term] + id: UBERON:0009567 ! nail of pedal digit + intersection_of: UBERON:0001705 ! nail + intersection_of: BFO:0000050 UBERON:0001466 ! pedal digit + + +Reasoning using logical definitions +----------------------------------- + +Currently OAK does not support classification-style reasoning. If you need this, +we recommend doing this ahead of time using a tool like ROBOT. + +Logical definitions and design patterns +--------------------------------------- + +Creating logical definitions by hand can be time consuming and error prone. Many groups +choose to do this via a templating system such as: + +- ROBOT templates +- DOSDPs +- OTTR templates +- LinkML-OWL + +Currently OAK does not support generating logical definition axioms or any other OWL +axioms from templates, but it might in the future + +Generating logical definitions +------------------------------ + +Sometimes it can be useful to generate logical definitions using heuristic methods such +as lexical pattern matching. In general these generated definitions should be reviewed +by experienced ontology developers before being added, but they can be useful to both +get a sense of missing definitions or as an aid to manual definition creation. + +Some OAK commands have a ``generate`` counterpart; above we saw the ``logical-definitions`` +command which is for looking up existing logical definitions. The ``generate-logical-definitions`` +counterpart can be used for generating logical definitions based on specified lexical patterns. + +At this time, this command is experimental, and the flags may change. + +Analyzing logical definitions +------------------------------ + +OAK will soon provide commands for analyzing logical definitions, in particular to determine +consistency of lattice population. + +Validating logical definitions +------------------------------ + +The ``validate-definitions`` command is used for validating text definitions - one aspect +of this is checking for consistency between text and logical definitions. + +Further reading +--------------- + +* `Use of OWL within the Gene Ontology `_ +* `Cross-Product Extensions of the Gene Ontology `_ \ No newline at end of file diff --git a/docs/search-syntax.rst b/docs/search-syntax.rst index ae9a75689..b5fd04527 100644 --- a/docs/search-syntax.rst +++ b/docs/search-syntax.rst @@ -3,7 +3,7 @@ Search Syntax ================= -The search syntax is used by the :ref:`search` interface +The search syntax is mapped to the :ref:`search` interface .. warning:: diff --git a/notebooks/Commands/DiffViaMappings.ipynb b/notebooks/Commands/DiffViaMappings.ipynb new file mode 100644 index 000000000..8f281883a --- /dev/null +++ b/notebooks/Commands/DiffViaMappings.ipynb @@ -0,0 +1,2121 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "681a0856", + "metadata": {}, + "source": [ + "# OAK diff-via-mappings command\n", + "\n", + "This notebook is intended as a supplement to the [main OAK CLI docs](https://incatools.github.io/ontology-access-kit/cli.html).\n", + "\n", + "This notebook provides examples for the `diff-via-mappings` command, which can be used to **find differences between two ontologies based on mappings between them**\n", + "\n", + "For more on mappings, see [Mappings](https://incatools.github.io/ontology-access-kit/guide/mappings.html) in the OAK guide.\n", + "\n", + "\n", + "## Help Option\n", + "\n", + "You can get help on any OAK command using `--help`" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "460639a7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Usage: runoak diff-via-mappings [OPTIONS] [TERMS]...\r\n", + "\r\n", + " Calculates cross-ontology diff using mappings\r\n", + "\r\n", + " Given a pair of ontologies, and mappings that connect terms in both\r\n", + " ontologies, this command will perform a structural comparison of all mapped\r\n", + " pairs of terms\r\n", + "\r\n", + " Example:\r\n", + "\r\n", + " runoak -i sqlite:obo:uberon diff-via-mappings --other-input\r\n", + " sqlite:obo:zfa --source UBERON --source ZFA -O csv\r\n", + "\r\n", + " Note the above command does not have any mapping file specified; the\r\n", + " mappings that are distributed within each ontology is used (in this case,\r\n", + " Uberon contains mappings to ZFA)\r\n", + "\r\n", + " If the mappings are provided externally:\r\n", + "\r\n", + " runoak -i ont1.obo diff-via-mappings --other-input ont2.obo --mapping-\r\n", + " input mappings.sssom.tsv\r\n", + "\r\n", + " (in the above example, --source is not passed, so all mappings are tested)\r\n", + "\r\n", + " If there are no existing mappings, you can use the lexmatch command to\r\n", + " generate them:\r\n", + "\r\n", + " runoak -i ont1.obo diff-via-mappings -a ont2.obo lexmatch -o\r\n", + " mappings.sssom.tsv\r\n", + "\r\n", + " runoak -i ont1.obo diff-via-mappings --other-input ont2.obo --mapping-\r\n", + " input mappings.sssom.tsv\r\n", + "\r\n", + " The output from this command follows the cross-ontology-diff data model\r\n", + " (https://incatools.github.io/ontology-access-kit/datamodels/cross-ontology-\r\n", + " diff/index.html)\r\n", + "\r\n", + " This can be serialized in YAML or TSV form\r\n", + "\r\n", + "Options:\r\n", + " -S, --source TEXT ontology prefixes e.g. HP, MP\r\n", + " --mapping-input TEXT File of mappings in SSSOM format. If not\r\n", + " provided then mappings in ontology(ies) are\r\n", + " used\r\n", + " -X, --other-input TEXT Additional input file\r\n", + " --other-input-type TEXT Type of additional input file\r\n", + " --intra / --no-intra If true, then all sources are in the main\r\n", + " input ontology [default: no-intra]\r\n", + " --autolabel / --no-autolabel If set, results will automatically have\r\n", + " labels assigned [default: autolabel]\r\n", + " --include-identity-mappings / --no-include-identity-mappings\r\n", + " Use identity relation as mapping; use this\r\n", + " for two versions of the same ontology\r\n", + " [default: no-include-identity-mappings]\r\n", + " --filter-category-identical / --no-filter-category-identical\r\n", + " Do not report cases where a relationship has\r\n", + " not changed [default: no-filter-category-\r\n", + " identical]\r\n", + " --bidirectional / --no-bidirectional\r\n", + " Show diff from both left and right\r\n", + " perspectives [default: bidirectional]\r\n", + " -p, --predicates TEXT A comma-separated list of predicates. This\r\n", + " may be a shorthand (i, p) or CURIE\r\n", + " -o, --output FILENAME Output file, e.g. obo file\r\n", + " -O, --output-type TEXT Desired output type\r\n", + " --help Show this message and exit.\r\n" + ] + } + ], + "source": [ + "!runoak diff-via-mappings --help" + ] + }, + { + "cell_type": "markdown", + "id": "672f0f62", + "metadata": {}, + "source": [ + "## Example: Diff between two anatomy ontologies\n", + "\n", + "To illustrate usage, we will calculate the diff between [UBERON](https://obofoundry.org/ontology/uberon) (a multi-species anatomy ontology) and [ZFA](https://obofoundry.org/ontology/zfa) (an anatomy ontology for zebrafish).\n", + "\n", + "Note that rather than provide a set of external mappings, we will use the mappings present in both ontologies (in this case, UBERON has xrefs to ZFA).\n", + "\n", + "To simplify the comparison, we will only consider [is_a](https://incatools.github.io/ontology-access-kit/glossary.html#term-IS_A) and [part_of](https://incatools.github.io/ontology-access-kit/glossary.html#term-Part-Of) relationships. This is specified using the `--predicates` (`-p`) option" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "ef4a124b", + "metadata": {}, + "outputs": [], + "source": [ + "!runoak -i sqlite:obo:uberon diff-via-mappings --other-input \\\n", + " sqlite:obo:zfa --source UBERON --source ZFA -p i,p -O csv -o output/uberon-zfa-diff.csv" + ] + }, + { + "cell_type": "markdown", + "id": "cb6e5491", + "metadata": {}, + "source": [ + "## Analyzing the results using Pandas\n", + "\n", + "Note that we asked for the output as a tabular file (`-O csv`), rather than native YAML. You could take the tabular output, analyze it in a spreadsheet, etc. Here we will use the Python pandas library." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "26c079cc", + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "df=pd.read_csv(\"output/uberon-zfa-diff.csv\", sep=\"\\t\").fillna(\"\")" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "1ad732b4", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
left_subject_idleft_object_idleft_predicate_idcategoryleft_subject_labelleft_object_labelleft_predicate_labelright_subject_idright_object_idright_predicate_idsright_subject_labelright_object_labelright_predicate_labelsleft_subject_is_functionalleft_object_is_functionalsubject_mapping_predicateobject_mapping_predicateright_intermediate_idssubject_mapping_cardinalityobject_mapping_cardinality
0UBERON:0000002UBERON:0000995BFO:0000050MissingMappinguterine cervixuterus1:0
1UBERON:0000002UBERON:0001560rdfs:subClassOfMissingMappinguterine cervixneck of organ1:0
2UBERON:0000002UBERON:0005156rdfs:subClassOfMissingMappinguterine cervixreproductive structure1:0
3UBERON:0000003UBERON:0000033BFO:0000050MissingMappingnarishead1:0
4UBERON:0000003UBERON:0005725BFO:0000050MissingMappingnarisolfactory system1:0
...............................................................
36336ZFA:0009401ZFA:0009000rdfs:subClassOfMissingMappinglens fiber cellcell1:0
36337ZFA:0009402ZFA:0005065BFO:0000050MissingMappingheart valve cellheart valve1:0
36338ZFA:0009402ZFA:0009000rdfs:subClassOfMissingMappingheart valve cellcell1:0
36339ZFA:0009403ZFA:0009402rdfs:subClassOfMissingMappingheart valve interstitial cellheart valve cell1:0
36340ZFA:0009404ZFA:0009402rdfs:subClassOfMissingMappingheart valve endothelial cellheart valve cell1:0
\n", + "

36341 rows × 20 columns

\n", + "
" + ], + "text/plain": [ + " left_subject_id left_object_id left_predicate_id category \\\n", + "0 UBERON:0000002 UBERON:0000995 BFO:0000050 MissingMapping \n", + "1 UBERON:0000002 UBERON:0001560 rdfs:subClassOf MissingMapping \n", + "2 UBERON:0000002 UBERON:0005156 rdfs:subClassOf MissingMapping \n", + "3 UBERON:0000003 UBERON:0000033 BFO:0000050 MissingMapping \n", + "4 UBERON:0000003 UBERON:0005725 BFO:0000050 MissingMapping \n", + "... ... ... ... ... \n", + "36336 ZFA:0009401 ZFA:0009000 rdfs:subClassOf MissingMapping \n", + "36337 ZFA:0009402 ZFA:0005065 BFO:0000050 MissingMapping \n", + "36338 ZFA:0009402 ZFA:0009000 rdfs:subClassOf MissingMapping \n", + "36339 ZFA:0009403 ZFA:0009402 rdfs:subClassOf MissingMapping \n", + "36340 ZFA:0009404 ZFA:0009402 rdfs:subClassOf MissingMapping \n", + "\n", + " left_subject_label left_object_label \\\n", + "0 uterine cervix uterus \n", + "1 uterine cervix neck of organ \n", + "2 uterine cervix reproductive structure \n", + "3 naris head \n", + "4 naris olfactory system \n", + "... ... ... \n", + "36336 lens fiber cell cell \n", + "36337 heart valve cell heart valve \n", + "36338 heart valve cell cell \n", + "36339 heart valve interstitial cell heart valve cell \n", + "36340 heart valve endothelial cell heart valve cell \n", + "\n", + " left_predicate_label right_subject_id right_object_id \\\n", + "0 \n", + "1 \n", + "2 \n", + "3 \n", + "4 \n", + "... ... ... ... \n", + "36336 \n", + "36337 \n", + "36338 \n", + "36339 \n", + "36340 \n", + "\n", + " right_predicate_ids right_subject_label right_object_label \\\n", + "0 \n", + "1 \n", + "2 \n", + "3 \n", + "4 \n", + "... ... ... ... \n", + "36336 \n", + "36337 \n", + "36338 \n", + "36339 \n", + "36340 \n", + "\n", + " right_predicate_labels left_subject_is_functional \\\n", + "0 \n", + "1 \n", + "2 \n", + "3 \n", + "4 \n", + "... ... ... \n", + "36336 \n", + "36337 \n", + "36338 \n", + "36339 \n", + "36340 \n", + "\n", + " left_object_is_functional subject_mapping_predicate \\\n", + "0 \n", + "1 \n", + "2 \n", + "3 \n", + "4 \n", + "... ... ... \n", + "36336 \n", + "36337 \n", + "36338 \n", + "36339 \n", + "36340 \n", + "\n", + " object_mapping_predicate right_intermediate_ids \\\n", + "0 \n", + "1 \n", + "2 \n", + "3 \n", + "4 \n", + "... ... ... \n", + "36336 \n", + "36337 \n", + "36338 \n", + "36339 \n", + "36340 \n", + "\n", + " subject_mapping_cardinality object_mapping_cardinality \n", + "0 1:0 \n", + "1 1:0 \n", + "2 1:0 \n", + "3 1:0 \n", + "4 1:0 \n", + "... ... ... \n", + "36336 1:0 \n", + "36337 1:0 \n", + "36338 1:0 \n", + "36339 1:0 \n", + "36340 1:0 \n", + "\n", + "[36341 rows x 20 columns]" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "6b959761", + "metadata": {}, + "outputs": [], + "source": [ + "df[\"left_source\"] = df.apply(lambda x: x.left_subject_id.split(\":\")[0], axis=1)" + ] + }, + { + "cell_type": "markdown", + "id": "1b0f2f50", + "metadata": {}, + "source": [ + "## Plotting mapping diff categories\n", + "\n", + "The diff tool works by taking each relationship/edge in the \"left\" ontology (here all UBERON and all ZFA edges\n", + "are considered) and trying to map it to a relationship in the \"right\" ontology.\n", + "\n", + "The mapping is assigned a category from the [cross-ontology-diff:DiffCategory](https://w3id.org/oak/cross-ontology-diff/DiffCategory) enumeration.\n", + "\n", + "(note that like many OAK operations, the output conforms to a data model that makes its semantics explicit)\n", + "\n", + "We will use seaborn/matplotlib to plot the category counts" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "20be04b9", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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eVeuXNHcuXNVuHBhlShRQhUqVJAkffvtt1blvv32W8XFxVlCR+bMmXXx4kWrMnv27ElR+0ymJ79tJbdNyVGhQgWdPHlSBQsWVKlSpSw/a9as0cqVK2Vra6s9e/bIzc1NHTt2tASzO3fuaM+ePZYer9KlS8ve3l7//e9/reqfO3euevXqZRlO+qwKFSokd3f3RPWuWbNGnTt31v3797Vnzx7VqlVLFStWlIODgyRp69atkv7XI/fo+jNnzqzixYvrxIkTVtv92muvaerUqZahoX5+fvrll1+snsx58OBBnTt3zvK3n5+fzp8/r3379lmtY+3atbK3t1fp0qWfuI3Ozs6qV6+e1q9fr+3btzOkEcBLj54zAICFj4+PPv74Y02aNEnHjx9Xw4YN5e7urr///lshISGKjo62BLfChQurUaNGmjJliqKiouTn56dDhw5p2rRpqlixogICAiRJb7zxhrZs2aJRo0YpMDBQu3fv1urVq1PUPjc3N+3du1e7du1S+fLlEz0NMrltSo62bdtqzZo1atu2rdq3by93d3dt2LBBK1asUP/+/SU9CF5Lly5VUFCQ3njjDV2+fFkhISG6evWqpRfPw8NDbdq00fz58+Xg4KAKFSpo//79Wrp0qfr27fvUwPk4tra2+uijjzRs2DBlzZpVgYGBOnnypKZMmaKWLVsqS5YsKl26tNatW6cSJUrI09NTe/fuVXBwsGxsbCxf5u3q6irpwT12Xl5eKlOmjHr16qXOnTurd+/eatCggeLi4jR37lzt379f3bp1kyR17dpVGzZsUMeOHdW+fXtFRkZq8uTJMplMlv3SuHFjLVmyRN27d1ePHj2UN29ebdmyRatWrdKHH35o6f17knfffVfvv/++smTJopo1a6botQKAFwXhDABg5YMPPlDx4sUVGhqqkSNH6ubNm8qVK5eqV6+url27KleuXJayI0aMUP78+bVq1SrNnj1bOXLkUJs2bdStWzdL6GjSpInOnDmjb775RsuWLZOfn5+mTJmi5s2bP3PbunbtqhkzZqhTp07asGGDcufOnahMctqUHDlz5tSyZcs0fvx4DRkyRNHR0SpQoIBGjBihd999V5LUqFEjnTt3TqtWrdKSJUuUM2dOVatWTS1atNDgwYN1/PhxeXl56dNPP1XWrFm1bNkyzZkzR3nz5tXgwYOtnmyYEi1btpSz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pOiBIM2aNVNQUJAcHR311ltvKVu2bLp69arWr1+v2bNn68MPP0ztdgIAAADASy1F4ax58+Y6ePCgxo0bp/Hjx1umx8fHq1GjRurcuXOqNRAAAAAAMoIUP0p/xIgRat++vXbu3KmbN2/KxsZGNWvWlJeXV2q3EQAAAABees90z9mRI0fUpEkTzZs3T5Lk5eWl5s2bq0WLFpo8ebJ69eqlkydPpklDAQAAAOBlluxwdu7cObVp00ZXr15VwYIFrebZ29urb9++unHjhlq0aMHTGgEAAADgGSU7nAUHB+uVV17RN998ozp16ljNc3JyUtu2bbVy5UplypRJs2bNSvWGAgAAAMDLLNnh7LffflPHjh3l4eHx2DLZs2dX+/bttX379lRpHAAAAABkFMkOZ5cvX1aBAgWeWq5IkSK6ePHi87QJAAAAADKcZIczDw8PXb58+anlIiIilCVLludqFAAAAABkNMkOZ35+fgoLC3tqudWrV6t48eLP1SgAAAAAyGiSHc5at26t8PBwBQUFKTo6OtH8mJgYjRkzRlu3blXLli1TtZEAAAAA8LJL9pdQlypVSv3799fIkSO1Zs0a+fv7K2/evIqLi9OFCxcUHh6uiIgIffzxxwoICEjLNgMAAADASyfZ4UySWrZsqaJFiyokJESbN2+29KC5uLioSpUqat++vcqUKZMmDQUAAACAl9kzhTNJKleunMqVKydJun79uuzs7OTm5pYqjZk1a5a2bdumRYsWWaYNGjRIX3/9tVW5PHnyaMuWLZIks9msadOm6euvv9atW7fk5+enzz//XK+++qql/KFDhzRixAj9+eef8vDwUNu2bdWmTRvL/OTUAQAAAABpKdn3nCXFw8Mj1YJZaGioJk2alGj6kSNH1LVrV23bts3ys3LlSsv8GTNmaMmSJRo+fLiWLVsms9msjh07KiYmRtKDp0e2a9dO+fLl06pVq9S9e3eNGzdOq1atSnYdAAAAAJDWniucpYZLly6pa9euGjduXKLvUYuPj9exY8dUsmRJZc+e3fKT8EXYMTExmjt3rnr06KHq1auraNGimjhxoi5evKhNmzZJklasWCF7e3sNGzZMXl5eatKkidq2bavg4OBk1wEAAAAAaS3dw9lff/0le3t7rV27NtH9amfOnNHdu3dVqFChJJc9fPiw7ty5I39/f8s0Nzc3FS9eXLt2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Usbb21uhoaF67733VKpUKb399tvavHmzZf7UqVPVvHlzTZ8+XRUrVlT58uXVv39/q2E1Dw+R6Nevn/r166fRo0fL399fZcqUUZcuXay64c+cOaNOnTrJ19dXAQEBmjdvnt58882XbmhkgwYN5O/vr4EDByY5DEmSbty4oaFDh6patWoqXbq0mjVrluwe0IRwbmf3v8+O9u7dq5YtW6p06dKqXr26hg4d+th1Sw965N5++22VLl1aPj4+atGihQ4cOCDpwb785ptvtHPnTsv/xaPDGPbt26c2bdqoXLlyqlixovr376+IiAjL/MDAQIWEhOijjz6Sr6+vKlasqC+//FKxsbGSHnwaOHbsWFWrVk0lS5ZUnTp1tHTpUqs23r9/X6NHj1alSpXk4+Ojbt266erVq5ISDwVp3bq1RowYoV69eqlMmTKqWrWqgoODU3SBiMQS3szLly+vSpUqJTlc/GnH4JOOiXPnzqlGjRqSpDZt2mjq1KmJho3dv39fkydP1htvvKEyZcqocePG2r59u6X+H3/8UU2bNpWPj49KlSqlxo0bJzpfvshS47wydepUtW3bVsHBwapatapKlSqlVq1a6fjx48/cnmfdn9LT99GThkvFxMRo7NixCggIkK+vr9577z1t27bNqswPP/ygt99+W6VKlVKLFi104cKFZG9PpkyZZGNjY+m5i4+P1+zZs1WjRg2VKVNG77zzjtauXfvY5W/evKlBgwYpICBAJUqUkL+/vwYNGqSoqChJspxL+/fvr379+iU6h8XFxWn+/PmqXbu2SpUqpdq1a1udE8PDw1W8eHH9/PPPql+/vuW8+eOPP1rKnDp1Sh06dFC5cuXk6+urDh066MiRI1btPHnypNq2batSpUopICBAs2bNssxL6vpl9erVql+/vkqXLq333ntPe/bsSfZr+jirVq1S3bp1Vbp0adWtW1cLFiyQ2Wy2vE7nz5/X9OnT5e3tneT7UXI97bokMDBQ58+f17Rp0yzD+o4ePaouXbrIz89PJUuWVI0aNTR37twkX6Nn3baE+tu0aSMfHx+9+eabyb5FIaEzQXpwLvT399e0adOsyixbtkxVqlSxvM8+joODg6QHH1RIiYc1/vnnn2rZsqXKlCmjGjVqaO3atSpevLjVueTOnTvq37+/ypcvr3Llyqlfv366e/eupMTHelognL3g6tat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0fpr1qxpVfZZ9qetrW2K99Hp06e1fv16rV69WsWKFZMktWvXTocPH1ZISIiqV69uee9KCCMFCxbU0aNHtXDhQqu6du/eLV9fX8vf0dHR8vDwUL169SwfPt69e1fz58/XhAkTLPci5cuXT+fPn1dISEiS9xxVrlxZfn5+ll6DvHnzavHixTp69Kik/90G4OrqKldXV6vROLdv39bSpUvVr18/vf3225Ie/A+dO3dOwcHB+s9//mMp+8knn8jf31+S1K1bN23cuFFHjx6Vr6+vzpw5o9dff1158uSRvb29Ro4cqRMnTshsNstketDX0KJFCzVs2NCy/Ny5c/Xnn39a6nxU586dVb9+fUnS8OHDtWPHDq1YsUK9e/d+7P56khkzZuiDDz6wfED+6quv6vbt2xo6dKg+/vhjZc+eXba2tnJ2dra8Zkm9H124cMFqPyZwd3fXli1bLH8/6brEw8PDsq5XXnlF169fV5s2bdSyZUvLOahHjx6aM2eOjhw5Yjn2Urpt3377raKiohQUFCRXV1e99tprGjBggLp3725Vz8PbZjabde/ePWXKlElz5syxlGnSpInmz5+vffv2ydfXVydPntS+ffv05ZdfPvY1iomJUebMmRUYGKhPP/3Uckw8bPny5XJ1ddWIESNkb2+vwoULa9CgQerWrZtVudKlS6tnz56SHvxvVK5cWX/++aekxMd6WiCcveBKliypV199VRs3blSbNm20YcOGx/aaSVKxYsVUv359de3aVdmzZ1flypVVvXp1vfnmm5IeDC1ZtWqVateurcKFC+v1119X7dq1nxjOvLy8LL8nHKj3799XRESEzp8/Lx8fH6vy5cuXTzKcVatWTfHx8frll19Uo0YNbdiwwRLqHhYYGKj9+/dr8uTJOnnypE6ePKljx45ZLuYTPHrR7uvrazVMqECBAlbLJIS2o0ePJhnO8uXLZ+kmT9jW+/fvS5IOHjyoggULWt1T8KKOdU6u999/Xxs3btSgQYMsPUbSg9fP1dXVaty4jY2Nypcvn+iTweDgYOXMmVO3b99WcHCw9u/fr27duqlo0aKWMgcPHtTp06eTfKM6fvx4ov3s5+en48ePa/r06Tpx4oROnz6tI0eOWA29eJKjR4+qcuXKVtMS9uWRI0cs4ezh416yPh5atmypH3/8UdWqVVOxYsVUuXJlvfXWW8qaNaulfL58+azePLJkyaJ79+49tl0VK1a0CmG+vr6aPXu2IiIieKjEc0i4uCxVqpRlWrZs2fTqq69a/k7uMfikY+JJTp48qfv376tMmTJW03v16mX5PUuWLAoODrYc04cPH5akVL/RPb0973klW7ZsVg9MSGofBAYGqk+fPonWndCrlOBZ9mexYsVSvI8S3g8fDnbSg/dRNzc3SUmfl3x9fROFs5IlS1p6Mo4fP67hw4eraNGi+vjjjy3bd+zYMUVHR6t3795W56DY2FjFxMQkeR5q0aKFtmzZom+++UanTp3SsWPHdO7cuSTfKx914sQJ3b9/X+XKlbOaXqFCBS1YsEDXrl2zTHu4vsyZM1teB+nBBxYjR47UkiVLVKFCBQUEBKh+/fpW21CgQAGrdbi5uSk6OvqxbXv4/cPe3l4lS5a0nBOe1fXr13Xx4kVNmDDB6kNIs9ms6OhonTt3LtEx9Tg5cuTQokWLEk1/NHA86brkUR4eHmrRooXWr1+vgwcP6syZM5Zj9Gnvj8nZtqNHj6pAgQJW1z1JnTMf3jaz2awbN25o9erVat++veVDsiJFiqhUqVJavXq1fH19tXr1apUuXdrq4TsP13PhwgUFBQXJ2dlZvXr1eux74sGDB1WyZEmr18zPzy9RuUePoyxZsliNpkhrhLOXQMLQxvfff1+bN2+2eiRrUsaPH6/u3btr69at+vXXX/Xpp5+qXLlyWrBggTw8PLRmzRrt27dP27dv17Zt27Rw4UJ99NFHiYYPJEgY3/uw+Ph4S9dzcu+LcXR0VGBgoL7//nuVKFFCf/31l9V48QTBwcGaPn26GjVqJH9/f7Vt21abN2/Wt99+a1Xu0a7vuLg4qxPbw/+cCfMlPfZpWkltZwJbW9tkX/y/TL788ku9/fbbGjVqlGXa4/b3w8dEgty5c1tuip4wYYI6duyozp07KywsTPnz55f04OT99ttvW3otHpbUCXjdunWWT2gTHs1/9OjRZD8F7kntf/iYedxxLz04sW/atEk7d+7U9u3b9dNPP2n27NkaNWqUGjVqJOnxx9njPPraJRxvRnrK5IsoIfA++v/78Oud3GPwScfEkzx6LnrUzp071aFDB1WvXl3lypXT22+/raioqESfSL8snue88qTzdAIXFxfL+eVJnmV/Ps8+SqgzNDQ0Ua9qwnuWjY1NomM0qePG0dHRsm358+dXvnz51LRpU/Xq1UszZ86UjY2NZX2TJk1KMlw9ut1ms1ldunTR33//rfr166tevXoqUaKEZXRBcrfvUQnb87T9l7B8y5YtVadOHf3888/67bffNGXKFH311VdavXq1smXLJinp8+GT/gefdp3wLBK2p3///kk+1OZZerjt7OxSfIw+zpUrV/T+++/Lw8NDgYGBqlKlikqVKmX5wPFJkrNtSR2jSQ1BTGrbfH19tWPHDi1cuNAygqFJkyaaOHGiBg4cqHXr1qljx46PrSd//vwKCQlRw4YN1blzZy1fvjzJ1ya512rp/b7KPWcvgbp162rv3r1atWqVXn311Sd+MrN//36NHDlShQoVstw4PXLkSO3YsUPXrl3T2rVrtXTpUpUrV049evTQihUr1LRpU23YsOGZ2+Xq6qo8efLo999/t5r+6N+PbsuWLVu0bt06Va5cOcmep5kzZ6p79+4aMmSI3n//ffn4+OjUqVOJTsAJD4BIsG/fPpUoUcLy98mTJ3Xr1i2r+ZIeOyT0SYoWLarTp0/rxo0blmnHjx+3qv9llDt3bvXr108rV6603E/l7e2tW7duWX36GB8frz179jzxkdO2trYKCgqSyWTSZ599ZjmBvvbaazp27Jjy589v+YmNjdWoUaP0zz//JKonODhY7777roKCgtSyZUv5+flZ7iFIOEaeNAzQ29s70U3hhw8f1u3bt5P9qefChQu1adMmVa5cWX379tW6devk7++fov+jBI8ez3v37lXevHkN9ajqF1HCUJ6H70eNjIy0esDQsx6DSXnSMZc/f37Z29sn2sfvvfee5s+fr7lz56pixYqWB15UrlzZst6X8aEwqXleSSuP7s/n2UevvfaapAcXzw8fY2FhYZYHShUtWtTyHpUgYZjVkxQuXFh9+vTRTz/9pGXLlkl60DtlZ2enCxcuWK3v559/VkhISKJwcujQIW3dulWTJ09Wnz591KBBA+XLl09nzpxJ1vHn5eUle3v7ROfV3bt3K3v27Mk6h127dk3Dhg3T/fv31bhxY40dO1Zr167VlStXtHPnzqcu/zgP/8/FxMTor7/+srpOeBZZs2aVh4eHzp49a/W6PnwfWFL+rWHpCfenLV26VN26ddObb75pGX76tP2YnG0rWrSoTp06ZfV9esk5RhOYzWardtSvX1/R0dGaN2+erl69ahl++jjZ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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import seaborn as sns\n", + "import matplotlib.pyplot as plt\n", + "\n", + "# set plot style: grey grid in the background:\n", + "sns.set(style=\"darkgrid\")\n", + "\n", + "# Set the figure size\n", + "plt.figure(figsize=(10, 7))\n", + "\n", + "sns.countplot(data=df, x='category')\n", + "plt.title('Count of each category')\n", + "plt.xlabel('Category')\n", + "plt.ylabel('Count')\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "d619fecb", + "metadata": {}, + "source": [ + "the dominant category is MissingMapping. This means that either/both subject and object of the left edge could not be matched in the right ontology. It is not surprising there are so many, because Uberon covers more species." + ] + }, + { + "cell_type": "markdown", + "id": "26fb6e19", + "metadata": {}, + "source": [ + "### Breaking down by predicate, and using log scale\n", + "\n", + "We can get a more detailed view by breaking down the counts by the predicate of the left edge.\n", + "\n", + "We will also use a log scale" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "0ab65e2f", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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+eI8mppyGBvngtvCfXge9PmDRlVq2BfD4G61cHft1Cx5IzM986Kw/msiIiJiOHYYURaWlJSIsLC3MDUtIPVDAXLusBmUtX3u/QgAJia5su8AqkRElH0olQIREXEcQJWyPYYgIiL6bgwjlBPw6jAiIiJSSwxBREREpJYYgoiIiEgtMQQREdEX8SJiygp+xfuQIYiIiD5JQyPlEvjExAQVV0L0v/fhl4Y3+V68OoyIiD5JLteAnl5ufPyYcj80bW2d7x6xnOhnCSGQmJiAjx8joKeXG3J55p2/YQgiIqLPMjRMueNwahAiUhU9vdzS+zGzMAQREdFnyWQyGBmZwsDAGApFsqrLITWloaGZqWeAUjEEERHRV8nlcsjl2l+fkSgbYcdoIiIiUksMQURERKSWGIKIiIhILTEEERERkVpiCCIiIiK1xBBEREREaokhiIiIiNQSQxARERGpJYYgIiIiUksMQURERKSWGIKIiIhILTEEERERkVpiCCIiIiK1xBBEREREaokhiIiIiNQSQxARERGpJYYgIiIiUksMQURERKSWGIKIiIhILTEEERERkVpiCCIiIiK1xBBEREREaokhiIiIiNQSQxARERGpJYYgIiIiUksMQURERKSWGIKIiIhILTEEERERkVpiCCIiIiK1xBBEREREaokhiIiIiNQSQxARERGpJYYgIiIiUksMQURERKSWGIKIiIhILTEEERERkVpiCCIiIiK1xBBEREREaokhiIiIiNQSQxARERGpJYYgIiIiUksMQURERKSWGIKIiIhILTEEERERkVpiCCIiIiK1xBBEREREaokhiIiIiNQSQxARERGpJYYgIiIiUksMQURERKSWGIKIiIhILTEEERERkVpiCCIiIiK1xBBEREREaokhiIiIiNQSQxARERGpJYYgIiIiUksMQURERKSWGIKIiIhILTEEERERkVpiCCIiIiK1xBBEREREaokhiIiIiNQSQxARERGpJYYgIiIiUksMQURERKSWGIKIiIhILTEEERERkVpiCCIiIiK1xBBEREREaokhiIiIiNQSQxARERGpJYYgIiIiUksMQURERKSWGIKIiIhILTEEERERkVpiCCIiIiK1xBBEREREaokhiIiIiNQSQxARERGpJYYgIiIiUksMQURERKSWfkkIevfu3a9YLREREVGm+aEQVLZsWdy8efOT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169fo2fPntDV1c0QcrS0tDBq1CisXr0a3bp1w44dOzjCO1EacWFvERf8UtVlEBFRGt98dZi3tzfy5MmD7du3o1mzZuke09PTQ69evRAQEAAdHR2sWLEi0wsl+hK5XAZNTXmW+09D44cuwCQiot/gm88EnT9/Hv3794eJicln58mbNy+cnJywYcOGTCmO6FvI5TLkyaPPwEFERN/lm0NQSEgIihcv/tX5LCws8O7du5+piei7yOUyaGjIscT/LIJCIlVdTjp2lgXRuZm9qssgIqJP+OYQZGJigpCQkK/OFxERASMjo58qiuhHBIVE4nlQhKrLSKdgXkNVl0BERJ/xze0HDg4O2LZt21fn27Fjx2eH1CAiIiLKKr45BPXo0QMXL17E7NmzkZCQkOHxxMREzJ07F6dOnYKjo2OmFklERESU2b65OczGxgZjx47FzJkzsXPnTlSvXh2FCxeGQqHAmzdvcPHiRURERGDo0KGoXbv2r6yZiIiI6Kd9180SHR0dYWVlBR8fHxw9elQ6I5QrVy7UqlULTk5OsLOz+yWFEhEREWWm7x42o1KlSqhUqRIAIDw8HJqamjA0ZOdPIiIiyl5+aOywVF+6ZxARERFRVsa7yxEREZFa+qkzQURERJT9pQz2LFN1GZ+lVIpfMsgzQxAREZEak8tlMDbWg1yuoepSPkupVCAiIi7TgxBDEBERkRpLOQukgWd7ViIu7K2qy8lAz7QASrTsB7lcxhBEREREmS8u7C3igl+quozfih2jiYiISC0xBBEREZFaYggiIiIitcQQRERERGqJIYiIiIjUEkMQERERqSWGICIiIlJLDEFERESklhiCiIiISC0xBBEREZFaYggiIiIitcQQRERERGqJIYiIiIjUEkMQERERqSWGICIiIlJLDEFERESklhiCiIiISC0xBBEREZFaYggiIiIitcQQRERERGpJU9UFEBER5XRyuQxyuUzVZXyShob6ng9hCCIiIvqF5HIZ8uTRV+uwkVUxBBEREf1CcrkMGhpyLPE/i6CQSFWXk4GdZUF0bmav6jJUgiGIiIjoNwgKicTzoAhVl5FBwbyGqi5BZXhujoiIiNQSQxARERGpJYYgIiIiUksMQURERKSWGIKIiIhILTEEERERkVpiCCIiIiK1xBBEREREaokhiIiIiNQSQxARERGpJYYgIiIiUksMQURERKSWGIKIiIhILTEEERERkVpiCCIiIiK1xBBEREREaokhiIiIiNQSQxARERGpJYYgIiIiUksMQURERKSWGIKIiIhILTEEERERkVpiCCIiIiK1xBBEREREaokhiIiIiNQSQxARERGpJYYgIiIiUksMQURERKSWGIKIiIhILTEEERERkVpiCCIiIiK1xBBEREREaokhiIiIiNQSQxARERGpJU1VF0BEROpJLpdBLpepuoxPUioFlEqh6jLoF2MIIiKi304ul8HYWA9yuYaqS/kkpVKBiIg4BqEcjiGIiIh+u5SzQBp4tmcl4sLeqrqcdPRMC6BEy36Qy2UMQTkcQxAREalMXNhbxAW/VHUZpKbYMZqIiIjUEkMQERERqSWGICIiIlJLDEFERESklhiCiIiISC0xBBEREZFaYggiIiIitcQQRERERGqJIYiIiIjUEkMQERERqSWGICIiIlJLDEFERESklhiCiIiISC0xBBEREZFaYggiIiIitZRjQpC7uztatmyJFi1aYN26daouh4iIiLI4TVUXkBlOnDiBBw8eYOfOnUhISECHDh1Qs2ZNlCpVStWlERERURaVI84EFShQAMOHD4eGhgb09fVRtGhRBAcHq7osIiIiysJyxJkgS0tL6d+BgYG4ffs2bG1tVVgRERERZXU54kxQqhs3bmDw4MFwd3dH7ty5VV0OERERZWE5JgSdPXsWgwcPxty5c1G9enVVl0NERERZXI5oDnv58iVGjRqF5cuXw8bGRtXlEBERUTaQI0LQqlWrkJiYiPHjx0vTRowYgdq1a6uwKiIiIsrKslwIWrFiBc6cOYP169dL05RKJRYvXowtW7YgOjoaDg4OmDhxIooUKQIAmDp1KqZOnZppNWhqfrmVUEMjx7QiqlRmbUfuj5/HfZF1qMs2zA6vk8dF1vIrtmOWCkEbNmyAp6cnKleunG760qVLsXHjRsyePRv58+eHu7s7+vbti927d0NbWztTa5DLZTA2zpWp66RPMzTUU3UJ9P+4L7IO7ousg/sia/kV+yNLhKDg4GBMmjQJFy9eRPHixdM9lpiYCF9fX4wYMQL16tUDAHh4eKB27do4dOgQWrZsmam1KJUCUVGxX5xHQ0POgyMTREXFQaFQ/vR6uD9+HvdF1pFZ+yKryw7vFR4XWcuX9oehod4PnSnKEiHozp070NLSwq5du7BkyRIEBQVJj92/fx8xMTHprvgyNDSEtbU1Ll++nOkhCACSk3P+B1BWoFAoua2zCO6LrIP7IuvgvshafsX+yBIhqEGDBmjQoMEnH3v37h2AlLtCp5UvXz7pMSIiIqLvleV7a8XFxQFAhr4/Ojo6SEhIUEVJRERElANk+RCkq6sLIKVvUFoJCQnQ02MbKxEREf2YLB+CUpvBQkJC0k0PCQmBubm5KkoiIiKiHCDLhyArKyvkzp0bFy9elKZFRUXh7t27cHBwUGFlRERElJ1liY7RX6KtrY3u3btj3rx5MDExQaFCheDu7o78+fOjSZMmqi6PiIiIsqksH4IAwNnZGcnJyRg/fjzi4+Ph4OAAHx8faGlpqbo0IiIiyqayXAiaPXt2hmkaGhoYOXIkRo4cqYKKiIiIKCfK8n2CiIiIiH4FhiAiIiJSSwxBREREpJYYgoiIiEgtMQQRERGRWmIIIiIiIrXEEERERERqiSGIiIiI1BJDEBEREaklhiAiIiJSSwxBREREpJYYgoiIiEgtMQQRERGRWmIIIiIiIrXEEERERERqiSGIiIiI1BJDEBEREaklhiAiIiJSSwxBREREpJYYgoiIiEgtMQQRERGRWmIIIiIiIrXEEERERERqiSGIiIiI1BJDEBEREaklhiAiIiJSSwxBREREpJYYgoiIiEgtMQQRERGRWmIIIiIiIrXEEERERERqiSGIiIiI1BJDEBEREaklhiAiIiJSSwxBREREpJYYgoiIiEgtMQQRERGRWmIIIiIiIrXEEERERERqiSGIiIiI1BJDEBEREaklTVUXQEREv4ZcLoNcLlN1GZ+kocHf4KR6DEFERDmQXC5Dnjz6DBtEX8AQRESUA8nlMmhoyLHE/yyCQiJVXU4GdpYF0bmZvarLIDXHEERElIMFhUTieVCEqsvIoGBeQ1WXQMSO0URERKSeGIKIiIhILTEEERERkVpiCCIiIiK1xBBEREREaokhiIiIiNQSQxARERGpJYYgIiIiUksMQURERKSWGIKIiIhILTEEERERkVpiCCIiIiK1xBBEREREakkmhBCqLiIrEUJAqfz6JtHQkCM8MhbJyYrfUNX30dHRglFuXSRGR0AoklVdTgYyDU1oGxhDoVBm2jqz6v7gvsg6uC+ylqy8P7gvspZv2R9yuQwymez7180QREREROqIzWFERESklhiCiIiISC0xBBEREZFaYggiIiIitcQQRERERGqJIYiIiIjUEkMQERERqSWGICIiIlJLDEFERESklhiCiIiISC0xBBEREZFaYggiIiIitcQQRERERGqJIYiI6BsJIVRdAhFlIoYgIqKvuHHjBpKTkyGTyRiEiFQsM49BhiD6JZRKpapLoDT4xf3jjh49iuHDh8PHxwcKhYJBiEgFXr58ibt37wIAZDIZgMz5XGMIol9CLk95a92/fx+RkZEqrkb9/DeEpn5oAAxE36tSpUqoUKECDh48yCBEn/WlH358r/wcIQT27t0Ld3d3vH//Hvv27cO7d+/Sfa79KJng3qFMpFQqpQB08uRJTJ48Gf/88w/++OMP5MqVS8XVqYe0++DgwYN49uwZNDQ0YGNjg2rVqqm4uuxFoVBAQ0MDUVFRmDp1Kh49eoQWLVqgT58+0NDQgBAiUz6IKXtLe8wdOHAA7969Q3x8PGrUqIHy5ctDLpfzvfKTHj16hLZt2yJfvnxQKpX4999/YW5u/tPr1cyE2ogApKT11A+CdevW4fXr13j79i0WLlwImUyGZs2aIXfu3CquMmdLuw/mzJmDbdu2oUSJEoiKisKiRYvg7OyMfv36SfPyQ/nLNDQ0oFQqYWhoiAkTJmDq1KnYu3cvADAIEYBPH3NlypTB/fv3sWvXLjRo0ADDhw/ne+UnJCcno0yZMqhXrx6OHDkCe3t7JCYmZsq62RxGmSb14F68eDEWL14MS0tLzJo1C1WrVsWCBQtw8OBBxMTEqLjKnC11H1y5cgWXL1/GsmXLsGnTJvj4+OCff/7B/PnzsXHjxnTz0qelniRPSkrCx48fYWRkhAkTJqBMmTLYu3cvm8ZI2v8A8OLFC5w8eRIrVqyAn58fTp8+jerVq+PUqVPw9fUFwGPue6U2MWpoaABIaZp2d3fHw4cPMXnyZDx48ECa90ePQYYgylQRERE4evQoxo4di/bt26Nt27aYP38+mjZtijlz5uDAgQP4+PGjqsvMUcaNG4dXr15Jf+/cuRNr1qyBhoYGrKysAAAFChRA9+7d0bt3b6xduxZv3rxRVbnZQuov9tOnT2PkyJHo378/du3ahTx58jAIEVxdXfHixQvpy9nb2xvjxo2DqakpSpQoAQDQ09PD4MGDUapUKRw9ehRJSUmqLDnbSUxMlM6wvXz5ElFRUejYsSNatWqF9evX49q1a5g7dy4ePnwI4Mc7SzMEUaZKTEzEu3fvYGpqKv0NAJMmTULp0qWxZMkSHD58GPHx8aosM8cICQlBWFgY8ufPL00LCwvDzZs38eTJE7x9+1aabmBggOrVq+P9+/eIjo5WRbnZhkwmw8GDB/HPP/9AoVAgT548GD16NLZu3ZrujNCBAwewdOnSdGcEKGe7efMm9PT0UKBAAWlasWLFcPfuXTx48ABRUVEAUs4SGRsbo3///rhx4wbu37+vqpKzlR07diAsLAza2toAAE9PT/Tt2xetW7fGjBkz8Pz5c5QrVw4bN27E9evXMXv2bJw/fx5Lly5FUFDQdx+HDEH0wz51NYS5uTny5s2Lbdu2AQC0tbWlIJQvXz7ExcVh1qxZCAwM/Ow66Nvly5cPy5cvh5aWFrZs2YKnT5/CyckJrq6uMDY2xvr16/HixQtp/oIFC6Jo0aLQ1GR3wC+5d+8epk2bhokTJ2LJkiUYMWIEhBAYP348/Pz8pCCUP39+nDt3Tvrio5zP1tYW06ZNg7a2NjZu3Ig7d+6gadOmWLhwIRISEuDr6wulUimdJUpKSoKlpaX0w5A+b9u2bZgzZw58fHyQmJiILVu2wN/fH0OGDEH9+vXx6NEjzJo1C8+ePUPZsmWxYcMG3LlzB8OGDcORI0fS/Rj8Vrw6jH5I2qshTp8+DYVCAX19fVSpUgVbtmyBt7c3GjRogLFjxwJIOUU5cOBAODk5wdfXF2FhYdiyZYsqX0KOEh0djUaNGiFfvnxYsmQJihYtio0bNyIgIAClS5dG69atYWhoiIULFyImJgYbN26U9h9ltHfvXgQEBGD16tWIiIjAxIkTYW5uDmNjYyxevBizZs1CmzZtEBsbi+jo6Ey5SoWyvrQdm1++fImhQ4ciJiYGXl5esLS0xNGjRzF06FC0bdsWf/zxB4yMjODp6YmYmBisX7+ex9w3mDNnDs6cOYMaNWogJCQEzZo1Q9OmTQEAmzdvxpYtW2BsbIyxY8eiRIkSePPmDR49eoRatWpJFzJ8z3ZmCKLvlvaDYPbs2di6dSvkcjn09fXRt29fODo6YuHChdi2bRvMzMxQrlw53Lt3Dx8+fMDhw4exadMm7NmzB+vXr2cTwg/61IH+6tUr9OvXD3p6eli4cCGKFi2KTZs2wdvbG2/evEGVKlVQuHBhTJ48Gdra2t/9YaFONmzYAD8/P2zcuBGbN2/Gy5cv0b9/fyQnJ6NFixYAgK5du2L8+PHSL37K2T51vJw5cwYrV65ESEgIPDw8YGVlhaNHj2LEiBGIi4tDo0aNkJycjCVLlvzQF7Q6SUpKgpaWFgBg1qxZOHPmDN68eQNPT0/UrVtXmm/z5s0ICAiAiYkJRo4ciVKlSkmPpd7S4ntwb9B3USqVUnA5ceIEjhw5Al9fX3h5eaFx48ZYunQpNm3ahKFDh8Ld3R2FChVCTEwMKleujEOHDgEAbt++DVNTUyQlJbEz6Q9ITk6WPkivXLmCa9eu4d69eyhSpAhWrlyJjx8/YujQoXjx4gW6dOkCZ2dnFCtWDHnz5kWvXr2gra2NpKQkfhj/v9T34Nu3b3H//n08f/4czZs3x8yZMyGTyXD58mVUqFABxYoVg7a2Nho0aICJEyeiR48eDEBqJPV4uX79Os6ePYvw8HDUqlULgwYNgqmpKYYPH4779++jYcOGWLRoEXLlygVtbW3MmDGDAegbpAYgABg7diwaN24MANi/fz8+fPggPdapUyd07NgRjx49QkBAQLp1/NDxKIi+walTp0R8fLz098mTJ8WIESPE/PnzpWkvX74UM2fOFNWrVxerV6+WpicmJop79+6Ju3fviunTp4tKlSqJBw8e/M7yc4T58+eLM2fOSH/Pnj1bVK1aVVSqVEk0adJE7N69WwiRsh8aNWok2rZtK168eCGEEMLPz0+0bNlSjBo1Sty9e1cl9WdFSqVSCCHEoUOHRMOGDUX9+vVFq1atxM2bN4VCoRAXL14UZcuWFU+ePBFCpOyDjh07io8fP6qybPpNPD09xY0bN6S/Z8+eLWrWrCnKlSsn2rRpI44cOSKEEOLSpUvC0dFRNG/eXNy/f18IIcTRo0eFra2tcHNzEyEhISqpPztQKBTSv319fcWgQYNEVFSUEEIId3d30bx5czFnzhwRERGRbrkjR46I5OTkn35+9o6kr/L09MSFCxdQs2ZNAMC7d++wevVq3LhxA3/88Yc0X5EiRdC9e3fIZDL4+voiPDwcLi4uiIiIwJIlS3Dz5k0UKFAAfn5+sLCwUNXLyZZu3bqFEydO4PLlyzAwMEB0dDT27duHxYsXIywsDMeOHcOcOXOgUCjw559/wtfXF3379kX37t2xZcsWODo6QkNDA0uXLoWOjg7Gjx8vXX2hzmQyGa5evYqxY8fCxcUFNWvWRHh4OEqUKAG5XA49PT3Y2Nhg1KhRyJs3L65evYp169bx7udqICQkBMuWLcPVq1cxYcIE3Lp1CwcOHMCMGTNgYGCAWbNmwcvLCwqFAk2aNMHQoUPh5eWFnj17YsuWLWjQoAG8vLzQv39/aGtrY8KECTwT9B9pz45dunQJb968wZEjR2BiYgI3NzeMGDECCoUCJ0+eBAAMGDAARkZGAICGDRsC+LEmsLTYJ4i+SXJyMjQ1NfHw4UNYWFjgzp07WLhwIW7evIkJEyZI/SQA4PXr11i2bBnCwsKwfPlyAMDHjx8RHR0NAwMD3jX6Bx0/fhxr166FQqGAmZkZrK2tpbs/379/H2vWrMGZM2cwcuRI/Pnnn3j+/Dk8PDzg7u4uBZ6AgABUrVoVRYoUUeVLUZktW7agdevW0NHRkfq2LVq0CA8ePMCSJUvSzfvo0SOcPn0aAPD06VPEx8djwIABKFOmjCpKp98o9cv5+fPn6Ny5M8qVK4dixYqhVKlS6N69OwAgPDwcgwYNQmxsLAYPHozGjRvj7NmzOHr0KMaNGyd9MZ89exb58+dP13dF3Yn/3Dnb3d0d27dvR5MmTfDu3TucP38e9evXx+zZs6Grqyt1lq5QoQJGjRqVud8hP30uiXK0tKcqDx48KCwtLcX27duFEELcvXtXDBgwQLRp00bs378/3XLBwcHSsmnXQd8v7fY7fPiw6N27tyhXrpzw8PBIN9+9e/fEmDFjRJ06dcSmTZvSPZaQkPA7Ss3Snj59KurXry9evnyZbvqkSZOEo6OjSExMFEL8r4ls/fr1okqVKiI8PFwIIURSUtLvLZhUKrWp5enTp6JKlSrC0tJSuLu7p5snLCxMdO3aVbRt21bs2rXrk8tTemFhYen+vnr1qqhVq5a4fPmyNO369evC3t5eDBs2TMTGxgohhBg/frxwdXWVjs/MwnNz9FkizZg4ANCkSRM0aNAA06ZNw86dO1G2bFkMGjQI5ubm8Pb2xsGDB6V58+XLB7lczs6AP+m/269Ro0bo3bs3rK2tsXv3bly6dEl6zMrKCr169YK1tTVOnDgB4H+dftW96UuhUKBEiRLYtWsXihQpglu3bkk37CxUqBDu3bsn3YI/9RdqhQoVYG5ujoSEBAA/2OmSsp3/DtVQokQJbN68Gaampjh37hxu374tzWtiYoLFixcjJiYGZ8+eTbcevl8yGjt2LJYtWwbgf59NkZGR0NXVle5ur1AoYG9vj6VLl+LgwYOYO3cu4uPjMW3aNMydOzfz786eqZGKcoy0Zx+USqWUxoUQYujQocLW1lbs2LFDCCHErVu3xMCBA0Xt2rXFhQsXfnutOVXafRAYGCj27t0rdXQ+ceKE6NGjh+jSpUu6X1BCCPHixQuefUsjLCxMXL16VTrTEx4eLmrUqCG6d+8ubSdHR0fRsGFDERgYKGJiYoQQKZ0y27ZtKyIjI1VWO/1e/z3mjh8/Lu7cuSOEEOLZs2fCwcFBdO/eXZqWKioqimd+vsHu3bul4zA6OloIIcSFCxeEtbW19DmWeqbn3bt3omrVqsLS0lK4ublJ68js7cw+QZSBSNNeu2bNGly4cAEREREoW7YsXF1dYWBgAFdXVxw9ehRTp05F69atcf36dRw5cgQuLi78BZQJ0u6DefPm4cCBAwgLC0PPnj0xfPhwAMDJkyexatUqJCUlYcSIEahcuXK6dfAsXIrLly9j2LBh8PT0hEKhQExMDLS0tODm5oZy5crBy8sLYWFhGDNmDG7duoUSJUrAwMAA9+7dw5o1a1C2bFlVvwT6Df57zO3ZswcKhQJ//PEHXFxcoKOjg6dPn6Jr166wsLCAm5tbhvfGz3bSVRdbtmzBrl27MGfOHOTNmxd9+/aFjo4OBg8eDFtbWwAp41BOmjQJ1atXx4wZMzB16lS0a9cu02thCKJ00n5xLlmyBKtXr0bnzp0RHx+Pffv2wczMDO7u7rCysoKzszPOnj2LUaNGoXPnztI6+EGQeVatWgVfX194enrC2toaCQkJUCqV+PDhA0qWLIkbN25g0aJFePfuHRYuXCidUqb/USqVGD9+PPbv34+4uDj4+PigZs2aOHv2LFxcXFCpUiUsXLgQWlpa2LRpkzRuUZMmTVCsWDFVl0+/mbe3N9auXQsvLy+Ym5vDwMAAAPDmzRtYWVnh1atX6NSpE8zMzODl5YXixYurtuAsbseOHYiIiICpqSn++OMPaGpqwsfHB7t27UKxYsUwffp03Lx5EwsWLICGhgY6duwIExMTbNy4EVFRUdi0aRP69+8PS0tLjB49OtPrYwiiT7py5Qq2bt2Kpk2bol69egBSLhnt0aMHjI2NsWnTJgDAX3/9BZlMhjVr1qiu2BxICIGYmBiMGDECTZo0Qbt27XDv3j3s3r0bu3btgkwmQ40aNTBr1iwcPnwYly9fxtixYxk+P+P48eMYOHAgtLW1sXjxYtSuXRsymUwKQnZ2dli+fDnPnKm5xMREuLi4oHr16nB0dMTdu3exd+9e7Ny5E6GhoejevTvGjx+PR48eYc6cOVixYgWPuS/o168fHj16hLi4OERGRqJt27aYNWsWgJS7sgcEBKBIkSKYOXMmHj16hH///Rf79u1DyZIlYWpqKo2L6OTkhFq1asHJySnTa2QIogzOnz+PSZMm4f3791i5ciUqV64s3dL8yZMn6NChA9zc3NCxY0cAbHbJLOI/l40KITB48GDExsaiVq1aWLNmDYoXL45atWohMTERmzdvxqZNm1CoUCFpGZ6F+7T79+/j5cuXOHHiBPbv34/Zs2ejSZMmUhAaPXo0ihYtitWrV6e7fJ5ytv/u548fP6Jv374oVKgQSpYsCX9/f5QqVQq1a9eGnp4epk2bhj179qB06dLSMjzmPq1r165ISEiAu7s7ACAwMBDjxo2Txt0DUoLQli1bULRoUUyePBkmJiYIDQ2FTCaDXC6HsbEx5s2bh23btsHf3/+XnJnlzRIpwweBsbExateujX///Rc3btxA5cqVoaWlBaVSCVNTU5ibm0OhUEjz8yqwn5d2+8XFxUFPTw8ymQz16tXDzp07sXz5cvTq1QtNmjRBmTJl8PTpUxw9elS6wikVP4xTpL6nX716hfj4eOTLlw9WVlZo0qQJEhISMGbMGMjlcjRu3Bg1a9bEjBkz4O7ujvDwcBQoUIABSA2kPeYSEhKgo6OD3Llzo127dvD29salS5fQtWtXNGzYEFZWVrh37x7s7e2hr68P4H/vMR5zGaUGID8/P2l76enpoUCBAoiLi5Pmc3R0BABs3boVkyZNwqhRo1CkSBHcu3cPo0ePhqamJsLDw7Fq1apf1jTNEKTm0n4QJCUlQSaTwcrKCgMHDoRCocDmzZuRJ08edOjQAXK5HLq6utDT05Pe2KkYgH6cSHMrgjVr1uD06dMwNTXFn3/+iY4dO6JDhw6IiYlB7ty5ERsbi8TERMycORMmJiYoUaKEiqvPmmQyGQ4ePIg5c+YgMjIS5cqVQ4cOHdC6dWvMnz8fI0aMwKhRozBx4kRERUXB1NQUW7ZsgZ6enqpLp98g7TG3evVqXL9+HQkJCXB1dUWnTp3QsGFD5MqVC7q6ukhKSkJycjLmz58PPT095M+fHwAYlD8j9WaSmzZtgra2NhITE6GtrQ1zc3PkzZsX5ubm6eZPDUIrVqzA5s2b4erqCnNzc/z999/Q1tZGuXLlUKBAgV9WL5vD1FjaM0CrV6/G7du38ezZMzRo0AAtW7aEmZkZPDw8cOjQITRt2hT58uXD1atX8fLlS+zZs4e/gDJB2n2wcOFCrFu3Di1atMCZM2dgZGSErl27olOnTggODsbKlStx6dIl6OvrIy4uDgEBAdIZOobQ9G7cuIEBAwbg77//hqmpKf79918kJyejS5cuaNu2LQBg5MiROHToEHR1deHv74+SJUuquGr6HdIeL3PnzsWWLVtQt25d3Lp1C1FRUViwYAGqV6+O4OBgzJgxA2FhYYiLi4MQAps3b+Yx9wXbt2/H2LFjMXToUAwcODDddho/fjwCAgLg6uoKIQTKlCkDExMT2NnZAQBOnTqFGjVqQFPz956bYQhSU2nfnF5eXli/fj26du2KZ8+e4fnz51AqlXB3d0fhwoUxf/58bNu2DUWKFEHv3r3Rrl07yOVytoVnokePHmHWrFkYPHgwKlasiODgYEycOBHh4eHo3LkzOnTogN27d+Phw4cwMzODo6MjNDU1peFM6H9evXqFAwcOIDo6Gi4uLgBShr2YMWMGPn78iM6dO0uX2l65cgWFCxeWft2T+vjw4QMmT56Mfv36oVy5ckhISICzszNu3bqF+fPno3r16ti9ezfu3LmDAgUK8Jj7Bs+fP8emTZuwb98+ODo6YsCAAQAAZ2dnnD9/HoUKFYKWlhYePXqE+Ph4aGpqQktLCwMHDkT//v0B4LdvX+5JNZUagF69eoW7d+9izpw5qF+/PgDgzJkzWLt2LaZOnYolS5agT58+0NTUxLFjxxATEwO5XM6Oo5low4YNOHToEGJjY6XLbc3NzTF+/HhMnz4d/v7+0NbWRuvWrdMtp1Ao+GGchhACkZGR6NGjB96/f49WrVpJj5UsWRJubm6YOXMmAgICEBcXB0dHxwz3ViL1sHnzZsyfPx+FChWSLoHX0dHBwoULMWzYMLi6umLevHlo1apVuvcRj7nPUygUKF68OLp16walUol169ZBX18fgYGBePbsGTZv3oyCBQtCR0cH4eHhuHXrFl6/fo179+6lu+rrd29fns9TY3v27EHjxo0RGBiYblTsWrVqoWPHjggKCsLTp09RpEgRdOvWDfXr14evry+8vb2l3vv08ywsLKQPg2vXrknTixQpgvHjxyNfvnzw9PTEkSNHAPzvdvM8C/c/SqUSMpkMefLkwaxZs1CoUCHcuXMHFy5ckOYpVaoUxo0bh+TkZBw9ehTR0dEqrJhUydraGiVLlsSTJ08QGxsLIOU9pKurC09PT9jb28PJyQm3bt1KtxyPuc9L3TZaWlpwdXVFixYtsHjxYpw4cQKbNm1CiRIlpHlMTExQt25dODo6Yvr06dIZNlXgt5ga+W/LZ8uWLdG0aVOEh4fj0aNH6d6ETZo0AQBpnJySJUvir7/+Qo0aNbBt2zZERkZm7vgtaiJ1XKK0fzs4OGDx4sUoWLAg1q1bl+6Lu0iRIhg9ejSaNWsmnanjGbj/SX0PJiQkICkpCQkJCahevTrmzJmDmJgYrFmzBleuXJHmL1myJObOnYvp06dLZwAoZ/vvMQcA5cuXx7Rp01C0aFG4uroiODhYOsOtq6uL+fPnw8nJCdbW1iqoOPs6e/YsWrZsifDwcPTq1Qtt27aFrq4uNm/eDCDlLM+n9kfqY6rAPkFqIm0foI8fPyIpKQnGxsYAgAEDBuD27dvw8PCAg4MDZDIZIiIi4OTkhEGDBqFRo0bSel69egU9PT2YmZmp5HVkZ2n3wd69e/Hs2TO8ePECLVq0QL169fDgwQMMGTIERYoUQf/+/VG1atUM62A/rP9JbZI9ffo0/P398fr1ayQmJsLJyQkdO3bEtWvXMHLkSFhYWKB///6oWLGiqkum3yztMXfgwAHpxn1WVlZo3bo1Hj16hGHDhkEul2PVqlUwNzfP0NTPY+7b3blzB25ubnB2dkbDhg2lPkJ79uzBX3/9hX79+gHIeFsWVWIIUgNpPwhWrlyJY8eOQVNTEw0bNkSvXr0AAE5OTrh79y7atm2LwoUL4/Tp03jz5g22bdsGTU3NLPWmzW7+u+3c3d2xc+dOlCxZEjExMbh79y46deqEMWPG4Pnz5xg2bBiKFSuGHj16oHbt2iqsPGtKuz2PHj0KV1dX9O3bFxUqVMDWrVuxb98+rF+/Hg4ODrh8+TLGjRsHc3NzuLq6wt7eXrXFk0rMnTsXO3fuhJWVFUJDQ/H48WP8+eefmDlzJp48eQJnZ2doaWlh2bJlv/Ry7Jzkc1fIjR07FteuXcPevXuhqamJV69eYePGjdi7dy/atWuHYcOG/f5ivyRTh2OlLCd1RF4hhJg9e7ZwcHAQ06ZNE8OGDRNVqlQRa9eulR4fOHCgsLS0FN27dxdz5syRpiclJf3WmnOyAwcOiDp16ojbt2+LhIQEIYQQ69atE/b29mL+/PlCCCGuXbsm7O3txaxZs1RZapbz5s0b6d8KhULExMSIXr16iWXLlgkhUkadbty4sZgzZ454+PChNCr1yZMnRatWrdItT+rj0KFDol69euLatWtCCCEiIyPF9u3bhZ2dnZg2bZoQQoiHDx+K6tWri5EjR6qy1Gwh7XeKEELcvn1bvHv3Tvr7xYsXom3btmLHjh3StFevXgk3NzcxYMCADMurGkNQDpX6BZtq7969on79+iIwMFAIIcT27duFlZWVqFq1qli+fLk03+DBg0XVqlXF3r17f2u9OdH48ePFkSNH0k1btWqVGDRokBBCiMTERGn6ihUrhI2NjXjy5IkQQohHjx6J5OTk31dsFufn5ye6dOkiLly4IE0LCwsTDRo0EA8ePBAfPnwQtWrVEuPHjxdCCOHr6ytatmwpYmJihBBCxMXFqaRu+r3+/vtvcfv27XTTlixZIvr3759uWlxcnFi9erWoUqWKuHnzphBCiOfPn/OY+4oPHz6k+zFx+PBhUa5cOdGqVSuxfft2ERISIoQQYsiQIcLZ2TndssHBwVIAykpBiB2jc6ApU6Zg586diImJkabdvXsX5cuXh62tLe7cuYOAgAAMHToUf/75J5YvX44NGzYASLlnkL29PaZPn47t27cjISFBVS8jW3vz5g3kcjnq1KmTbvrr16/x4MEDAClXUSQmJgIAWrVqBS0tLTx79gwAULp0aWhoaKQbnkSdlS5dGm/fvsXq1atx6dIlAClXmBQoUACbN29GmzZt0KBBA0yYMAEApPetlpYWgJTLnylnS0hIgJmZGcqUKQPgfx2iQ0NDERUVJR1rAKCrq4uKFSsiNjYWUVFRAIBixYrxmPuCMWPGoH///ujYsSNcXV3x4cMHNGrUCLNnz0a1atUwadIkDB8+HGvWrEG/fv1w7tw5nDx5Ulo+X758kMlk0pWcWQVDUA7j7+8Pf39/+Pn54dixY/j48SOAlMsXc+XKheDgYOzYsQO2trbo378/ateujbi4OEybNg2enp4AgOXLl6NEiRJYvHgxkpKSVPhqsqfY2FgULFgQEyZMgJaWFrZs2SKFzJo1ayI5ORnr1q1DbGwstLW1AQAxMTHInz8/8uXLl25d7JCZcvO0qlWrwsvLC0+fPsXatWulK+jKly8vDaw4ZcoUaXu+e/cOhQoVkr4Is9KHLmW+8PBw6OjoYNq0adDW1saaNWtw8eJFAECFChVw/fp1HDt2LN0yuXPnRpkyZWBoaJhuOo+5jLp164anT5+iZ8+eGD58OC5cuIDx48cDSLnK2M3NDWvXrkWlSpWwcuVKjBs3DlpaWjh79iwAZBhrMivhXZ9ymFq1aiFv3rx48OABFixYAABo0aIFOnTogNjYWERERODGjRsYPnw45HI5NDQ0UL9+fXTr1g3Vq1eX1rNhwwa8ffsWuXPnVtVLyZZCQkKwYcMG9OzZE6ampkhMTMTWrVuRmJgIY2NjNGvWDHv27IG/vz+ioqLQvn17xMTEwN3dHcbGxihXrpyqX0KWkxpgChYsiKZNm2LdunWIj4+HsbExhg0bhqdPn+LFixcYN24crK2tcefOHRw8eBD+/v48A6QGwsPDcfDgQTRs2BD58uVDbGwsjh49imXLlmHJkiVo1aoVLl26hLFjxyI6Ohq2trbQ19fHjBkzoK+vz2PuK1IHQ127dq10WwkTExNMmTIFwcHBMDMzg1wuh729PcqXL4+BAwdi5cqVuHr1KgICAtCjRw8UKVJExa/i83h1WA6S2lvf398fT548wYMHD3D79m1MnToVzZo1g5aWFrZt24YVK1Zgw4YNMDMzwz///INcuXJh7ty5kMlkSE5O5sjIP+HJkydwcXFBqVKlAACNGjVC2bJlMWvWLISGhuKff/5BvXr1MGHCBFy6dAlBQUGwsLCAvr4+1q9fz3GJ/iN1Wxw8eBCjR4/Gn3/+iZCQEFy6dAkVKlTAiBEjUKxYMSxevBjnz59HUlISihQpgqFDh8LS0lLV5dNvcP/+fbRp0wa9e/cGAJiZmaFz585wdnbGw4cP4eHhgZIlS2LFihXw9/eHrq4u8ufPD319ffj5+fGY+4L+/fvjxYsXOHjwIABIg6Heu3cP/fr1Q+vWrfHixQs4ODigQ4cO0o9mIQQ+fPiAcePGoWTJknBxcYFMJsuaZ2RV3CeJMknajmanT58W3bp1E69evRLjx48X9vb2YteuXSIxMVGcO3dOWFpaii5duoiWLVuKli1bSld/ZaXOatnZ5s2bRc2aNYWlpaU4ceKEECKlo7OTk5No27atOHz4sBBCiKCgIHHo0CFx69YtoVAohBC8Ek8IIbZt2yZOnjwphEh5T7579060bNky3ZWMN27cEO3btxd9+vQR9+7dk+aNiorKcFEA5XwnTpwQ5cqVE9bW1uLcuXNCCCFiYmJEjx49RJ06dcSlS5eEEEJcv35dnDhxQly8eJHH3FfcvHlT/PHHH6J169bprv4SQohBgwaJKlWqCDc3N9G2bVvh4OAgFi5cKBQKRbrvkXHjxokhQ4b87tK/C6NvNhcSEiLdNyX1js+1atVCyZIlMWnSJEybNg0NGjTAxIkTceDAAVSvXh1z586Fra0tGjRogO3bt0NTUxMKhSJrpvRsJLX/iZ6eHoQQKFmyJLZv3443b96gdOnSGDt2LIyNjbF8+XLs2LEDBQsWROPGjVG+fHnI5XIolUq1H5coMjISGzZswMKFC3HhwgXIZDJoaWkhJiYG5ubmAFK2s52dHSZNmoTr16/Dw8MDp0+fhkwmg4GBgdQviHI+IQSUSiXi4+ORnJwMhUKB48eP4/Xr19DX18fy5ctRvHhxDB8+HFeuXIG9vT3q1q2LKlWqSINAq/sx9ymxsbGwsbHByJEjoa+vj379+uH9+/cAgCFDhuDZs2fYtGkTZsyYgW3btqFs2bI4efIkEhMTpe+RsLAwhIeH4/3794iPj8+yIwwwBGVjvXv3hrOzMxYvXoyEhIR0B3OvXr2gqamJwMBAaUTkiRMnYs+ePWjVqhXGjh2L4cOHS2O2sPnrx6WGn9TT6Q4ODti6dSu6d++Oly9fYubMmXj79i1Kly4NNzc3mJiYwMvLC8ePH0+3Hp6OB4yMjDBv3jwYGxvDw8MD586dg56eHmJjY/H27VsAKZ0slUolbGxsUKlSJZw7dw6HDx+WxoCinC9th3e5XI6mTZvi/v378PT0xLp16+Dj44OgoCApCFlYWKB79+64f/9+uvXwcy8jV1dXDBo0CFFRUahXrx769+8PfX19DBgwAE5OTggKCsKyZctQokQJ6Yq7ypUrQ0tLK92FNBEREcibNy8mTZoEXV3drPsjW7UnouhHpTZrWVpaijp16oj69euLjRs3Sk0DiYmJwtXVVQwePFhaZsiQIcLS0lKcPn1aVWXnOKmn1IUQ4tixY2Lr1q1SU44QQqxevVq0bdtWDBkyRAQHBwshUprGZs2axXuSfMHz589Fr169RIcOHcTt27eFn5+fsLKySrdthRBizJgxYuHChSIoKEhFldLvlvaYO3r0qNi0aZM4f/681Ay6fft2YWlpKaZOnSrevn0rhBDi/fv3YsGCBTzmvsGxY8dEhQoVxPDhw0VkZKQ0zdHRUVhbW0tNi6n3OVMoFKJ79+5iwoQJGdaVHZqm2TE6G9uyZQsmTJiAzp07IyYmBsHBwQgODkaXLl3QuXNnxMXFoV+/fvjrr7/Qpk0bAICHhweGDBnCU8CZQKQZvmHu3LnYsmWLdBuC0aNHS0OSrFmzBjt37oS+vj6ioqLQp08faX9wXKLPe/HiBSZNmoS4uDh07doVd+7cwfr16zFw4EAYGRkhKCgI27dvx549e5A/f35Vl0u/wX+Pue3btyM5ORlmZmZo3bo1+vTpA21tbezYsQNjxoxB06ZNERoairp166J///4AeMx9SXJyMjQ1NXHhwgUMHDgQjRs3xpgxY2BiYoJjx47B29sbCQkJ8PT0RLFixZCcnIyBAwfizZs32LlzZ7YcYokhKBtKSkqSbgK3evVqzJs3D8OGDYOVlRVevXqFhQsXwtraGlZWVtDW1oaGhgacnZ3TrSP1zU4/Ju2BvnjxYvj5+WHJkiUoXLgwpk+fjsOHD2PMmDFSENqwYQMuXryIiIgI+Pr6SvuPvuz58+eYNm0aYmJi0L17dyQkJGDlypXQ1taGmZkZRo4cibJly6q6TPrNli5divXr18PT01O659mbN2/Qvn179O3bF9ra2ti1axc2btwIAwMDLFu2jJ93X5H2CrkrV67g4MGDWL9+Pdq1a4fRo0fDyMgIx48fx4oVK5CYmIiFCxfC3d0dDx48wJ49e6ClpZU9v1dUdxKKMsuKFSuEpaWlWLBggRAiZZwWb29v8eeff0pNZk+fPlVxlTnDiRMnRFRUlBAi5TRwcHCwcHR0lK4Cu379umjUqJEYPHiwsLS0FOvXr5eWTUhIkK6c4BUp3+7Zs2fCyclJdOzYUWruTUpKkobEoJzt4sWL0r+VSqV4+/ataNeunTh69KgQQoi7d++KunXrCkdHR9GsWTPh7e0tNcOEhobymPtO7u7uombNmmLatGliwIABwtbWVjg7O0tNY8ePHxddu3YVlpaWokmTJlKzWHbdvgxB2UjatvBdu3aJHj16SH97e3sLS0tL4e7uLj5+/CiESPnSXbBggRg+fDjbwjPB6tWrhb29vVi3bp2Ijo4WQqQMFmhvby9Onjwp3rx5I4YPHy68vb1FWFiYcHR0FJaWlmL8+PEiPDxcWg9vRfD9UoNQkyZNxPnz51VdDv0mW7duFZaWlmL79u3StNevX4uGDRuKs2fPipcvX4rhw4eLdevWCSGEaNmypahZs6bo37+/iI+Pl5bhMfdtAgMDRe3ataXBh4UQ4urVq6JixYrC2dlZfPjwQQghxL59+8S0adOk4JNdA5AQQmSz81bqK+2pyiNHjuDy5cu4dOkSRo8ejTlz5qBfv36QyWSYN28eNDQ00K1bN5ibm2P48OHSOtgW/nN69eqFW7duYf369RBCoF27dsifPz8GDx6MggUL4uzZs9DW1kbNmjVhYmICc3NzVKhQAa9evYKRkZG0nuzUXp5VFC9eHOPGjcOCBQtQuHBhVZdDv0mjRo3w8OFDaYiGNm3aIFeuXKhXrx7y5csnHXP29vYAgFKlSuHt27fImzdvumYZHnPfJjY2FjKZDMWKFQOQ8p1RsWJFrFixAr169YKJiQmGDBmC5s2bo3nz5gCyf9cKXpObTaQGoDlz5mD27NlISkqCnZ0dzp07h3/++QcA0LdvX4wcORLe3t7w9vZGeHi4tLwQggHoJ6Teg2n+/PmwsbHBunXrsH37dgBAjx49ULRoUfj7+8PQ0BDW1tbSECVOTk5Ys2aNdB8g+nElS5ZkCFIzhoaGGDx4MBwdHeHm5oatW7ciT548GDlyJEqXLo09e/agQIECsLGxQXJyMmJjY9GhQwdMmzYNGhoaPOa+QPx/d2CRpluwoaEhgoODcf36dQAp3ztCCJQoUQImJibw9/fH+vXr060nOwcggGOHZSsXLlzA/v37sWDBAlSsWBHJycnYv38/fHx8MHjwYCxevBh9+vRBTEwMzp07B2NjY2lZ/hL6OZqamlKH9Pnz52PYsGFYvXo1AKBt27bInTs3jIyMEB0djSNHjiAgIACxsbFo0KABgJQPGt4H6OfxRojqJ3fu3Bg0aBCUSiUmTJgADQ0NtGnTBgkJCfjw4QPevn2Lc+fOwcfHB5GRkWjXrh1kMhmPuS9I27IQFxcHIOWHnrW1NVq0aIFFixZBV1cXderUAQDo6OigWrVqaNWqVboxJnMCXh2WjezcuRNeXl7Yvn27NJBdbGwsduzYgblz56JZs2aYPXs2gP9dQSay2eWKWc1/xxRKSEiQBuUcOXIkrl69it69e8PR0RGbNm3CqlWroKmpCXNzc+kqMDZDEn27tMfcfz+/pk2bBn9/f0yfPh3t2rXDvn37MHbsWBQqVAjGxsZYs2YNj7mvSLt9fX19ceXKFbx48QIGBgYYOXIkkpOT4ePjg3v37qFz584wMjLCiRMn8P79e+zYsQNyuTzbN4GlxRCURX0qvBw5cgSzZ8+Gl5dXusuCg4KC0K5dOyiVStSuXVsaPZ6DAv6ctNtv8+bNuH79Oh4/fgx7e3v07t0bBQsWhKurK65fv45+/fqha9euCA0NRVxcHAoVKpTjPiyIfrW0x9y///6Le/fu4eXLlyhZsiQGDx6MPHnyYObMmfDz88OsWbPw559/Ijg4GDExMShevDiPue8wf/58BAQEwMXFBcbGxpg/fz4+fPiAw4cP4/Xr19i3bx+2b9+OYsWKwczMDO7u7jlysNmc80pyEKVSKQWg4OBgfPjwAQBgY2OD2NhY+Pj4IDg4ON0y5cqVQ48ePfD06VMcPnwYAIdh+Fmp28/d3R1LlixBnjx50L59e6xfvx6TJ09GYmIi5s+fjwoVKsDHxwfe3t7Q19dHkSJFOBYY0Q9Ie8wtWrQIpqamsLGxQUBAAP766y8kJibCxcUF3bp1w7hx47Bx40aYm5ujZMmSPOa+4OPHj+n+fvLkCU6ePImFCxeiY8eO0NTURGhoKMaMGYP79+8jd+7ccHFxwf79+6X7MaXeByinfa/krFeTQ6S+yRYvXoy///4bLVu2hI+PD/Lly4f58+fjwIEDmDJlCrZt24bLly9j/Pjx0NTURI8ePRAbG4vHjx+r+BVkb2lPjl65ckXqhzV69GiULl0aWlpa6Ny5Mx4+fAgg5RdVsWLFcOfOHejp6UnL5rQPC6Jf4b+NEZcuXcLhw4exZMkSDBkyBPb29tDU1ES/fv3w6NEjyGQyjBkzBq1atcLevXvTLc9jLqNhw4Zh9OjR6S6UiYqKQnh4OOzt7XH8+HEMHz4cw4cPR+vWrfHvv/9iyZIlSExMTDfmlxAiRwZMvmOyCKVSme5KhqVLl8LPzw/NmzdH3bp14e7ujrlz56JSpUrYuHEjQkND4enpifHjx0OpVGLx4sUwNjZGiRIlYGpqqsJXkn2lDv6XthkyLCwM+fLlQ6VKlXDo0CH069cPY8eORZ06dTBjxgysW7cOAODj4wMPDw+pQyYRfZvU4y71uAkLC4O+vj7s7e1x6NAhuLi4wNXVFXXr1oWXlxcCAgKgqamJcePGwc/Pj8fcVzRp0gRnz57FvHnzpCCkp6eHfPnyYeXKlXB1dcWYMWPQrVs3ACkDnyYnJ0NbW1stbjOQ82JdNpX2F0xQUBAiIyPh4eEh9cS3trbGtGnTIITA6NGjsW7dOsTFxSE8PBzm5ubQ1taGh4cHbt++Ld1Tg77dggUL8OjRIyQlJaFLly6oVq0acufODW1tbcTExGDdunXw9PTEqFGj0LVrV8TFxeHjx48ICwuT1pF6Op6/Rom+bv78+Xj06BHi4+PRq1cv1KtXD0DKxQe5cuXCjh07MGXKFOmYi4mJwePHj6X+kLlz5wbAvo+f4+rqCmdnZ/zxxx/Q19fHkCFDIITA2LFjYWVlBV1dXXh5eWHgwIHo3LkzgJRAmpCQIN0nSB0wBKmYs7MzihcvDhcXFwDAiRMn8Pfff8PAwAA1a9aU5nN0dASQcnWEXC5Hz549kT9/fjx8+BBz5sxBcHAwoqKisHLlShQpUkQlryW76tGjB96/f4+SJUvi1atXGDNmDNzd3VG/fn0UKFAAcrkcc+fOxYABA9C1a1dpOQ0NDRQsWDDduvhhTPR1PXv2REREBAoWLIjIyEj8/fff8Pb2Rp06dVCjRg3MmjULY8aMwYQJE6RjTqlUwtjYmMfcNwgMDISOjg4KFCgAAKhXrx48PDwwfPhwKBQKzJw5E4sWLYKTkxP27dsHpVIJU1NTHD16FB8+fMDff/+t4lfw+/DqMBWKjY3FkSNH0KxZs3T3P5kzZw5Wr16NUaNGwdHRUbokGwD8/f0xZcoUTJgwAY6Ojnj37h0OHToEY2NjVKhQgTeS+05du3ZFQkICfH19YWRkBJlMhpYtWyJ//vxYtWoVgJQrwzw9PVG5cmU0btwYBgYGWL9+PcLDwxEQEMBLcYm+Q9euXZGYmIgVK1bAzMwMQUFBGDFiBHLlygUvLy/o6enh+PHjGDNmDKpVq4bmzZtDT08P69atQ1hYGLZu3cpj7hukXmHs7+8POzs7WFtb48iRIxg+fDiaN2+OyZMnIzk5GRMmTMDTp0+RK1cuFC9eHNOmTVOr2wwwBGURfn5+OHPmDJYvXw4AmDJlCrZs2YLp06ejZcuW6dpmDx8+jPr16+fITmq/U7du3RAfHw8/Pz/o6+sjMTER2tramDJlCqKjozFv3jxpXn9/fxw6dAgXL16ElZUVzMzMsGTJErX6sCD6WV26dEFiYiLWr1+PXLlySdPHjh2L2NhYLFy4EEBKk9jVq1cxadIkxMTEwNTUFIUKFYKXlxePua9Ie3uVly9fwtnZGXFxcfDy8oKFhYUUhJo1a4ZZs2ZBU1MTHz9+hEwmk/aJOt1mQD1eZRaU+kYVQiAxMRExMTG4e/cuRo0ahblz52LSpElQKBSYMGECAKQLQo0bNwagXm/UzNazZ09ERUVh586d0NDQkAIQALx9+xYlSpRIt327du2K9u3bIygoCAYGBjA1NYVMJuM+IPpG//zzD969e4cTJ04AQLpjLiYmJl0zl46ODmrUqIGtW7fiw4cPkMlkKFy4MI+5r/hv/6iiRYvCxcUFPj4+GDZsGDw8PNCoUSN4eHjA1dUVGhoaGDlyZLqLaXLqVWCfoz6vNAtJ+0aVyWTQ0dFBz549oa+vj9WrV2PEiBGYN28epk6dCplMhilTpiAuLg6dOnVK9+tHnd6omen169cIDw+HUqlEZGQkTExMpA/jsWPH4sSJE4iOjkbv3r1hZGSEIkWKoE6dOsiVKxdsbW2l9fCeJETf5ubNm3j58iXy58+PmzdvwtbWNt0xd+jQITg4OEifgxUqVICdnR309fV5zH2jtN8rN2/eRFxcHCpWrIg6depAS0sLS5cuxfDhw6UgtGDBAgwaNAjFixdP1wcop14F9jlsDvvN0r5Rd+zYgRs3biBPnjzo378/lEolAgICsG7dOlSsWFFqjhk+fDhCQ0MzDFxHP0YIgcePH2PUqFGIj4/Hhg0bpNGR79+/j/r160NfXx9Pnz7Fw4cPERERgcjISFStWhVr165VdflE2dLhw4exceNGxMbGYty4cbC1tYWzszMePHiAWrVqoWDBgrhw4QJCQ0Px6NEjJCcno2bNmli1apXafTF/r7TfKx4eHtixYwdCQ0Nha2sLNzc32NjY4Pz581i6dCnCw8Ph4eEBCwsLXLlyRboPk7piCFKROXPmYPfu3bCzs0OBAgXg4uICfX19xMfHY8uWLfD19YWDgwPmzp0L4H9vco4FlnkePnyIkSNHQqlUokCBAggODsaKFSuQP39+aZ7o6GgkJCTg+vXr7IdF9J1cXV3x/v176X5ahw8fhp+fH5KTkyGEkPqqpF7QkdrX5/nz5wgJCUHFihV5zH0HDw8PbNq0CRMmTEDp0qXx119/wc7ODs7OzihfvjzOnz+PZcuW4cGDBwgICJCuJFbrJkZBv93evXtF3bp1RWBgoBBCiPfv34sbN26IlStXipMnTwohhPDz8xM2NjbCy8tLWk6hUKik3pxCqVSm+78QQjx8+FB069ZNWFpaimvXrgkhhEhOThZCfHp7JyUl/YZKibK/hIQEsXHjRlGxYkXh7OwsTT948KDo3r27sLe3F3v37hVCpByTycnJ0jGX9hjlMfdpsbGxQoj/bau7d++KZs2aiYsXLwohhLh3756oVKmSqFixomjbtq24ffu2EEKIM2fOiMmTJ0ufc+qOZ4JUYPny5bh//z48PT1x4cIF+Pv74+rVqwBSmmrmzp0Le3t7nDx5Ek2bNuVVEJkg7enixMRExMbGIk+ePABSxtFxcXGBQqGAt7d3hvuQENGPSUhIwMGDBzFjxgxUr14dnp6eAFIGg163bh2SkpLg4uICBwcH1RaazYSEhOD06dNo0qQJDAwMAKQM8TNq1Chs27YNYWFhWLBgARo3bowGDRqgXr16qFChApo0aSLdGBEAr7IDh8345dIOhZEqf/78OHnyJPr374++fftKd4HesGEDDAwM8PbtW+TKlQt//PEHNDQ0oFAoVFB5zpE2AK1atQrOzs5o0aIFhg0bhm3btqFUqVLw9PSETCZD//798fbtWxVXTJS9pX5m6ejoQFdXF82bN8eBAwcwduxYAECjRo3Qo0cPaGtrY8GCBbhy5Yoqy812Hjx4AE9PT+zevRseHh7Yv38/zMzMULJkScTExODgwYMwMzODtbU1dHR0kC9fPty5cwc3b95MN8SIugcggH2Cfqm0X75Pnz6FUqmEgYEBtLW1sXPnTly8eBEtWrRA/fr1pfszdOjQAd27d0ebNm3Y/ycTpN2Gnp6e2LRpE3r37o3k5GRcvnwZT58+Ra9eveDk5IQnT55gxIgRePPmDfbt28cx2Ih+0pw5c3D48GHUqlULDx8+xM2bN1G/fn14eXkBSOkj5O/vj6CgICxcuBBWVlYqrjj7mDp1Kvbs2SPd7LVSpUoICQmBkZEROnXqhD59+qB169b48OEDxo0bh06dOqF27drsW/ofatoT6tcTQkgByNPTE/v370d0dDQGDx6Mbt26oVevXujVqxeioqJw69YtFCxYEFOnToVCoUCrVq0AqN+liplp4cKFqFOnDipUqAClUomHDx/i2LFjWLx4MSpXrgwAeP78Ofz8/ODv7w8rKyvUqFEDM2fOxNq1a6WmMiL6MdevX8fevXsxf/58ODg4ID4+HqdOncKECRPg7OyMRYsWoXHjxoiPj0dgYCDKlCmj6pKzhdROzIULF0ZUVBTMzMxw+/ZtlCxZEvny5cPbt2/x8uVLaWzJMWPGICoqSgpAHGvtP1TUFylHS9vhbOHChaJatWri3Llz4sKFCyIiIkIkJSWJd+/eCSGE8PX1FZUqVRItW7YUjo6OIjExMcM66PtcvXpVNGvWTPTo0UPqDHj58mVRs2ZN8fjx43SdLp8+fSrq168vli1blmE93AdEP+7gwYOibt26Ijo6WpqWkJAgdu/eLSwtLcWECRMyLMNj7vP+e6HGixcvxJs3b8TEiRNFvXr1hLe3twgLCxNCCDFu3DhhaWkpGjRoINq1ayd9r/DimowYBzPRsGHDEB8fL7WzhoeH4+rVq5g9ezaqV68OMzMzbN68Ga1atcKff/6JQ4cOoXv37li+fDmmTZuGdevWQUtLC8nJyWyr/QkVK1bE0KFDIYTA7Nmzce/ePRgYGCA0NBTR0dGQyWRITEwEAJQoUQIFCxZEaGhohvVwHxB9G/GJXhXm5uaIi4vD+fPnpWna2tqws7ODsbExNm/ejPnz56dbhsfcp6U9e3PmzBkcP34cYWFhKFCgAKZMmYLq1atj48aN2L59OxITEzFmzBjs2LEDEydOxObNm6XvFZ4ByojNYZnk2rVr0NTUTNeEFRMTg/v37+Pp06cIDw/HsmXLYGJigmrVqiE+Ph6TJ0+Gg4OD1DwD8I6oP0MIAaVSCQ0NDTRr1gxCCKxfvx4zZ86Ei4sL/vzzTwwbNgxr1qxB8eLFAQAfP35EQkICSpYsqdriibKptF/QSqUS8fHx0NbWRqlSpVC6dGls3rwZRkZGqFKlCgBAX18f9vb26Ny5M2rXrq3K0rMFkaZrxezZs7F161bIZDLkzp0bjo6O6NOnD2bOnIlx48Zh3bp1eP/+PR49eoQuXbpIQywpFAp+r3wGO0ZngqlTpyJfvnxwdHSEgYEB/Pz80KZNG+TOnRuzZs1CQEAAlEolunTpgiZNmqBChQrYtWsXtm7dCm9v73SjxNPPS/uhvHfvXmzcuBFyuRy1a9fGtWvXcOPGDQwZMgQAcOzYMYSGhmLr1q38kCD6TmmPtdWrV+POnTu4ceMGqlSpgvbt28PAwABDhgyBiYkJatSogVKlSmHLli2Ii4uTjku1vlHfV6TdvsePH8f06dPh4eGB2NhYHDlyBAcOHMD/tXfnYTXn/R/Hn0d7SWOvaGNGZsaWjOjinkGELI3hHurmVrqRqOz7yBhNGCrLjK07ZaJFaS7Z7jENk8s2NYzsJmkZ1CRbpcU5/f7w63vrNgvDKHo/rqvr6vRdfM7XOef7Op917NixeHl5ARAQEEB6ejq6urpKy4L4fRKCntFXX33F7NmzadOmDSNGjOCdd95h8uTJaDQakpKSMDQ05Ny5czRp0oRmzZopx3l5eWFoaEhoaKh0gH5G8+bNw9zcnKZNm+Lo6IiRkRGNGjVSticnJxMeHg5Av379uH79OnFxcVhZWWFhYcHy5ctlZWohnsL/dq5duXIl8fHx+Pj4oFKpiIiIoKCggLS0NE6fPk1sbCwHDx6kZcuWNGzYkNWrV6OjoyOddH9DampqtfXVUlJS2LVrF+bm5vj7+wMPV4iPjIx8LAjl5ubSokULWWz2CUkIekaZmZm4ublRXFxM+/bt6du3Ly1btmTjxo3cvn2bhIQE6tevT2ZmJmvWrEFPT4/MzExKSkqIj49HR0dHhis+g6tXr9K/f3/lcaNGjaioqKB9+/ZYWVnh4OCAnZ0de/bsISUlhYqKCoKCgjA1NUWtVqOjoyMfFkL8CVVfGtLT05k1axaBgYHY2dlx/PhxvL29Wbp0KWZmZpiammJqakpRUREqlQpDQ0N5z/2OIUOG0Lp1a1atWoVKpSInJ4ePPvqIU6dOMXjwYD7++GNl3+zsbLZu3cr+/fsZMWKEUsMNjwdV8evkCj2DyspKbGxslBlP1Wo1O3bsIDc3l4kTJ2JsbMywYcMoKSnB2toaCwsLiouLcXBwICEhQemsJgHoz7O2tiY2Npb69evj5OSEh4cH06ZNw9jYmEOHDjF37lz69OnD9u3bOXLkCD/++CPjx48nOzsbXV1dVCoVlZWV8mEsxBPw9/dn0qRJwH87MVdUVABgZ2fH/v37mThxItOnT6d3795s2rSJhIQENBoN9evXx8jISN5zv8PNzQ1dXV2WLl2q3BcsLCzw8fHB3t6eAwcOsH//fmV/S0tLxowZg6OjI2fPnq3WQV0C0JORmqA/6dGUferUKcLDw/H09CQmJobU1FTc3NywtLRk3bp1FBcXk5CQgKGhYbVvP9L88vykpaUxc+ZMnJ2dmThxIiYmJmg0GvLz80lPTyc/P59Dhw6Rl5eHvr4+27Ztk2svxFOKjIxk1apVDBgwgE8//RSAb775hiVLluDt7c2KFSuYOnUq7u7uwMObuo2NDUuXLq3JYr8URo0aRVlZGVFRURgYGFBeXq40hwF8//33bNq0iYKCAiZNmoSTk5OyLS8vj6ZNm8pEiH+ChKCnlJWVhZWVFVA9CM2dO5f8/HzCwsKYOXMm6enpjBw5EktLS7744gsyMjI4cuQI+vr6NVn8V1pqaiq+vr4MHjyYUaNGKSPA/lfVh4SEUCGezKpVqxg+fDhNmjRhz549fPzxxwwcOJCgoCDg4Q385MmTzJkzh7FjxwIPR8d6enrSv39/PDw8arD0tV9VANq6dStGRkZUVFQonZojIiL45z//CTwMQps3b1aCUJ8+faqdR5rAnp5crafg7e3N5MmTWblyJaWlpdW2zZgxgwcPHpCSksKKFSto06YNcXFx5Obm4unpyYABA6Sn/l+sS5curFmzht27dxMdHU1ubq6yTa1WK+u4qVQqZSi9EOL3paSkcPXqVczMzDA0NMTV1ZWFCxeye/duZs2aBTz8EvjGG28QFRVFXFwc0dHR+Pn5cf/+fUaPHl3Dz6B2mzJlCtnZ2SQkJGBkZER5eblyr/D19WXr1q0UFhYC8M477+Dl5UWzZs1YsmTJY2uuSQB6elIT9ISysrJwdnZGT09P6Qs0YMAAnJyceP311ykvL2f58uXcuXOHFStWAA/bz48ePcqiRYsYOHAgIE1gL0Jqair+/v4MHjwYd3d3WrZsWdNFEuKlVlXDsHfvXiwtLXn77beJi4tj8eLFuLq68sknn3D16lWCgoI4e/YsTZs2xdrammXLlsnIy99x5coVPvnkEzIzM/n000/p1q2bss3X15fMzEzWr19PixYtql3DtLQ0Dhw4wIwZM+S6PiMJQU/h9OnTSltsYWEharWa1NRUJk2aRL9+/dDX18fV1VWZmA8erhs2ZcoUeaG+YGlpaUydOpWePXvi5+dXbXoCIcSTqWo6rqys5NKlS0yYMIE333wTf39/bG1tlSA0dOhQpd9Pbm4ujRs3Rl9fX0aBPYG0tDQiIyM5deoUy5cvx8HBgSlTpnD16lUlAD36/7Bjxw5cXFwwNDQE5Iv1s5IQ9JRSU1OZO3cuAwYMwNHRkaysLDZt2kTjxo3p3r072traFBQUMHv2bAwMDJTj5IX64h09epSIiAi++OIL6SgoxFP6tf4l8fHxREVFYW5uzuTJk2nbti1xcXEsWbKEAQMGsGzZsmr7SyfdX3f58mXOnj2Lq6ur8njVqlVcvHiRJk2a8ODBA0JDQ7GwsKh2DT08PCgqKiImJkaavp4TCUF/QmpqKlOnTmXgwIFMnz6dX375hSNHjvDFF19w48YNNBoNe/fuxcbGpqaLWudVfYBIh0Ehntyj75effvqJoqIiOnXqBDycIDYsLAxLS0slCMXHxzN//nz8/Pzw9vauwZLXfpcvXyY/P5+lS5fi6elJYWEhw4cPJzc3l/DwcL755hsCAgIYNmxYtS/PVVN77Nq1S+aXe44kBP1JVSORBg0axLhx42jevDmlpaWEh4dz5coVgoKCpOanlpAPCyGe3KMBKCQkhMTERO7cuYOtrS0bNmzAxMRECUJWVlZMnjwZW1tbvv32W3r27ClNX7/D3d0de3t7XFxciIyMJDk5mVu3bnHw4EFMTU1JTU0lMjKS9PR0AgICePfdd9FoNEyYMIHs7GySkpKU+eXkOj8fEoKeQVpaWrUh2VZWVtVuuNIEJoR4Wa1atYqYmBhmz55No0aNmD17Nu3bt2flypVKENqyZQtGRkYEBgZiaWkJIDfo35CUlMT8+fPZt28fZmZmhIWFERwcjJmZGR4eHri5uQEPF+PesmULZ86cYdGiRSQkJHDhwgUJQH8RaR94Bvb29qxevZo9e/awceNG8vLyqtU4SAASQrwMysrKqj1OT0/nwIEDrFu3jmHDhqGvr49arSY9PR1PT0/u3LnD0KFDGT16NObm5tVGYMoN+rcZGxtz8uRJPvroI06ePMnmzZvp1q0b27ZtU9Y37Ny5M2PHjqVDhw5MmDBBAtBfTK7mM7K3t2fZsmVERETICCQhxEsnJyeHrKwsevToofytsLCQ4uJi7OzsyMjIICIiAl9fX7p27crIkSOZNWsW48aNY9iwYQwbNgyQifr+yIABA9izZw9BQUEUFhYSFRVFx44dadq0KRUVFcTHx6NSqRg7diydO3dGS0uLVq1aMWnSJLS1tSUA/UXkij4H3bt3p1u3btIBVwjx0rlw4QJ+fn6sX7+ezMxMbGxsMDU1pWPHjvz888/s3r2b5s2b07t3bwwMDGjQoAGHDh1CW1ubrl27KueRz73fVhVgmjRpQn5+PpaWluTk5NCmTRtat27Nv/71LzZv3kxcXByFhYUUFhbi4eGBr69vtePF8yd9gp4j6YArhHgZffLJJ8TGxqLRaNi1axc2NjZcv36d+vXrM3LkSHx8fBg4cCB5eXksWbIEPz8/WrduLcHnD/zvPSE5OZmGDRvy+eefc+3aNTw8PBg8eDB6enpkZGSwZcsWjh8/jkajYd++fRJ8XgC5ws+RBCAhxMuoWbNmynINGRkZWFpaYmZmRm5uLvfu3aOkpISioiICAgK4d++eEoBk8Mdve7RVoKCgAI1Gw9tvv03z5s0JCQnB19dX6Qc0ePBgWrduzYwZMyguLsbU1FSu7wsiNUFCCFHH/G+z/dWrV1GpVGzZsoWEhASWLl2Ks7MzRUVFzJgxg7Nnz6Knp0fjxo2JiYlBR0dHmv5/x6M1QOvXr+fYsWNkZmZiYGDAsGHDGD9+PKWlpfj4+JCfn4+HhwcuLi7o6ekp55Dr+2JICBJCiDrk0ZvriRMnUKvVtGjRQhniPn/+fHbt2kVgYCCDBg3i559/5sqVK5SVldGrVy+0tLSkj8oTCgkJITo6msWLF2Nqasq6dev47rvv2LNnD61ataK0tJTJkydz5swZgoKCeO+992q6yHWOvIqFEKIOqQpAy5Yt46uvvuLu3bu89dZbjBgxghEjRrB06VJUKhVz5szhxo0bZGdn07t3b5ycnICH859JAPpjN27c4OjRo3z22Wf06NGDQ4cOcfLkSZYvX05eXh45OTm8++67rFmzhpCQEHr27FnTRa6TpK5NCCFecZWVlWg0GuVxYmIi+/fvZ9WqVWzatAljY2NiY2OJjY0FHnaUHjFiBGFhYVy4cKHa8Hnpo/JkysrKyMrKwsbGhkOHDuHv78/UqVMZMmQI3377LZGRkdy9excDAwPmzp2LlpYWarW6potd50icF0KIV1h2djaWlpZKH5UTJ05w8eJFxo4dS7du3QBo0aKFMkIM4O9//zuLFi3iH//4BzY2NtJJ90/Q0tLC3Nyc8PBwEhMTmTNnDh9++CEA9+/fB6BBgwaPHSNeLKkJEkKIV5SLiwtLliwBHtYGXb9+HR8fH8LDw8nJyVH2s7S0ZMGCBTRu3Jj4+Hi2bNkCoIwC02g0coP+FQkJCWRlZf3qtpYtW/LWW2/x5ZdfMnToUCUAlZWVkZOTQ+vWrV9kUcVvkI7RQgjxCho1ahRqtZrw8HCMjIyUEUtnzpxhxowZGBoaMmPGDBwdHZVjcnJymD59Om3atGHJkiUy7cfvyMjIwMXFBQ8PD9zd3astHVLV+Vyj0eDv78/hw4dxdnbGxMSEs2fPcuvWLRITE9HW1pb55WqYhCAhhHjFjBo1irKyMqKiojAwMKCiogIdHR3lhnvy5ElmzZqFtbU1Xl5eODg4KMfm5eXRtGlT6tWrJzfoP5CWloa/vz8uLi64u7tjYWGhbHv02gUHB3P+/HnUajXW1tbMnTsXbW1taWKsBSQECSHEK2TkyJFUVFQQERFB/fr1lQAEsGPHDpydnTE2NubUqVPMnDkTGxsbvLy8qi2BATJPzZNKS0vDz8+PQYMGVQtCj16/7OxsLl26pIywA1kKo7aQV7gQQrwivL29uXbtGvHx8dSvX5+ysjIlAPn4+JCYmIharaayspJOnTqxYsUKsrKyWL58OefOnat2LglAT8be3p6QkBCSkpKIiopS+lpV1aSdP3+eyZMns379euBhDVFlZaUEoFpCXuVCCPEKyMjI4OLFi7Rq1Ypjx44BKDMQ+/r6kp2dTVBQEK+99hpVDQCdOnUiMDCQFi1a0LZt2xor+8uuS5cuhIaGkpSUxJdffsm1a9cAuHjxInPnzqWyspLt27cDD5dXkibG2kOaw4QQ4hVx9OhRQkND0dfXx9vbGwcHB3x9fbly5QobNmygRYsWSl+VyspKvvnmm2pNNNIE9mwebRpzdHQkNDSUsrIydu7ciY6OjjSB1UISgoQQ4iX3aCfco0ePEhISgrGxMUVFRdy/f5/169djZmZWbb/Ro0dTUlLCjh07pGbiOaoKQrdv36ZVq1bEx8dLAKrFJPILIcRLTKPRKDU7AN27d2fKlCncvn2bn376ibFjx2JmZgaghJ3x48dz8+ZNoqOjqx0rnp29vT3BwcHY2dmRkJAgAaiWk5ogIYR4ST3afJWZmQmAhYUF2traHDlyRKkRGjNmDO+++y4AXl5e5OTkkJSUJDfov1BVrZtc39pN/meEEOIlVRWAgoOD2blzJyqVCktLS8LCwnB0dOTBgwesXbuWyMhIVCoV0dHREoBekKoaNrm+tZvUBAkhxEvm0b49sbGxrFy5kgULFnD9+nXi4+PR1tZm586d6Orq8t133/H5559z+vRpbGxsSExMlAAkxP+TECSEEC+pw4cP89133/HWW2/h6upKZWUlx44dIyAgAC0tLXbu3Imenh4HDx7k66+/ZvHixWhra0sAEuL/SQgSQoiX0OnTp1m8eDE//fQTK1euVIa6q9VqTpw4weLFi9HR0SEuLg59fX3lOAlAQvyXjA4TQoiXwP9+X7WysmLo0KEYGRkRFRWl/F1LS4uuXbsSEBDA9evXmT9/frXjJQAJ8V9SEySEELXco6PAysvLKS4upmHDhqjVanbs2MG6deuws7MjNDRUOUatVnP+/HnefPNNWaRTiN8gIUgIIWqxRwPQ5s2bSU1NJT09nS5dutC3b18GDRpETEwMGzZsoGPHjgQHBz92DlmtXIhfJyFICCFqqUdHgYWEhBAdHY2HhwcPHjzgxIkTXLlyhYkTJ+Lu7s727dvZsmUL5ubmhIeH13DJhXg5SOOwEELUMqGhofztb3/Dzs4OjUbDpUuXSE5OZu3atXTp0gUAFxcXvvzyS8LDw7G1teX999/n3r17nDt3TtYAE+IJybtECCFqkR9++IF9+/YRHBzM2bNnqVevHkVFRRQWFtKwYUOlg7O1tTXu7u5oNBq+//579PX1GT16NMHBwdSrVw+NRlPDz0SI2k9CkBBC1CKdO3fGz8+PyspKgoKCOH/+PMbGxhQUFHDv3j1UKhXl5eUA2NjYYG5uzs2bNwEwMDBQZiqWmiAh/pi8S4QQohaorKxErVYD0L9/f9zc3FCr1QQGBlJSUsLQoUPx9/fn6tWr6OrqAlBUVERZWRmtWrWqdi5ZFV6IJyMdo4UQohZ5tD/P7t272bZtG/Xq1aNnz5788MMPnDp1iilTpgCQnJxMQUGBslSGEOLpSAgSQogaNG/ePMzNzWnatCmOjo4YGRnRqFEjZXtycrIy2qtfv35cv36duLg4rKyssLCwYPny5ejo6MgweCH+BAlBQghRQ65evUr//v2Vx40aNaKiooL27dtjZWWFg4MDdnZ27Nmzh5SUFCoqKggKCsLU1BS1Wo2Ojg4qlUqWwhDiT5IQJIQQNej06dN4enrSrVs3OnbsSP369Tl27Bjp6encunWLiooKzM3Nyc7ORldXFwsLC9auXYuNjQ1QfS4hIcTTkRAkhBA1LC0tjZkzZ+Ls7MzEiRMxMTFBo9GQn59Peno6+fn5HDp0iLy8PPT19dm2bZs0fQnxHEgIEkKIWiA1NRVfX18GDx7MqFGjsLa2/tX9qmp+pA+QEM9OhsgLIUQt0KVLF9asWcPu3buJjo4mNzdX2aZWq5XJD1UqFRqNRgKQEM+B1AQJIUQtkpqair+/P4MHD8bd3Z2WLVvWdJGEeGVJCBJCiFomLS2NqVOn0rNnT/z8/GjWrFlNF0mIV5I0hwkhRC1jb2/PsmXLuHnzJk2bNq3p4gjxypKaICGEqKWqOkHLqvBC/DUkBAkhRC0m8wAJ8deRrxZCCFGLSQAS4q8jIUgIIYQQdZKEICGEEELUSRKChBBCCFEnSQgSQgghRJ0kIUgI8dKTQa5CiD9DQpAQokakp6czc+ZM3nvvPTp06ICTkxMLFy4kJyfnqc5z+fJlRo0a9ReVUgjxKpMQJIR44aKiohg5ciQ3b95k+vTpbNq0ifHjx3PixAmGDx/OhQsXnvhc+/bt4+TJk39haYUQryrtmi6AEKJuSUtLY+nSpbi7uzN//nzl7w4ODjg5OeHq6sq8efNISEiowVIKIeoCqQkSQrxQYWFhGBsbM23atMe2NWrUiDlz5tCnTx9KSkooLS1l5cqV9OvXj3bt2tG5c2c8PDw4f/48AGvWrGHt2rUA2NrasmbNGgA0Gg0bN26kb9++tGvXDmdnZ7Zu3fqrZenTpw8dOnRg5MiRJCcnY2try/Hjx5V90tPTGTduHA4ODnTu3JmJEydy+fJlZfvx48extbUlOjqaXr160blzZw4cOICtrS2HDx+u9u+lpqZia2tLWlras19IIcQzk5ogIcQLU1lZyeHDh+nduzcGBga/us/AgQOV3319fUlNTWXatGlYWlqSlZVFaGgo06dPZ/fu3YwYMYIbN26wY8cOYmJiMDU1BSAgIICEhAQmTJiAnZ0d33//PYGBgdy9excfHx8A1q5dy7p16xg3bhzdunUjJSUFf3//amU5duwYXl5eODg4EBgYSFlZGRs2bGDkyJHExsbSunVrZd+1a9eyYMECSktLcXR0pFmzZnz11Vf06NFD2ScxMRFra2vs7e2f1yUVQjwDCUFCiBfm1q1blJWV0bJlyz/ct7y8nOLiYhYsWKAEo65du1JUVERQUBAFBQWYmpoqwadTp04AZGZmEhsby7Rp0xg/fjwAPXr0QKVSsWHDBtzc3NDT02PTpk24u7szY8YMZZ/79+8TExOjlGHlypVYWVmxceNGtLS0lP369u3L6tWrCQ0NVfZ1c3Ojf//+yuP333+frVu3UlxcjJGREaWlpezdu1cpkxCi5klzmBDihakKEmq1+g/31dXVJSwsjIEDB5KXl8exY8eIjo7m22+/BR6GpF9z7NgxKisr6d27Nw8ePFB+evfuTVlZGWlpaZw6dYrS0tJqoQVg0KBByu8lJSWkp6czYMAApdwADRo0oFevXpw4caLasW+++Wa1xx988AElJSV8/fXXAHz99deUlJTg6ur6h89dCPFiSE2QEOKFMTExwcjIiGvXrv3mPiUlJVRUVGBiYkJKSgqBgYFcuXIFIyMj2rZti6GhIfDbcwPdvn0bABcXl1/dnpeXh4mJCfCwD9KjGjdurPx+7949KisradKkyWPnaNKkCffu3av2t6pyVbGysqJr164kJibi6upKYmIijo6ONG/e/DefuxDixZIQJIR4oXr06MHx48cpKytDT0/vse2xsbEsW7aMuLg4fHx8cHJyYsOGDVhYWKBSqYiKiiIlJeU3z9+gQQMAIiIiMDIyemy7ubk5mZmZANy8eZNWrVop2woLC5XfjY2NUalUFBQUPHaOX375hddee+0Pn+sHH3zAvHnzyMjI4OjRo3z22Wd/eIwQ4sWR5jAhxAvl6enJ7du3CQkJeWzbL7/8wr///W9ef/11srOzKSsrY/z48VhaWqJSqQCUAFRVE1SvXvWPsS5dugAP+x+1b99e+SksLCQ0NJTbt2/Ttm1bjI2NlaaqKv/5z3+U3w0NDWnXrh179+6t1nx37949Dh48+ESdm52dnTEwMCAgIAAjIyOcnJye4AoJIV4UqQkSQrxQnTp1ws/Pj5CQEDIyMnB1daVhw4ZcvnyZsLAwysrKCAkJQVtbG21tbVasWIGnpyfl5eUkJCRw8OBB4GGzGfy35icpKYmOHTtia2vLkCFDWLhwIT///DPt2rUjMzOT4OBgWrZsibW1NVpaWnh5ebF69WoMDAzo2rUrJ06cYPv27cB/g9X06dMZN24c48ePx83NjYqKCjZu3Eh5ebkyyuz3GBgY4OLiQkxMDKNGjUJXV/cvuKJCiD9LVSmL7gghasChQ4eIiori3Llz3LlzBzMzM7p3787EiRMxMzMDHs4GvXbtWrKzszExMaFTp06MGTOG0aNHs3DhQtzd3cnLy8PHx4cLFy4wfPhwAgICePDgARs2bGDnzp3cuHGDxo0b06tXL/z9/ZVmrMrKStavX09MTAwFBQV07NiRvn378umnn5KQkMDbb78NPJwHaPXq1Zw5cwZdXV26dOnCtGnTeOONN5TtY8aMITIyEgcHh8eeZ3JyMt7e3sTFxdGhQ4cXc3GFEE9EQpAQos558OABSUlJODg4KIELHi7n8cknn3D8+HGlhulZLVq0iB9//JHExMTncj4hxPMjzWFCiDpHW1ubTZs2ERERgbe3Nw0bNuTSpUuEhITg6ur6XAJQZGQkV65cITY2lhUrVjyHUgshnjepCRJC1Ek5OTmsWrWK48ePc/fuXczNzRkyZAgTJkxAR0fnmc/v6+tLSkoKH374IXPmzHkOJRZCPG8SgoQQQghRJ8kQeSGEEELUSRKChBBCCFEnSQgSQgghRJ0kIUgIIYQQdZKEICGEEELUSRKChBBCCFEnSQgSQgghRJ0kIUgIIYQQdZKEICGEEELUSf8HSRLgBF3CKjwAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "sns.countplot(data=df, x='category', hue=\"left_predicate_id\")\n", + "plt.title('Count of each category')\n", + "plt.xlabel('Category')\n", + "plt.ylabel('Count')\n", + "plt.yscale('log')\n", + "plt.xticks(rotation=45) # Make x-axis labels diagonal\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "ed630003", + "metadata": {}, + "source": [ + "## Exploring categories\n", + "\n", + "### Non-entailed relationships\n", + "\n", + "i.e. this is an edge in UBERON that corresonds to a different kind of edge in ZFA (or vice versa), where the former\n", + "doesn't subsume the latter.\n", + "\n", + "For example, in UBERON PNS *part_of* NS; in ZFA PNS *is_a* NS" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "866cd714", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
left_subject_idleft_object_idleft_predicate_idcategoryleft_subject_labelleft_object_labelleft_predicate_labelright_subject_idright_object_idright_predicate_idsright_subject_labelright_object_labelright_predicate_labelsleft_subject_is_functionalleft_object_is_functionalsubject_mapping_predicateobject_mapping_predicateright_intermediate_idssubject_mapping_cardinalityobject_mapping_cardinality
22UBERON:0000010UBERON:0001016BFO:0000050NonEntailedRelationshipperipheral nervous systemnervous systemZFA:0000142ZFA:0000396rdfs:subClassOfperipheral nervous systemnervous system
24UBERON:0000011UBERON:0002410BFO:0000050NonEntailedRelationshipparasympathetic nervous systemautonomic nervous systemZFA:0001575ZFA:0001574rdfs:subClassOfparasympathetic nervous systemautonomic nervous system
29UBERON:0000013UBERON:0002410BFO:0000050NonEntailedRelationshipsympathetic nervous systemautonomic nervous systemZFA:0001576ZFA:0001574rdfs:subClassOfsympathetic nervous systemautonomic nervous system
196UBERON:0000095UBERON:0002342BFO:0000050NonEntailedRelationshipcardiac neural crestneural crestZFA:0001648ZFA:0000045rdfs:subClassOfcardiac neural crestneural crest
725UBERON:0000936UBERON:0003931BFO:0000050NonEntailedRelationshipposterior commissurediencephalic white matterZFA:0000320ZFA:0000338rdfs:subClassOfcaudal commissurediencephalic white matter
...............................................................
35155ZFA:0005661ZFA:0001488rdfs:subClassOfNonEntailedRelationshipolfactory bulb glomerulusmulti-tissue structureUBERON:0005387UBERON:0000481BFO:0000050|RO:0002131|RO:0002323olfactory glomerulusmulti-tissue structure
35458ZFA:0005829ZFA:0005249rdfs:subClassOfNonEntailedRelationshipSchlemm's canalvasculatureUBERON:0004029UBERON:0002049BFO:0000050|RO:0002131|RO:0002323canal of Schlemmvasculature
35680ZFA:0007017ZFA:0001477rdfs:subClassOfNonEntailedRelationshipposterior neural plateportion of tissueUBERON:0003057UBERON:0000479BFO:0000050|RO:0002131|RO:0002323chordal neural platetissue
35701ZFA:0007037ZFA:0001477rdfs:subClassOfNonEntailedRelationshipposterior neural tubeportion of tissueUBERON:0003076UBERON:0000479BFO:0000050|RO:0002131|RO:0002202|RO:0002254|R...posterior neural tubetissue
35702ZFA:0007038ZFA:0001477rdfs:subClassOfNonEntailedRelationshipanterior neural tubeportion of tissueUBERON:0003080UBERON:0000479BFO:0000050|RO:0002131|RO:0002202|RO:0002254|R...anterior neural tubetissue
\n", + "

205 rows × 20 columns

\n", + "
" + ], + "text/plain": [ + " left_subject_id left_object_id left_predicate_id \\\n", + "22 UBERON:0000010 UBERON:0001016 BFO:0000050 \n", + "24 UBERON:0000011 UBERON:0002410 BFO:0000050 \n", + "29 UBERON:0000013 UBERON:0002410 BFO:0000050 \n", + "196 UBERON:0000095 UBERON:0002342 BFO:0000050 \n", + "725 UBERON:0000936 UBERON:0003931 BFO:0000050 \n", + "... ... ... ... \n", + "35155 ZFA:0005661 ZFA:0001488 rdfs:subClassOf \n", + "35458 ZFA:0005829 ZFA:0005249 rdfs:subClassOf \n", + "35680 ZFA:0007017 ZFA:0001477 rdfs:subClassOf \n", + "35701 ZFA:0007037 ZFA:0001477 rdfs:subClassOf \n", + "35702 ZFA:0007038 ZFA:0001477 rdfs:subClassOf \n", + "\n", + " category left_subject_label \\\n", + "22 NonEntailedRelationship peripheral nervous system \n", + "24 NonEntailedRelationship parasympathetic nervous system \n", + "29 NonEntailedRelationship sympathetic nervous system \n", + "196 NonEntailedRelationship cardiac neural crest \n", + "725 NonEntailedRelationship posterior commissure \n", + "... ... ... \n", + "35155 NonEntailedRelationship olfactory bulb glomerulus \n", + "35458 NonEntailedRelationship Schlemm's canal \n", + "35680 NonEntailedRelationship posterior neural plate \n", + "35701 NonEntailedRelationship posterior neural tube \n", + "35702 NonEntailedRelationship anterior neural tube \n", + "\n", + " left_object_label left_predicate_label right_subject_id \\\n", + "22 nervous system ZFA:0000142 \n", + "24 autonomic nervous system ZFA:0001575 \n", + "29 autonomic nervous system ZFA:0001576 \n", + "196 neural crest ZFA:0001648 \n", + "725 diencephalic white matter ZFA:0000320 \n", + "... ... ... ... \n", + "35155 multi-tissue structure UBERON:0005387 \n", + "35458 vasculature UBERON:0004029 \n", + "35680 portion of tissue UBERON:0003057 \n", + "35701 portion of tissue UBERON:0003076 \n", + "35702 portion of tissue UBERON:0003080 \n", + "\n", + " right_object_id right_predicate_ids \\\n", + "22 ZFA:0000396 rdfs:subClassOf \n", + "24 ZFA:0001574 rdfs:subClassOf \n", + "29 ZFA:0001574 rdfs:subClassOf \n", + "196 ZFA:0000045 rdfs:subClassOf \n", + "725 ZFA:0000338 rdfs:subClassOf \n", + "... ... ... \n", + "35155 UBERON:0000481 BFO:0000050|RO:0002131|RO:0002323 \n", + "35458 UBERON:0002049 BFO:0000050|RO:0002131|RO:0002323 \n", + "35680 UBERON:0000479 BFO:0000050|RO:0002131|RO:0002323 \n", + "35701 UBERON:0000479 BFO:0000050|RO:0002131|RO:0002202|RO:0002254|R... \n", + "35702 UBERON:0000479 BFO:0000050|RO:0002131|RO:0002202|RO:0002254|R... \n", + "\n", + " right_subject_label right_object_label \\\n", + "22 peripheral nervous system nervous system \n", + "24 parasympathetic nervous system autonomic nervous system \n", + "29 sympathetic nervous system autonomic nervous system \n", + "196 cardiac neural crest neural crest \n", + "725 caudal commissure diencephalic white matter \n", + "... ... ... \n", + "35155 olfactory glomerulus multi-tissue structure \n", + "35458 canal of Schlemm vasculature \n", + "35680 chordal neural plate tissue \n", + "35701 posterior neural tube tissue \n", + "35702 anterior neural tube tissue \n", + "\n", + " right_predicate_labels left_subject_is_functional \\\n", + "22 \n", + "24 \n", + "29 \n", + "196 \n", + "725 \n", + "... ... ... \n", + "35155 \n", + "35458 \n", + "35680 \n", + "35701 \n", + "35702 \n", + "\n", + " left_object_is_functional subject_mapping_predicate \\\n", + "22 \n", + "24 \n", + "29 \n", + "196 \n", + "725 \n", + "... ... ... \n", + "35155 \n", + "35458 \n", + "35680 \n", + "35701 \n", + "35702 \n", + "\n", + " object_mapping_predicate right_intermediate_ids \\\n", + "22 \n", + "24 \n", + "29 \n", + "196 \n", + "725 \n", + "... ... ... \n", + "35155 \n", + "35458 \n", + "35680 \n", + "35701 \n", + "35702 \n", + "\n", + " subject_mapping_cardinality object_mapping_cardinality \n", + "22 \n", + "24 \n", + "29 \n", + "196 \n", + "725 \n", + "... ... ... \n", + "35155 \n", + "35458 \n", + "35680 \n", + "35701 \n", + "35702 \n", + "\n", + "[205 rows x 20 columns]" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df.query(\"category=='NonEntailedRelationship'\")" + ] + }, + { + "cell_type": "markdown", + "id": "c88189b1", + "metadata": {}, + "source": [ + "### No relationship\n", + "\n", + "In this case, the edge in UBERON has no corresponding edge in ZFA (or vice versa), entailed or otherwise" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "a6807bcf", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
left_subject_idleft_object_idleft_predicate_idcategoryleft_subject_labelleft_object_labelleft_predicate_labelright_subject_idright_object_idright_predicate_idsright_subject_labelright_object_labelright_predicate_labelsleft_subject_is_functionalleft_object_is_functionalsubject_mapping_predicateobject_mapping_predicateright_intermediate_idssubject_mapping_cardinalityobject_mapping_cardinality
7UBERON:0000004UBERON:0000475rdfs:subClassOfNoRelationshipnoseorganism subdivisionZFA:0000047ZFA:0001308peripheral olfactory organorganism subdivision
188UBERON:0000089UBERON:0004750BFO:0000050NoRelationshiphypoblast (generic)blastodermZFA:0000117ZFA:0001176hypoblastblastoderm
647UBERON:0000471UBERON:0003103BFO:0000050NoRelationshipcompound organ componentcompound organZFA:0001489ZFA:0000496compound organ componentcompound organ
661UBERON:0000479UBERON:0000468BFO:0000050NoRelationshiptissuemulticellular organismZFA:0001477ZFA:0001094portion of tissuewhole organism
663UBERON:0000480UBERON:0000468BFO:0000050NoRelationshipanatomical groupmulticellular organismZFA:0001512ZFA:0001094anatomical groupwhole organism
...............................................................
35714ZFA:0007057ZFA:0005297rdfs:subClassOfNoRelationshipocular blood vesselcranial blood vesselUBERON:0002203UBERON:0011362vasculature of eyecranial blood vasculature
35715ZFA:0007058ZFA:0000012BFO:0000050NoRelationshiproof platecentral nervous systemUBERON:0003054UBERON:0001017roof platecentral nervous system
35716ZFA:0007058ZFA:0001488rdfs:subClassOfNoRelationshiproof platemulti-tissue structureUBERON:0003054UBERON:0000481roof platemulti-tissue structure
35720ZFA:0007071ZFA:0001477rdfs:subClassOfNoRelationshipflexural organportion of tissueUBERON:0011577UBERON:0000479flexural organtissue
35722ZFA:0007072ZFA:0001488rdfs:subClassOfNoRelationshipblood sinusmulti-tissue structureUBERON:0006615UBERON:0000481venous sinusmulti-tissue structure
\n", + "

1375 rows × 20 columns

\n", + "
" + ], + "text/plain": [ + " left_subject_id left_object_id left_predicate_id category \\\n", + "7 UBERON:0000004 UBERON:0000475 rdfs:subClassOf NoRelationship \n", + "188 UBERON:0000089 UBERON:0004750 BFO:0000050 NoRelationship \n", + "647 UBERON:0000471 UBERON:0003103 BFO:0000050 NoRelationship \n", + "661 UBERON:0000479 UBERON:0000468 BFO:0000050 NoRelationship \n", + "663 UBERON:0000480 UBERON:0000468 BFO:0000050 NoRelationship \n", + "... ... ... ... ... \n", + "35714 ZFA:0007057 ZFA:0005297 rdfs:subClassOf NoRelationship \n", + "35715 ZFA:0007058 ZFA:0000012 BFO:0000050 NoRelationship \n", + "35716 ZFA:0007058 ZFA:0001488 rdfs:subClassOf NoRelationship \n", + "35720 ZFA:0007071 ZFA:0001477 rdfs:subClassOf NoRelationship \n", + "35722 ZFA:0007072 ZFA:0001488 rdfs:subClassOf NoRelationship \n", + "\n", + " left_subject_label left_object_label left_predicate_label \\\n", + "7 nose organism subdivision \n", + "188 hypoblast (generic) blastoderm \n", + "647 compound organ component compound organ \n", + "661 tissue multicellular organism \n", + "663 anatomical group multicellular organism \n", + "... ... ... ... \n", + "35714 ocular blood vessel cranial blood vessel \n", + "35715 roof plate central nervous system \n", + "35716 roof plate multi-tissue structure \n", + "35720 flexural organ portion of tissue \n", + "35722 blood sinus multi-tissue structure \n", + "\n", + " right_subject_id right_object_id right_predicate_ids \\\n", + "7 ZFA:0000047 ZFA:0001308 \n", + "188 ZFA:0000117 ZFA:0001176 \n", + "647 ZFA:0001489 ZFA:0000496 \n", + "661 ZFA:0001477 ZFA:0001094 \n", + "663 ZFA:0001512 ZFA:0001094 \n", + "... ... ... ... \n", + "35714 UBERON:0002203 UBERON:0011362 \n", + "35715 UBERON:0003054 UBERON:0001017 \n", + "35716 UBERON:0003054 UBERON:0000481 \n", + "35720 UBERON:0011577 UBERON:0000479 \n", + "35722 UBERON:0006615 UBERON:0000481 \n", + "\n", + " right_subject_label right_object_label \\\n", + "7 peripheral olfactory organ organism subdivision \n", + "188 hypoblast blastoderm \n", + "647 compound organ component compound organ \n", + "661 portion of tissue whole organism \n", + "663 anatomical group whole organism \n", + "... ... ... \n", + "35714 vasculature of eye cranial blood vasculature \n", + "35715 roof plate central nervous system \n", + "35716 roof plate multi-tissue structure \n", + "35720 flexural organ tissue \n", + "35722 venous sinus multi-tissue structure \n", + "\n", + " right_predicate_labels left_subject_is_functional \\\n", + "7 \n", + "188 \n", + "647 \n", + "661 \n", + "663 \n", + "... ... ... \n", + "35714 \n", + "35715 \n", + "35716 \n", + "35720 \n", + "35722 \n", + "\n", + " left_object_is_functional subject_mapping_predicate \\\n", + "7 \n", + "188 \n", + "647 \n", + "661 \n", + "663 \n", + "... ... ... \n", + "35714 \n", + "35715 \n", + "35716 \n", + "35720 \n", + "35722 \n", + "\n", + " object_mapping_predicate right_intermediate_ids \\\n", + "7 \n", + "188 \n", + "647 \n", + "661 \n", + "663 \n", + "... ... ... \n", + "35714 \n", + "35715 \n", + "35716 \n", + "35720 \n", + "35722 \n", + "\n", + " subject_mapping_cardinality object_mapping_cardinality \n", + "7 \n", + "188 \n", + "647 \n", + "661 \n", + "663 \n", + "... ... ... \n", + "35714 \n", + "35715 \n", + "35716 \n", + "35720 \n", + "35722 \n", + "\n", + "[1375 rows x 20 columns]" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df.query(\"category=='NoRelationship'\")" + ] + }, + { + "cell_type": "markdown", + "id": "a931a547", + "metadata": {}, + "source": [ + "### LeftEntailedByRight\n", + "\n", + "in this case UBERON has a direct edge that corresponds to two or more direct edges in ZFA (or vice versa)\n", + "\n", + "In this case the edges must chain together via OWL semantics.\n", + "\n", + "for example, UBERON has `ganglion part-of nervous system`, ZFA has `ganglion part-of PNS is-a nervous system`" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "188a5c06", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
left_subject_idleft_object_idleft_predicate_idcategoryleft_subject_labelleft_object_labelleft_predicate_labelright_subject_idright_object_idright_predicate_ids...right_object_labelright_predicate_labelsleft_subject_is_functionalleft_object_is_functionalsubject_mapping_predicateobject_mapping_predicateright_intermediate_idssubject_mapping_cardinalityobject_mapping_cardinalityleft_source
35UBERON:0000016UBERON:0000949BFO:0000050LeftEntailedByRightendocrine pancreasendocrine systemZFA:0001260ZFA:0001158BFO:0000050|RO:0002131|RO:0002202...endocrine systemUBERON
91UBERON:0000045UBERON:0001016BFO:0000050LeftEntailedByRightganglionnervous systemZFA:0000190ZFA:0000396BFO:0000050|RO:0002131...nervous systemUBERON
182UBERON:0000086UBERON:0000992BFO:0000050LeftEntailedByRightzona pellucidaovaryZFA:0001111ZFA:0000403BFO:0000050|RO:0002131...ovaryUBERON
645UBERON:0000467UBERON:0000061rdfs:subClassOfLeftEntailedByRightanatomical systemanatomical structureZFA:0001439ZFA:0000037BFO:0000050|RO:0002131|rdfs:subClassOf...anatomical structureUBERON
658UBERON:0000477UBERON:0001062rdfs:subClassOfLeftEntailedByRightanatomical clusteranatomical entityZFA:0001478ZFA:0100000rdfs:subClassOf...zebrafish anatomical entityUBERON
..................................................................
35706ZFA:0007043ZFA:0001477rdfs:subClassOfLeftEntailedByRighthindbrain neural tubeportion of tissueUBERON:2007043UBERON:0000479BFO:0000050|RO:0002131|RO:0002202|RO:0002254|R......tissueZFA
35711ZFA:0007048ZFA:0005145rdfs:subClassOfLeftEntailedByRightventral intermandibularis posteriormuscleUBERON:2007048UBERON:0001630RO:0002131|RO:0002202|RO:0002254|RO:0002258|RO......muscle organZFA
35717ZFA:0007068ZFA:0001486rdfs:subClassOfLeftEntailedByRightotic epitheliumepitheliumUBERON:0003249UBERON:0000483RO:0002131|RO:0002323|rdfs:subClassOf...epitheliumZFA
35721ZFA:0007072ZFA:0000010BFO:0000050LeftEntailedByRightblood sinuscardiovascular systemUBERON:0006615UBERON:0004535BFO:0000050|RO:0002131|RO:0002202|RO:0002254|R......cardiovascular systemZFA
35723ZFA:0007073ZFA:0001643rdfs:subClassOfLeftEntailedByRightblood sinus cavityanatomical spaceUBERON:0034940UBERON:0000464rdfs:subClassOf...anatomical spaceZFA
\n", + "

883 rows × 21 columns

\n", + "
" + ], + "text/plain": [ + " left_subject_id left_object_id left_predicate_id category \\\n", + "35 UBERON:0000016 UBERON:0000949 BFO:0000050 LeftEntailedByRight \n", + "91 UBERON:0000045 UBERON:0001016 BFO:0000050 LeftEntailedByRight \n", + "182 UBERON:0000086 UBERON:0000992 BFO:0000050 LeftEntailedByRight \n", + "645 UBERON:0000467 UBERON:0000061 rdfs:subClassOf LeftEntailedByRight \n", + "658 UBERON:0000477 UBERON:0001062 rdfs:subClassOf LeftEntailedByRight \n", + "... ... ... ... ... \n", + "35706 ZFA:0007043 ZFA:0001477 rdfs:subClassOf LeftEntailedByRight \n", + "35711 ZFA:0007048 ZFA:0005145 rdfs:subClassOf LeftEntailedByRight \n", + "35717 ZFA:0007068 ZFA:0001486 rdfs:subClassOf LeftEntailedByRight \n", + "35721 ZFA:0007072 ZFA:0000010 BFO:0000050 LeftEntailedByRight \n", + "35723 ZFA:0007073 ZFA:0001643 rdfs:subClassOf LeftEntailedByRight \n", + "\n", + " left_subject_label left_object_label \\\n", + "35 endocrine pancreas endocrine system \n", + "91 ganglion nervous system \n", + "182 zona pellucida ovary \n", + "645 anatomical system anatomical structure \n", + "658 anatomical cluster anatomical entity \n", + "... ... ... \n", + "35706 hindbrain neural tube portion of tissue \n", + "35711 ventral intermandibularis posterior muscle \n", + "35717 otic epithelium epithelium \n", + "35721 blood sinus cardiovascular system \n", + "35723 blood sinus cavity anatomical space \n", + "\n", + " left_predicate_label right_subject_id right_object_id \\\n", + "35 ZFA:0001260 ZFA:0001158 \n", + "91 ZFA:0000190 ZFA:0000396 \n", + "182 ZFA:0001111 ZFA:0000403 \n", + "645 ZFA:0001439 ZFA:0000037 \n", + "658 ZFA:0001478 ZFA:0100000 \n", + "... ... ... ... \n", + "35706 UBERON:2007043 UBERON:0000479 \n", + "35711 UBERON:2007048 UBERON:0001630 \n", + "35717 UBERON:0003249 UBERON:0000483 \n", + "35721 UBERON:0006615 UBERON:0004535 \n", + "35723 UBERON:0034940 UBERON:0000464 \n", + "\n", + " right_predicate_ids ... \\\n", + "35 BFO:0000050|RO:0002131|RO:0002202 ... \n", + "91 BFO:0000050|RO:0002131 ... \n", + "182 BFO:0000050|RO:0002131 ... \n", + "645 BFO:0000050|RO:0002131|rdfs:subClassOf ... \n", + "658 rdfs:subClassOf ... \n", + "... ... ... \n", + "35706 BFO:0000050|RO:0002131|RO:0002202|RO:0002254|R... ... \n", + "35711 RO:0002131|RO:0002202|RO:0002254|RO:0002258|RO... ... \n", + "35717 RO:0002131|RO:0002323|rdfs:subClassOf ... \n", + "35721 BFO:0000050|RO:0002131|RO:0002202|RO:0002254|R... ... \n", + "35723 rdfs:subClassOf ... \n", + "\n", + " right_object_label right_predicate_labels \\\n", + "35 endocrine system \n", + "91 nervous system \n", + "182 ovary \n", + "645 anatomical structure \n", + "658 zebrafish anatomical entity \n", + "... ... ... \n", + "35706 tissue \n", + "35711 muscle organ \n", + "35717 epithelium \n", + "35721 cardiovascular system \n", + "35723 anatomical space \n", + "\n", + " left_subject_is_functional left_object_is_functional \\\n", + "35 \n", + "91 \n", + "182 \n", + "645 \n", + "658 \n", + "... ... ... \n", + "35706 \n", + "35711 \n", + "35717 \n", + "35721 \n", + "35723 \n", + "\n", + " subject_mapping_predicate object_mapping_predicate \\\n", + "35 \n", + "91 \n", + "182 \n", + "645 \n", + "658 \n", + "... ... ... \n", + "35706 \n", + "35711 \n", + "35717 \n", + "35721 \n", + "35723 \n", + "\n", + " right_intermediate_ids subject_mapping_cardinality \\\n", + "35 \n", + "91 \n", + "182 \n", + "645 \n", + "658 \n", + "... ... ... \n", + "35706 \n", + "35711 \n", + "35717 \n", + "35721 \n", + "35723 \n", + "\n", + " object_mapping_cardinality left_source \n", + "35 UBERON \n", + "91 UBERON \n", + "182 UBERON \n", + "645 UBERON \n", + "658 UBERON \n", + "... ... ... \n", + "35706 ZFA \n", + "35711 ZFA \n", + "35717 ZFA \n", + "35721 ZFA \n", + "35723 ZFA \n", + "\n", + "[883 rows x 21 columns]" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df.query(\"category=='LeftEntailedByRight'\")" + ] + }, + { + "cell_type": "markdown", + "id": "06b37850", + "metadata": {}, + "source": [ + "## Breaking things down by direction\n", + "\n", + "By default, the command will do diffs in both directions, unless `--no-bidirectional` is passed.\n", + "\n", + "We can post-hoc break the summary statistics down based on the source of the left term" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "e07d6097", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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Tz9JEiEbMNCz3v+zZMmi06dPa926dSpbtqw6derEaw42FxkZqSxZsqh06dLaunWr8ubNq4iICFuX9dYxRBwflOgPmQkTJmjChAnKmjWrBg8erCJFimjUqFHasmWLQkNDbVwl8HLRx/L+/fu1b98+TZ48WUuWLNHMmTPVpk0bjRw5UosWLYrRFnhbogfDPXz4UPfu3VPixInVu3dvZcmSRT/99BPDxQGDRL+OJOnChQv6448/NHXqVC1YsEDbt29X0aJF9eeff2rWrFmSeP+HbUSfvmBvby/p0elDw4cP1z///KN+/frp77//traNj58JBGx8cIKCgrRt2zb16NFDX375pb744guNHDlSFStW1NChQ7V582bdu3fP1mUCT+nZs6cuXbpk/Xvt2rWaM2eO7O3tlS1bNklSypQpVb9+fTVu3Fhz587V1atXbVUuPhDRPWTbt29X165d1aJFC61bt05JkiQhZAMG6dy5sy5cuGANLNOmTVPPnj3l5eWljBkzSpJcXFzUrl07Zc6cWdu2bdPDhw9tWTI+UBEREdYRFhcvXlRISIhq166tatWqaf78+Tp48KCGDRumf/75R1L8nPiMgI0PTkREhK5fvy4vLy/r35LUt29fffTRR5o4caJ++eUXPXjwwJZlAjEEBATo9u3bSpEihXXZ7du3dfToUZ09e1bXrl2zLk+UKJGKFi2qmzdv6u7du7YoFx8QOzs7bdmyRW3atFFUVJSSJEmi77//XitXrozRk71582ZNmjQpRg8cgJc7evSoXFxclDJlSuuy9OnT6+TJk/r7778VEhIi6VHvtoeHh1q0aKHDhw/r9OnTtioZH6A1a9bo9u3bcnJykiSNGTNGzZo1U/Xq1TVw4ED9999/ypEjhxYtWqRDhw5pyJAh2rVrlyZNmqQrV67Eq88FAjbitWfNsJk8eXIlTZpUq1atkiQ5OTlZQ3ayZMkUFhamwYMH68iRI8/dB/CuJUuWTFOmTJGjo6OWL1+uc+fOqUmTJurcubM8PDw0f/58Xbhwwdo+VapUSpcunRwcmGoDb9epU6fUv39/9enTRxMnTlSXLl1ksVjUq1cvLViwwBqyU6RIob/++ssaBgDETu7cudW/f385OTlp0aJFOnHihCpWrKixY8cqPDxcs2bNktlstvZuP3z4UFmzZrV2JABv26pVqzR06FDNnDlTERERWr58uRYvXqzvvvtOZcqU0ZkzZzR48GCdP39e2bNn18KFC3XixAl17NhRW7dujdF5EB8wizjircdn2Ny+fbuioqLk6uqqwoULa/ny5Zo2bZrKli2rHj16SHo0NKV169Zq0qSJZs2apdu3b2v58uW2vAvAU+7evavy5csrWbJkmjhxotKlS6dFixZpxYoV+uijj1S9enW5u7tr7NixCg0N1aJFi6yvA+Bt+Omnn7RixQrNnj1bQUFB6tOnj5InTy4PDw9NmDBBgwcPVo0aNXT//n3dvXs33s8eCxjp8UnKLl68qA4dOig0NFTjx49X1qxZtW3bNnXo0EFffPGFKleurMSJE2vMmDEKDQ3V/Pnzef/HOzN06FDt2LFDxYoVU0BAgCpVqqSKFStKkpYtW6bly5fLw8NDPXr0UMaMGXX16lWdOXNGJUqUsE6SGV+OVwI24qXHP5CGDBmilStXymQyydXVVc2aNVO9evU0duxYrVq1St7e3sqRI4dOnTqlO3fu6JdfftGSJUu0YcMGzZ8/P14NWcH751kfOJcuXVLz5s3l4uKisWPHKl26dFqyZImmTZumq1evqnDhwkqTJo369esnJyenePWhhbhn4cKFWrBggRYtWqRly5bp4sWLatGihSIjI1WlShVJUt26ddWrVy9rDxuAl3vWe/eOHTs0ffp0BQQEaPTo0cqWLZu2bdumLl26KCwsTOXLl1dkZKQmTpwY70IL4qaHDx/K0dFRkjR48GDt2LFDV69e1ZgxY1SqVClru2XLlmnFihXy9PRU165dlTlzZuu66Ms8xhe84hDvmM1mayj+/ffftXXrVs2aNUvjx4/Xp59+qkmTJmnJkiXq0KGDhg8frtSpUys0NFQFCxbUzz//LEk6fvy4vLy89PDhw3g16QLeL5GRkdYvRvv379fBgwd16tQppU2bVtOnT9e9e/fUoUMHXbhwQV9//bXat2+v9OnTK2nSpGrUqJGcnJz08OFDvlzBMNHvh9euXdPp06f133//6bPPPtOgQYNkZ2enffv2KV++fEqfPr2cnJxUtmxZ9enTRw0aNIhXX56AdyH6vfvQoUPauXOnAgMDVaJECbVt21ZeXl7q1KmTTp8+rXLlymncuHFKmDChnJycNHDgQMI13pnocC1JPXr00KeffipJ2rRpk+7cuWNd99VXX6l27do6c+aMVqxYEWMf8e3zgR5sxBvbt29X4cKF5ezsLEn6888/tX79eqVMmVJ+fn6SHvX8LViwQOvXr1eLFi3UqFEjSY9+fTt79qwsFotWrVql1atXa9GiRfLx8bHV3cEHbNSoUSpSpIiKFy8u6dGwq9WrVysyMlJeXl767rvvVLVqVV26dElNmjRRokSJNGbMGKVLl04LFy7UkiVL5Ovrq0aNGil79uw2vjeIL6JHBv3yyy8aOnSozGaz3NzcNHDgQOXIkUP79+9Xo0aNtGHDBmXKlEmjRo3S7t27NXv2bCVMmNDW5QPvhbFjx6p06dLKkyePpEfv/+vXr9edO3eUJUsWtWvXTuXKldO+ffs0duxYBQYGavTo0cqaNat+/fVXderUSVWrVlXHjh2VNGlSG98bxGeP/4Aze/ZsHThwQIMHD1aiRIk0YsQI/frrrypdurRatGihJEmSWLfbtm2bSpcuHe9C9eOY/QbxwpgxY7R7925rILl+/bpmz56tw4cPq3LlytZ2adOmVf369WVnZ6dZs2YpMDBQfn5+CgoK0sSJE3X06FGlTJlSCxYsIFzDJo4dO6bff/9d+/btU6JEiXT37l1t3LhREyZM0O3bt/Xrr79q6NChioqK0ueff65Zs2apWbNmql+/vpYvX6569erJ3t5ekyZNkrOzs3r16mWd0RN4E3Z2djpw4IB69OghPz8/FS9eXIGBgcqYMaNMJpNcXFyUK1cudevWTUmTJtWBAwc0b948wjUQSwEBAZo8ebIOHDig3r1769ixY9q8ebMGDhyoRIkSafDgwRo/fryioqJUoUIFdejQQePHj1fDhg21fPlylS1bVuPHj1eLFi3k5OSk3r1704ONt+LxcL13715dvXpVW7dulaenp/z9/dWlSxdFRUXpjz/+kCS1bNlSiRMnliSVK1dOUvwbFv44erARb0RGRsrBwUH//POPfHx8dOLECY0dO1ZHjx5V7969recCStLly5c1efJk3b59W1OmTJEk3bt3T3fv3lWiRInk5uZmq7sB6LffftPcuXMVFRUlb29v+fr6qnnz5pKk06dPa86cOdqxY4e6du2qzz//XP/9959Gjx6t4cOHW8P0ihUrVKRIEaVNm9aWdwXvseXLl6t69epydna29l6PGzdOf//9tyZOnBij7ZkzZ7R9+3ZJ0rlz5/TgwQO1bNlSWbJksUXpwHsnOrD8999/qlOnjnLkyKH06dMrc+bMql+/viQpMDBQbdu21f3799WuXTt9+umn2rlzp7Zt26aePXtaw8rOnTuVIkWKGOe4AkZ4fI4jSRo+fLhWr16tChUq6Pr169q1a5fKlCmjIUOGKEGCBNaJz/Lly6du3bp9MN+vCdh47z3+K9rPP/+s9u3ba8iQIapRo4ZOnTqlsWPH6saNG2rZsqUqVapk3S4gIEDe3t4ymUycp4Q44fHjcOvWrVq0aJH27t2rZs2aqWPHjtZ2p0+f1ty5c/XXX3+pTZs2qlOnjnVdREQEPdZ4Y+fPn1fTpk01d+7cGD/S9OvXT//++69mz54tR0dH65etBQsWaPz48dq8ebM8PDysP3gCiL3oHr3z58/r66+/VnBwsJo1a6YuXbpY2wQGBqpdu3Z68OCBGjdurGrVqj21PfA2BAYGytPT0/r3wYMH1aFDB40ePVoFCxaUJB0+fFiNGzdW6dKlNWjQILm4uKh3794KCwvT8OHDP5iJg0kUeK9ZLJYYwbhChQoqW7as+vfvr7Vr1yp79uxq27atkidPrmnTpmnLli3WtsmSJSNcI8548jgsX768GjduLF9fX61fv1579+61rsuWLZsaNWokX19f/f7775L+N/kU4RpvKioqShkzZtS6deuUNm1aHTt2TA8ePJAkpU6dWqdOndLff/8tSdYvS/ny5VPy5MkVHh4uKf5NWAO8TWazWdL/XjcZM2bUsmXL5OXlpb/++kvHjx+3tvX09NSECRMUGhqqnTt3xtgPrzu8LT169NDkyZMl/e/7RnBwsBIkSKBs2bJJevTZkTdvXk2aNElbtmzRsGHD9ODBA/Xv31/Dhg2TnZ3dBzNxMKkC763HZwu3WCwKCwuTJE2aNEklS5ZUnz59tHbtWuXKlUvt2rVTihQpNHDgQO3ZsyfGfgjXsLXHw/XRo0e1ceNGXbx4USVLllTbtm2VOnVqjR49Wvv377dukzVrVvXo0cM6VPdD+VUYb1dgYKCOHDmihw8fys3NTUFBQWrVqpWaN28us9ms5s2bK3v27OrYsaOOHj2q+/fvS3o0W6yDg4NcXV0lcTwCsfXk+//vv/+ukydPKn369Fq4cKEuX76soUOH6uTJk9ZtPD09tWLFCg0cONBWZeMDU7x4cXXr1k2SFBoaKklydXXV1atXdfr0aUn/+z6dKVMmubu7a/Hixerfv791XVRU1Afz2cAQcbyXHj8HZM6cOdq9e7eCgoKUPXt2de7cWYkSJVLnzp21bds2/fjjj6pevboOHTqkrVu3ys/Pj195EWc8fiyPGDFCmzdv1u3bt9WwYUN16tRJkvTHH39oxowZevjwobp06WIdihWNURgwyr59+9SxY0eNGTNGUVFRCg0NlaOjo/z9/ZUjRw6NHz9et2/fVvfu3XXs2DFlzJhRiRIl0qlTpzRnzhxmrQdewZPv/xs2bFBUVJQqV64sPz8/OTs769y5c6pbt658fHzk7+//1GuMYeF4l5YvX65169Zp6NChSpo0qZo1ayZnZ2e1a9dOuXPnliQFBQWpb9++Klq0qAYOHKgff/xRNWvWtHHl7xYBG++dx8PExIkTNXv2bNWpU0cPHjzQxo0b5e3treHDhytbtmxq3769du7cqW7dusU4T5UPJMQ1M2bM0KxZszRmzBj5+voqPDxcZrNZd+7cUaZMmXT48GGNGzdO169f19ixY61DsgAjmc1m9erVS5s2bVJYWJhmzpyp4sWLa+fOnfLz81OBAgU0duxYOTo6asmSJbp9+7acnJxUoUIFpU+f3tblA++ladOmae7cuRo/frySJ0+uRIkSSZKuXr2qbNmy6dKlS/rqq6/k7e2t8ePHK0OGDLYtGB+ENWvWKCgoSF5eXqpcubIcHBw0c+ZMrVu3TunTp9eAAQN09OhRjRo1Svb29qpdu7Y8PT21aNEihYSEaMmSJWrRooWyZs2q77//3tZ3550iYOO9tX//fq1cuVIVK1ZU6dKlJT2auKxBgwby8PDQkiVLJEnffvut7OzsNGfOHNsVCzyHxWJRaGiounTpogoVKqhmzZo6deqU1q9fr3Xr1snOzk7FihXT4MGD9csvv2jfvn3q0aMHPxDhrfntt9/UunVrOTk5acKECSpZsqTs7OysITtPnjyaMmUKoyYAA0RERMjPz09FixZVvXr1dPLkSf30009au3atbt26pfr166tXr146c+aMhg4dqqlTp/L+j7euefPmOnPmjMLCwhQcHKwvvvhCgwcPliQtXLhQK1asUNq0aTVo0CCdOXNGS5cu1caNG5UpUyZ5eXlpypQpcnR0VJMmTVSiRAk1adLExvfo3SJg4720a9cu9e3bVzdv3tT06dNVsGBBPXz4UI6Ojjp79qxq1aolf39/1a5dWxJDaBG3PHmZC4vFonbt2un+/fsqUaKE5syZowwZMqhEiRKKiIjQsmXLtGTJEqVOndq6DaMw8LacPn1aFy9e1O+//65NmzZpyJAhqlChgjVkf//990qXLp1mz54d4xJeAF7uydfLvXv31KxZM6VOnVqZMmXS4sWLlTlzZpUsWVIuLi7q37+/NmzYoI8++si6De//eJvq1q2r8PBwDR8+XJJ05MgR9ezZU4MHD1aNGjUkPQrZy5cvV7p06dSvXz95enrq1q1bsrOzk8lkkoeHh0aMGKFVq1Zp8eLFH9wIJ66hgffCkx9IHh4eKlmypJYuXarDhw+rYMGCcnR0lNlslpeXl5InT66oqChre2YLR1zx+HEYFhYmFxcX2dnZqXTp0lq7dq2mTJmiRo0aqUKFCsqSJYvOnTunbdu2WWdxjsaXKxgl+v310qVLevDggZIlS6Zs2bKpQoUKCg8PV/fu3WUymfTpp5+qePHiGjhwoIYPH67AwEClTJmScA3E0uPv/+Hh4XJ2dpabm5tq1qypadOmae/evapbt67KlSunbNmy6dSpU8qbN6918sDo1yrv/3hbosP1ggULrMedi4uLUqZMaZ1MWJLq1asnSVq5cqX69u2rbt26KW3atDp16pS+//57OTg4KDAwUDNmzPjgwrVEwMZ74PEPpIcPH8rOzk7ZsmVT69atFRUVpWXLlilJkiSqVauWTCaTEiRIIBcXF+sbQzTCNWzt8cvKzZkzR9u3b5eXl5c+//xz1a5dW7Vq1VJoaKjc3Nx0//59RUREaNCgQfL09FTGjBltXD3iKzs7O23ZskVDhw5VcHCwcuTIoVq1aql69eoaOXKkunTpom7duqlPnz4KCQmRl5eXli9fLhcXF1uXDrw3Hn//nz17tg4dOqTw8HB17txZX331lcqVK6eECRMqQYIEevjwoSIjIzVy5Ei5uLgoRYoUkpidH29X/fr1JUlLliyRk5OTIiIi5OTkpOTJkytp0qRKnjx5jPbRIXvq1KlatmyZOnfurOTJk6tVq1ZycnJSjhw5lDJlynd+P+IChogjTnu853r27Nk6fvy4zp8/r7Jly6pq1ary9vbW6NGj9fPPP6tixYpKliyZDhw4oIsXL2rDhg38yos44/FjeezYsZo3b56qVKmiHTt2KHHixKpbt66++uor3bhxQ9OnT9fevXvl6uqqsLAwrVixwjpCgx+KYLTDhw+rZcuWatWqlby8vLR06VJFRkbq66+/1hdffCFJ6tq1q37++WclSJBAixcvVqZMmWxcNfD+ePy9e9iwYVq+fLlKlSqlY8eOKSQkRKNGjVLRokV148YNDRw4ULdv31ZYWJgsFouWLVvG+z/eutWrV6tHjx7q0KGDWrduHeN469Wrl1asWKHOnTvLYrEoS5Ys8vT0VJ48eSRJf/75p4oVKyYHB/ptoxGwEWc9/uIeP3685s+fr7p16+r8+fP677//ZDabNXz4cKVJk0YjR47UqlWrlDZtWjVu3Fg1a9a0XnOPkI245MyZMxo8eLDatWun/Pnz68aNG+rTp48CAwNVp04d1apVS+vXr9c///wjb29v1atXTw4ODoqMjOTDC4a7dOmSNm/erLt378rPz0+SdO7cOQ0cOFD37t1TnTp1rJdX2b9/v9KkSWPtTQPwau7cuaN+/fqpefPmypEjh8LDw9W+fXsdO3ZMI0eOVNGiRbV+/XqdOHFCKVOm5P0f78x///2nJUuWaOPGjapXr55atmwpSWrfvr127dql1KlTy9HRUWfOnNGDBw/k4OAgR0dHtW7dWi1atJAkjtPH8CggzooO15cuXdLJkyc1dOhQlSlTRpK0Y8cOzZ07Vz/++KMmTpyopk2bysHBQb/++qtCQ0NlMpmYeAdxzsKFC/Xzzz/r/v371susJE+eXL169dKAAQO0ePFiOTk5qXr16jG2i4qK4kMLhrJYLAoODlaDBg108+ZNVatWzbouU6ZM8vf316BBg7RixQqFhYWpXr16T11/HUDsLVu2TCNHjlTq1Kmtl+FydnbW2LFj1bFjR3Xu3FkjRoxQtWrVYrweef/H2xYVFaUMGTLom2++kdls1rx58+Tq6qojR47o/PnzWrZsmVKlSiVnZ2cFBgbq2LFjunz5sk6dOhVjdnCO0/9hrAnitA0bNujTTz/VkSNHlDBhQuvyEiVKqHbt2rpy5YrOnTuntGnT6ptvvlGZMmU0a9YsTZs2zTqTIRBX+Pj4WD+UDh48aF2eNm1a9erVS8mSJdOYMWO0detWSY9CkMSEZjCW2WyWnZ2dkiRJosGDByt16tQ6ceKEdu/ebW2TOXNm9ezZU5GRkdq2bZvu3r1rw4qB95+vr68yZcqks2fP6v79+5IevRYTJEigMWPGKG/evGrSpImOHTsWYzve//G2RR9jjo6O6ty5s6pUqaIJEybo999/15IlS5QxY0ZrG09PT5UqVUr16tXTgAEDrCMsEBPpA3HKk2csVK1aVRUrVlRgYKDOnDkT40VcoUIFSdLx48clPep1+fbbb1WsWDGtWrVKwcHBT+0PeFfMZvNTfxcqVEgTJkxQqlSpNG/evBiBJm3atPr+++9VqVIl60gNRmDASNHvh+Hh4Xr48KHCw8NVtGhRDR06VKGhoZozZ472799vbZ8pUyYNGzZMAwYMsPa4AXi5J9//JSlnzpzq37+/0qVLp86dO+vGjRvW0XYJEiTQyJEj1aRJE/n6+tqgYnzodu7cqapVqyowMFCNGjXSF198oQQJEmjZsmWSHvVOP+u4jl6HmDgHG3HG4+dc37t3Tw8fPpSHh4ckqWXLljp+/LhGjx6tQoUKyc7OTkFBQWrSpInatm2r8uXLW/dz6dIlubi4yNvb2yb3A3j8WP7pp590/vx5XbhwQVWqVFHp0qX1999/67vvvlPatGnVokULFSlS5Kl9MH8AjBR9ysz27du1ePFiXb58WREREWrSpIlq166tgwcPqmvXrvLx8VGLFi2UP39+W5cMvJcef//fvHmzzpw5o7CwMGXLlk3Vq1fXmTNn1LFjR5lMJs2YMUPJkyd/6pQ23v/xrp04cUL+/v5q3769ypUrZz0ne8OGDfr222/VvHlzSU9fNhfPRsBGnPD4B9L06dP166+/ysHBQeXKlVOjRo0kSU2aNNHJkyf1xRdfKE2aNNq+fbuuXr2qVatWycHBgRc9bO7JY3D48OFau3atMmXKpNDQUJ08eVJfffWVunfvrv/++08dO3ZU+vTp1aBBA5UsWdKGlSO+evyY3LZtmzp37qxmzZopX758WrlypTZu3Kj58+erUKFC2rdvn3r27KnkyZOrc+fOyps3r22LB95jw4YN09q1a5UtWzbdunVL//77rz7//HMNGjRIZ8+eVfv27eXo6KjJkyd/sJcygm08b0b6Hj166ODBg/rpp5/k4OCgS5cuadGiRfrpp59Us2ZNdezY8d0X+55iiDhs7vFrQw4dOlTTp09Xjhw55O3trcmTJ2vevHmSpFmzZil//vyaPXu2Nm/erEyZMmndunXW8z8I17C1x4/BLVu2aMOGDZo6dapmzJihlStXyt/fX+vWrdPkyZOVPXt2DRkyRPv27dPOnTttWDXio2vXrkl6dEyazWbdv39fCxYsUKtWrdSuXTt99NFHOn78uJo0aaIkSZJo//79KlSokHr16qXg4OCnrncKIPZ++eUXbdq0SRMmTNDMmTM1f/58DRw4UBs3btSAAQOUOXNmjRkzRgEBARo9erSty8UHIrpPNfo794kTJ3Tjxg3r+tatWythwoT66aefJD06da1evXoqWbKkTp8+zWmXr4CADZuJiIiQ9L9QsnHjRm3ZskUzZsxQr169VKpUKYWEhGjSpEmaOnWqJGnSpEn69NNPdebMGeXMmdO6L87/gC317t1b27Zti7Hs8uXLypUrl3LkyGE9xhs0aKDWrVtrzpw5OnfunPLly6fly5era9eutigb8dTChQvl5+enPXv2SHr0ZerBgwe6ePGiypYtq+DgYNWqVUtFihRRt27dtGPHDv3www+6f/++PvnkEy1btoweNSCWWrdurRMnTsRYdubMGfn4+ChfvnySJHd3d1WqVEkdO3bU+vXrdezYMWXJkkWLFy/W4MGDbVE2PjDBwcG6fv269e+tW7eqTp06at68udasWaObN28qXbp0SpMmjX799VdruzRp0qhDhw6aPHmy7OzsCNmxRMCGTfzwww9au3atQkNDrctOnjypnDlzKnfu3Dpx4oRWrFihDh066PPPP9eUKVO0cOFCSY+uiZ03b14NGDBAq1evVnh4uK3uBqCrV6/KZDLpk08+ibH88uXL+vvvvyU9mpkz+gelatWqydHRUefPn5ckffTRR7K3t1dUVNS7LRzx1kcffaRr165p9uzZ2rt3r6RHM7+mTJlSy5YtU40aNVS2bFn17t1bkqzvoY6OjpIeXToIwMuFh4fL29tbWbJkkfS/yc1u3bqlkJAQ6/u+JCVIkED58+fX/fv3FRISIklKnz497/9467p3764WLVqodu3a6ty5s+7cuaPy5ctryJAh+vjjj9W3b1916tRJc+bMUfPmzfXXX3/pjz/+sG6fLFky62goRovGDgEb79zixYu1ePFiLViwQL/++qvu3bsn6dFlAhImTKgbN25ozZo1yp07t1q0aKGSJUsqLCxM/fv315gxYyRJU6ZMUcaMGTVhwgQ9fPjQhvcGH7L79+8rVapU6t27txwdHbV8+XLrD0HFixdXZGSk5s2bp/v378vJyUmSFBoaqhQpUihZsmQx9sWENjBCZGSkihQpovHj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165drB4ftWvXxtnZmTlz5tCxY0dcXFwoVaoUPj4+sa7XLlCgAK6urnh4eFiWf/bZZ7i4uJAyZcrnusW/LXd3d7p3786kSZO4ffs2efLkYenSpc91xc+bNy9JkyalatWqluPgdZUsWRI/Pz+mT5+Ot7c3dnZ2fPbZZyxcuPCVIexlx++rpEiRgvnz5zNlyhT69+9PeHi4pbU8ppfB67wHmTJlYuzYscyaNYs2bdqQJUsWhg8fzvDhw/+1hhw5crBkyRImTpxI165dcXFxoXTp0vTo0QN7e3vs7e1ZtmwZEydOZPz48Tx48IC0adPSvXt3WrRo8Xov6huImTor5seZF+nUqROdO3f+132tX7+eJEmSPHf9f4w0adJQqFAhtm7dSt++fV94LbmIiMH8JiPoiIiIvOeCgoIoW7YsU6dOfa41skmTJgAsWbIkPkqT99TRo0f55ptv2LhxI9myZYvvckRE5AOmFmwREfkonDx5kp9//pkff/yRjBkzxppSSuRF9u/fz/79+wkICKBkyZLxFq5NJtNrjZj/b4PsiYhI/NOZWkREPgoREREsXLiQlClTMmnSpP90fbckDHfv3mXhwoV8+umnjBgxIt7qmDFjBtOnT//X9X7++eeXjsItIiLvB3URFxEREYlHQUFB3Lp161/Xy5o16xtfIy4iInFLAVtERERERETECtR/TkRERERERMQKFLBFRERERERErEABW0RERERERMQKNIr4P5jNZkwmXZYuIiIiIiIiYDQaMBgMr7WuAvY/mExmQkLC4rsMEREREREReQ+4uztjY/N6AVtdxEVERERERESsQAFbRERERERExAoUsEVERERERESsQAFbRERERERExAoUsEVERERERESsQKOIi4iIiIhIvDGZTERHR8V3GZJA2djYYjRar91ZAVtEREREROKc2Wzm/v0QwsMfxncpksA5OrqQOLH7a891/SoK2CIiIiIiEudiwrWLixv29g5WCTcib8JsNhMZGcHDh3cBSJIk2X/e50cTsCdMmMDOnTsxGo20b9+eqlWrxndJIiIiIiLyAiZTtCVcu7gkju9yJAGzt3cA4OHDu7i6uv3n7uIfRcDet28ff/75J5s2beL+/ftUrVqV8uXLY29vH9+liYiIiIjIP0RHRwP/Czci8SnmOIyOjsJo/G8Z8qMI2EWLFqVgwYIYjUZu3bqF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RERERETEChSwRURERERERKxAXcTjkNFoiLfrSRKS1x2AQERERERExJoUsOOI0WggaVInhT8RERERkf+nCY3kfWDN41ABO44YjQZsbIzMWLGba7dC47ucj1qerGmoVzlvfJchIiIiIi9hY/N0Gq7IyAjs7R3iuRpJ6CIjIwCwsfnv8VgBO45duxXKxWsvn2Re/rs0yRPHdwkiIiIi8gpGow2Oji48fPj0e7G9vQMGgy6llLhlNpuJjIzg4cO7ODq6YDT+997GCtgiIiIiIhLnEid2B7CEbJH44ujoYjke/ysFbBEREZGPlAZYjTuau/nNGQwGkiRJhqurG9HRUfFdjiRQNja2Vmm5jqGALSIiIvIRMhoNuLk5YjTaxHcpCYLJFM3du+EK2W/BaDRiNNrHdxkiVqGALSIiIvIRetp6bcOFzfMIv3Mjvsv5qDkmS02m6q0xGg0K2CIJnAK2iIiIyEcs/M4NwoMux3cZIiIJgiZlFhEREREREbECtWCLSIKiAX/iho2Nfr8VERGRhEcBW0QSDKPRQNKkTgp/IiIiIvJOKGCLSIJhNBqwsTEyY8Vurt0Kje9yPmp5sqahXuW88V2GiIiISJxSwBaRBOfarVAuXrsb32V81NIkTxzfJYiIiIjEOfWTFBEREREREbECBWwRERERERERK1DAFhEREREREbECBWwRERERERERK1DAFhEREREREbECBWwRERERERERK1DAFhEREREREbECBWwRERERERERK1DAFhEREREREbECBWwRERERERERK1DAFhEREREREbECBWwRERERERERK1DAFhEREREREbECBWwRERERERERK1DAFhEREREREbECBWwRERERERERK1DAFhEREREREbECBWwRERERERERK1DAFhEREREREbECBWwRERERERERK1DAFhEREREREbECBWwRERERERERK1DAFhEREREREbECBWwRERERERERK1DAFhEREREREbECBWwRERERERERK1DAFhEREREREbECBWwRERERERERK1DAFhEREREREbECBWwRERERERERK7CN7wJEREQk4TEaDRiNhvgu46NmY6N2FBGRuKaALSIiInHKaDSQNKmTAqCIiHx0FLBFREQkThmNBmxsjMxYsZtrt0Lju5yPVp6saahXOW98lyEikqAoYIuIiEi8uHYrlIvX7sZ3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Dh3C39+fxMTEP32elJTExIkT+fHHH/Hx8TFrIUVERERERERedn97H+ySJUsyePBgxo0bx7p166hatSr58+cnNTWV27dvc+jQIR4+fEivXr2oUaNGZpZZRERERERE5KXztwM2gI+PD8WLF2fBggXs3LnT1JPt7OxM9erV6dixI6VLl86UgoqIiIiIiIi8zP6jgA1Qvnx5ypcvD0BMTAw2NjZkz57d7AUTEREREREReZX8xwE7vf9tT2wRERERERGRN8nfXuRMRERERERERP49BWwRERERERERM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETEDBWwRERERERER1ICysQAAW9hJREFUM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETEDBWwRERERERERM1DAFhERERERETGD1y5gR0VFUadOHUsXQ0RERERERN4wr1XAPnDgAF999RXR0dGWLoqIiIiIiIi8YV6rgL1mzRoCAwMtXQwRERERERF5A9lYugDmNHnyZEsXQURERERERN5Qr1UPtoiIiIiIiIilKGCLiIiIiIiImIECtoiIiIiIiIgZKGCLiIiIiIiImMFLGbCDg4Np27ZthveMRiPTpk2jRo0alClThs6dO3Pjxo2/PP/YsWNZUUwRERERERERk5duFfHly5cTGBhIhQoVMrw/a9YsVqxYgb+/P3ny5GHSpEl06tSJ9evXY2dnZ9Yy2NiY/7mDtfVL+SxD5B97lf62X6WyivynXqW/71eprCL/Cf1ty1/R30XWsvS/90sTsKOiohgxYgSHDh2iUKFCGT5LSkpi4cKF9OvXj5o1awIQEBBAjRo12LZtG40aNTJbOaysDLi6Opvt+0Red9mzO1q6CCKC6qLIy0D1UMTyLF0PX5qAffr0aWxtbfnhhx+YOXMmt27dMn127tw5YmNjqVq1qum97NmzU6JECX755RezBmyjMY0nT+LM9n0vWFtbWfw/tkhmePIkntRUo6WL8beoHsrrTHVRxPJepXooWUfXvKyVGfUwe3bHv90z/tIE7Fq1alGrVq2//Ozu3bsAeHp6Znjfw8PD9Jk5paTowijyd6WmGlVnRF4Cqosilqd6KGJ5lq6Hr8SEgPj4eIA/zbW2t7cnMTHREkUSERERERERyeCVCNgODg7A87nY6SUmJuLoqOEWIiIiIiIiYnmvRMB+MTT83r17Gd6/d+8euXPntkSRRERERERERDJ4JQJ28eLFyZYtG4cOHTK99+TJE86cOUPFihUtWDIRERERERGR516aRc7+N3Z2drRp04bJkyfj5uZGvnz5mDRpEnny5KFu3bqWLp6IiIiIiIjIqxGwAXx9fUlJSWHYsGEkJCRQsWJFFixYgK2traWLJiIiIiIiIvJyBmx/f/8/vWdtbU3//v3p37+/BUokIiIiIiIi8r97JeZgi4iIiIiIiLzsFLBFREREREREzEABW0RERERERMQMFLBFREREREREzEABW0RERERERMQMXspVxEVEREREJHNZWRmwsjJYuhivPWtr9Wm+SRSwRURERETeMFZWBt56y0nhT8TMFLBFRERERN4wVlYGrK2tmLnyJ27de2zp4rzWShfLS8v6ZSxdDMkiCtgiIiIiIm+oW/cec/XWQ0sX47WWN1d2SxdBspDGhIiIiIiIiIiYgQK2iIiIiIiIiBkoYIuIiIiIiIiYgQK2iIiIiIiIiBkoYIuIiIiIiIiYgQK2iIiIiIiIiBkoYIuIiIiIiIiYgQK2iIiIiIiIiBkoYIuIiIiIiIiYgQK2iIiIiIiIiBkoYIuIiIiIiIiYgQK2iIiIiIiIiBkoYIuIiIiIiIiYgQK2iIiIiIiIiBkY0tLS0ixdiJdJWloaRmPm/JNYW1sR8ziOlJTUTPl+ec7e3pYc2RxIevqQtNQUSxfntWWwtsHOxZXUVKOli/IfUT3MGqqHWUd1Uf4d1cOso3oo/xvVxayRmfXQysqAwWD4e+VQwBYRERERERH55zREXERERERERMQMFLBFREREREREzEABW0RERERERMQMFLBFREREREREzEABW0RERERERMQMFLBFREREREREzEABW0RERERERMQMFLBFREREREREzEABW0RERERERMQMFLBFREREREREzEABW0RERERERMQMFLBFREREREREzEABW0RERERERMQMFLBFREREREREzEABW0REREQsLi0tzdJFEBH5xxSwRTKBGgkilmc0Gi1dBBH5P1y/fp0zZ84AYDAYAN1DReTVpoAtYgZ/bMi/aCSAGgoilmJl9fwWd+7cOR4/fmzh0ojIH6WlpbFx40YmTZrE/fv32bRpE3fv3s1wDxWRl8//9gBb7V4wpOlfQeQfMRqNpob81q1buXLlCtbW1pQsWZIqVapYuHQib570dXLv3r2MHDmSbt260aBBA5ydnS1cOhFJ7+LFi3z66ad4eHhgNBpZvXo1uXPntnSxROTfSH+P3bJlC3fv3iUhIYFq1arx/vvvY2VlRVpa2hv9oMzG0gUQeZWlpaWZLjITJkwgIiICb29vnjx5wrRp0/D19aVz586mY9/ki41IVkhfJ0NCQrh58yZ37twhKCgIg8FA/fr1yZYtm4VLKSIAKSkpvPPOO9SsWZMdO3ZQpkwZkpKSLF0sEfk3/qrd+84773Du3Dl++OEHatWqRe/evbG2tn6j270aIi7yD7y4cBw5coRffvmF2bNns2rVKhYsWEC3bt2YMmUKK1asyHCsiGSeF/VsxowZzJgxg2LFijF+/HgqV67M1KlT2bp1K7GxsRYupcib7cXwUmtrawDKly/PpEmTuHDhAiNHjuT8+fOmYzXQUuTlkJqaarrHXrt2jb179xIcHMyyZcvYt28fVatW5ccff2ThwoXAm93uVcAW+Q8NHTqUGzdumF6vW7eOxYsXY21tTfHixQHw9PSkTZs2dOjQgSVLlnD79m1LFVfkjfPw4UN27tzJ4MGD+eyzz/j000+ZMmUK9erVY8KECWzZsoVnz55Zupgib6SkpCRTD9j169d58uQJLVq0oHHjxixdupRff/2ViRMncuHCBUALn4lYWt++fbl27ZrpgdjcuXMZOnQoOXPmxNvbGwBHR0d69OhBkSJF2LlzJ8nJyZYsssUpYIv8B+7du8eDBw/IkyeP6b0H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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "unique_srcs = df['left_source'].unique()\n", + "n_unique = len(unique_srcs)\n", + "fig, axs = plt.subplots(nrows=n_unique, figsize=(10, 6*n_unique))\n", + "\n", + "for ax, left_source in zip(axs, unique_srcs):\n", + " sub_df = df[df['left_source'] == left_source]\n", + " sns.countplot(data=sub_df, x='category', hue='left_predicate_id', ax=ax)\n", + " ax.set_title(f'Count of each type broken down by left_source = {left_source}')\n", + " ax.set_xlabel('Type')\n", + " ax.set_ylabel('Count')\n", + " ax.set_yscale('log') # Make y-axis logarithmic\n", + " ax.tick_params(axis='x', rotation=45) # Make x-axis labels diagonal\n", + "\n", + "plt.tight_layout() # Ensure layout is tight so labels don't get cut off\n", + "plt.show()\n" + ] + }, + { + "cell_type": "markdown", + "id": "5f2b0bc2", + "metadata": {}, + "source": [ + "As expected `MissingMapping` dominates the Uberon->ZFA direction. Note this is not actually \"missing\" in the sense of incomplete, we expect most edges in uberon to be non-mappable to ZFA due to difference in taxonomic scope." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cae729ec", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/Commands/LogicalDefinitions.ipynb b/notebooks/Commands/LogicalDefinitions.ipynb new file mode 100644 index 000000000..a7c13c384 --- /dev/null +++ b/notebooks/Commands/LogicalDefinitions.ipynb @@ -0,0 +1,1002 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "738137ef", + "metadata": {}, + "source": [ + "# OAK logical-definitions command\n", + "\n", + "This notebook is intended as a supplement to the [main OAK CLI docs](https://incatools.github.io/ontology-access-kit/cli.html).\n", + "\n", + "This notebook provides examples for the `logical-definitions` command, which can be used to **lookup and summarize logical defs**\n", + "\n", + "For more on logical definitions, see [Logical Definitions](https://incatools.github.io/ontology-access-kit/guide/logical-definitions.html) in the OAK guide.\n", + "\n", + "## Help Option\n", + "\n", + "You can get help on any OAK command using `--help`" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "838586ce", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Usage: runoak logical-definitions [OPTIONS] [TERMS]...\r\n", + "\r\n", + " Show all logical definitions for a term or terms.\r\n", + "\r\n", + " To show all logical definitions in an ontology, pass the \".all\" query term\r\n", + "\r\n", + " Example; first create an alias:\r\n", + "\r\n", + " alias pato=\"runoak -i obo:sqlite:pato\"\r\n", + "\r\n", + " Then run the query:\r\n", + "\r\n", + " pato logical-definitions .all\r\n", + "\r\n", + " By default, \".all\" will query all axioms for all terms including merged\r\n", + " terms; to restrict to only the current terms, use an ID query:\r\n", + "\r\n", + " pato logical-definitions i^PATO\r\n", + "\r\n", + " You can also restrict to branches:\r\n", + "\r\n", + " pato logical-definitions .desc//p=i \"physical object quality\"\r\n", + "\r\n", + " By default, the output is a subset of OboGraph datamodel rendered as YAML,\r\n", + " e.g.\r\n", + "\r\n", + " definedClassId: PATO:0045071 genusIds: - PATO:0001439\r\n", + " restrictions: - fillerId: PATO:0000461 propertyId: RO:0015010\r\n", + "\r\n", + " You can also specify CSV to generate a flattened form of this.\r\n", + "\r\n", + " Example:\r\n", + "\r\n", + " pato logical-definitions .all --output-type csv\r\n", + "\r\n", + " You can optionally choose to \"--matrix-axes\" to transform the output to a\r\n", + " matrix form. This is a comma-separated pair of axes, where each element is a\r\n", + " logical definition element type: \"f\" for filler, \"p\" for predicate, \"g\" for\r\n", + " genus, \"d\" for defined class.\r\n", + "\r\n", + " Example:\r\n", + "\r\n", + " - Each property/predicate is a column - For repeated properties, columns of\r\n", + " the form prop_1, prop_2, ... are generated\r\n", + "\r\n", + " Example:\r\n", + "\r\n", + " pato logical-definitions .all --matrix-axes d,p --output-type csv\r\n", + "\r\n", + " This will generate a row for each defined class with a logical definition,\r\n", + " with columns for each predicate (\"genus\" is treated as a predicate here).\r\n", + "\r\n", + " Limitations:\r\n", + "\r\n", + " Currently this only works for definitions that follow a basic genus-\r\n", + " differentia pattern, which is what is currently represented in the OboGraph\r\n", + " datamodel.\r\n", + "\r\n", + " Consider using the \"axioms\" command for inspection of complex nested OWL\r\n", + " axioms.\r\n", + "\r\n", + " More examples:\r\n", + "\r\n", + " https://github.com/INCATools/ontology-access-\r\n", + " kit/blob/main/notebooks/Commands/LogicalDefinitions.ipynb\r\n", + "\r\n", + " Python API:\r\n", + "\r\n", + " https://incatools.github.io/ontology-access-kit/interfaces/obograph\r\n", + "\r\n", + " Data model:\r\n", + "\r\n", + " https://w3id.org/oak/obograph\r\n", + "\r\n", + "Options:\r\n", + " --unmelt / --no-unmelt Flatten to a wide table [default: no-\r\n", + " unmelt]\r\n", + " --matrix-axes TEXT If specified, transform results to matrix\r\n", + " using these row and column axes. Examples:\r\n", + " d,p; f,g\r\n", + " -p, --predicates TEXT A comma-separated list of predicates. This\r\n", + " may be a shorthand (i, p) or CURIE\r\n", + " --autolabel / --no-autolabel If set, results will automatically have\r\n", + " labels assigned [default: autolabel]\r\n", + " -O, --output-type TEXT Desired output type\r\n", + " -o, --output FILENAME Output file, e.g. obo file\r\n", + " --if-absent [absent-only|present-only]\r\n", + " determines behavior when the value is not\r\n", + " present or is empty.\r\n", + " -S, --set-value TEXT the value to set for all terms for the given\r\n", + " property.\r\n", + " --help Show this message and exit.\r\n" + ] + } + ], + "source": [ + "!runoak logical-definitions --help" + ] + }, + { + "cell_type": "markdown", + "id": "5b899d5c", + "metadata": {}, + "source": [ + "## Set up an alias\n", + "\n", + "For convenience we will set up an alias for use in this notebook" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "6050d1a5", + "metadata": {}, + "outputs": [], + "source": [ + "alias uberon runoak -i sqlite:obo:uberon" + ] + }, + { + "cell_type": "markdown", + "id": "c906c109", + "metadata": {}, + "source": [ + "## Fetching logical definitions for individual terms\n", + "\n", + "First we will pass in a simple list of terms to the command.\n", + "\n", + "Like most OAK commands, this command accepts lists of either IDs, labels, queries, or boolean combinations thereof" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "572be78c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "definedClassId: UBERON:0009565\r\n", + "genusIds:\r\n", + "- UBERON:0001705\r\n", + "restrictions:\r\n", + "- fillerId: UBERON:0002389\r\n", + " propertyId: BFO:0000050\r\n", + "\r\n", + "---\r\n", + "definedClassId: UBERON:0009567\r\n", + "genusIds:\r\n", + "- UBERON:0001705\r\n", + "restrictions:\r\n", + "- fillerId: UBERON:0001466\r\n", + " propertyId: BFO:0000050\r\n" + ] + } + ], + "source": [ + "uberon logical-definitions fingernail toenail" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "3b3c4afa", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "definedClassId\tdefinedClassId_label\tgenusIds\tgenusIds_label\trestrictions\trestrictionsPropertyIds\trestrictionsPropertyIds_label\trestrictionsFillerIds\trestrictionsFillerIds_label\r", + "\r\n", + "UBERON:0009565\tnail of manual digit\tUBERON:0001705\tnail\tBFO:0000050=UBERON:0002389\tBFO:0000050\tpart of\tUBERON:0002389\tmanual digit\r", + "\r\n", + "UBERON:0009567\tnail of pedal digit\tUBERON:0001705\tnail\tBFO:0000050=UBERON:0001466\tBFO:0000050\tpart of\tUBERON:0001466\tpedal digit\r", + "\r\n" + ] + } + ], + "source": [ + "uberon logical-definitions fingernail toenail -O csv" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "c9900ca0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[Term]\r\n", + "id: UBERON:0009565 ! nail of manual digit\r\n", + "intersection_of: UBERON:0001705 ! nail\r\n", + "intersection_of: BFO:0000050 UBERON:0002389 ! manual digit\r\n", + "\r\n", + "\r\n", + "[Term]\r\n", + "id: UBERON:0009567 ! nail of pedal digit\r\n", + "intersection_of: UBERON:0001705 ! nail\r\n", + "intersection_of: BFO:0000050 UBERON:0001466 ! pedal digit\r\n", + "\r\n", + "\r\n" + ] + } + ], + "source": [ + "uberon logical-definitions fingernail toenail -O obo" + ] + }, + { + "cell_type": "markdown", + "id": "d08c6000", + "metadata": {}, + "source": [ + "## Matrix views\n", + "\n", + "We can use the `--matrix-axes` option to summarize a large collection of logical definitions as a wide table.\n", + "\n", + "This takes two values, separated by a comma:\n", + "\n", + "- d: defined_class\n", + "- f: filler\n", + "- g: genus\n", + "- p: predicate\n", + "\n", + "### Define class x Predicate\n", + "\n", + "In the following example `d,p` will create a matrix whose rows are defined classes and whose columns are predicates" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "ae4748a0", + "metadata": {}, + "outputs": [], + "source": [ + "uberon logical-definitions -p p .desc//p=i \"bone element\" .and .desc//p=i,p UBERON:0002544 --matrix-axes d,p -O csv -o output/uberon-digit-defs-dp.tsv" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "dcb7bb7e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
defined_classdefined_class_labelgenusgenus_labelpart_ofpart_of_label
0UBERON:0001436phalanx of manusUBERON:0003221phalanxUBERON:0002389manual digit
1UBERON:0002234proximal phalanx of manusUBERON:0004302proximal phalanxUBERON:0002389manual digit
2UBERON:0004328proximal phalanx of manual digit 2UBERON:0004302proximal phalanxUBERON:0003622manual digit 2
3UBERON:0004329proximal phalanx of manual digit 3UBERON:0004302proximal phalanxUBERON:0003623manual digit 3
4UBERON:0004330proximal phalanx of manual digit 4UBERON:0004302proximal phalanxUBERON:0003624manual digit 4
.....................
59UBERON:0014503proximal phalanx of digit 3UBERON:0004302proximal phalanxUBERON:0006050digit 3
60UBERON:0014504proximal phalanx of digit 4UBERON:0004302proximal phalanxUBERON:0006051digit 4
61UBERON:0014505proximal phalanx of digit 5UBERON:0004302proximal phalanxUBERON:0006052digit 5
62UBERON:0004248pedal digit boneUBERON:0001474bone elementUBERON:0001466pedal digit
63UBERON:0004249manual digit boneUBERON:0001474bone elementUBERON:0002389manual digit
\n", + "

64 rows × 6 columns

\n", + "
" + ], + "text/plain": [ + " defined_class defined_class_label genus \\\n", + "0 UBERON:0001436 phalanx of manus UBERON:0003221 \n", + "1 UBERON:0002234 proximal phalanx of manus UBERON:0004302 \n", + "2 UBERON:0004328 proximal phalanx of manual digit 2 UBERON:0004302 \n", + "3 UBERON:0004329 proximal phalanx of manual digit 3 UBERON:0004302 \n", + "4 UBERON:0004330 proximal phalanx of manual digit 4 UBERON:0004302 \n", + ".. ... ... ... \n", + "59 UBERON:0014503 proximal phalanx of digit 3 UBERON:0004302 \n", + "60 UBERON:0014504 proximal phalanx of digit 4 UBERON:0004302 \n", + "61 UBERON:0014505 proximal phalanx of digit 5 UBERON:0004302 \n", + "62 UBERON:0004248 pedal digit bone UBERON:0001474 \n", + "63 UBERON:0004249 manual digit bone UBERON:0001474 \n", + "\n", + " genus_label part_of part_of_label \n", + "0 phalanx UBERON:0002389 manual digit \n", + "1 proximal phalanx UBERON:0002389 manual digit \n", + "2 proximal phalanx UBERON:0003622 manual digit 2 \n", + "3 proximal phalanx UBERON:0003623 manual digit 3 \n", + "4 proximal phalanx UBERON:0003624 manual digit 4 \n", + ".. ... ... ... \n", + "59 proximal phalanx UBERON:0006050 digit 3 \n", + "60 proximal phalanx UBERON:0006051 digit 4 \n", + "61 proximal phalanx UBERON:0006052 digit 5 \n", + "62 bone element UBERON:0001466 pedal digit \n", + "63 bone element UBERON:0002389 manual digit \n", + "\n", + "[64 rows x 6 columns]" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import pandas as pd\n", + "df = pd.read_csv(\"output/uberon-digit-defs-dp.tsv\", sep=\"\\t\")\n", + "df" + ] + }, + { + "cell_type": "markdown", + "id": "7448e67f", + "metadata": {}, + "source": [ + "### Filler x Genus\n", + "\n", + "We can flip this around, and have each row be a filler (`f`) and each column be a genus (`g`)." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "27ef3274", + "metadata": {}, + "outputs": [], + "source": [ + "uberon logical-definitions -p p .desc//p=i \"bone element\" .and .desc//p=i,p UBERON:0002544 --matrix-axes f,g -O csv -o output/uberon-digit-defs-fg.tsv" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "d9357cb9", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
fillerfiller_labelphalanxphalanx_labelproximal_phalanxproximal_phalanx_labelmiddle_phalanxmiddle_phalanx_labeldistal_phalanxdistal_phalanx_labelbone_elementbone_element_label
0UBERON:0002387pesUBERON:0001449phalanx of pesNaNNaNNaNNaNNaNNaNNaNNaN
1UBERON:0009563pastern region of limbUBERON:0009558pastern boneNaNNaNNaNNaNNaNNaNNaNNaN
2UBERON:0002389manual digitUBERON:0001436phalanx of manusUBERON:0002234proximal phalanx of manusUBERON:0003864middle phalanx of manusUBERON:0003865distal phalanx of manusUBERON:0004249manual digit bone
3UBERON:0003622manual digit 2UBERON:0003636manual digit 2 phalanxUBERON:0004328proximal phalanx of manual digit 2UBERON:0004320middle phalanx of manual digit 2UBERON:0004311distal phalanx of manual digit 2NaNNaN
4UBERON:0003623manual digit 3UBERON:0003637manual digit 3 phalanxUBERON:0004329proximal phalanx of manual digit 3UBERON:0004321middle phalanx of manual digit 3UBERON:0004312distal phalanx of manual digit 3NaNNaN
5UBERON:0003624manual digit 4UBERON:0003638manual digit 4 phalanxUBERON:0004330proximal phalanx of manual digit 4UBERON:0004322middle phalanx of manual digit 4UBERON:0004313distal phalanx of manual digit 4NaNNaN
6UBERON:0001463manual digit 1UBERON:0003620manual digit 1 phalanxUBERON:0004338proximal phalanx of manual digit 1NaNNaNUBERON:0004337distal phalanx of manual digit 1NaNNaN
7UBERON:0003625manual digit 5UBERON:0003639manual digit 5 phalanxUBERON:0004331proximal phalanx of manual digit 5UBERON:0004323middle phalanx of manual digit 5UBERON:0004314distal phalanx of manual digit 5NaNNaN
8UBERON:0001466pedal digitNaNNaNUBERON:0003868proximal phalanx of pesUBERON:0003866middle phalanx of pesUBERON:0003867distal phalanx of pesUBERON:0004248pedal digit bone
9UBERON:0003632pedal digit 2UBERON:0003641pedal digit 2 phalanxUBERON:0004333proximal phalanx of pedal digit 2UBERON:0004324middle phalanx of pedal digit 2UBERON:0004316distal phalanx of pedal digit 2NaNNaN
10UBERON:0003633pedal digit 3UBERON:0003642pedal digit 3 phalanxUBERON:0004334proximal phalanx of pedal digit 3UBERON:0004325middle phalanx of pedal digit 3UBERON:0004317distal phalanx of pedal digit 3NaNNaN
11UBERON:0003634pedal digit 4UBERON:0003862pedal digit 4 phalanxUBERON:0004335proximal phalanx of pedal digit 4UBERON:0004326middle phalanx of pedal digit 4UBERON:0004318distal phalanx of pedal digit 4NaNNaN
12UBERON:0003631pedal digit 1UBERON:0003640pedal digit 1 phalanxUBERON:0004332proximal phalanx of pedal digit 1NaNNaNUBERON:0004315distal phalanx of pedal digit 1NaNNaN
13UBERON:0003635pedal digit 5UBERON:0003863pedal digit 5 phalanxUBERON:0004336proximal phalanx of pedal digit 5UBERON:0004327middle phalanx of pedal digit 5UBERON:0004319distal phalanx of pedal digit 5NaNNaN
14UBERON:0012137pedal digit 7UBERON:4100009pedal digit 7 phalanxNaNNaNNaNNaNNaNNaNNaNNaN
15UBERON:0006049digit 2NaNNaNUBERON:0014502proximal phalanx of digit 2UBERON:0014488middle phalanx of digit 2UBERON:0014484distal phalanx of digit 2NaNNaN
16UBERON:0006050digit 3NaNNaNUBERON:0014503proximal phalanx of digit 3UBERON:0014489middle phalanx of digit 3UBERON:0014485distal phalanx of digit 3NaNNaN
17UBERON:0006051digit 4NaNNaNUBERON:0014504proximal phalanx of digit 4UBERON:0014490middle phalanx of digit 4UBERON:0014486distal phalanx of digit 4NaNNaN
18UBERON:0006048digit 1NaNNaNUBERON:0014501proximal phalanx of digit 1NaNNaNUBERON:0014483distal phalanx of digit 1NaNNaN
19UBERON:0006052digit 5NaNNaNUBERON:0014505proximal phalanx of digit 5UBERON:0014491middle phalanx of digit 5UBERON:0014487distal phalanx of digit 5NaNNaN
\n", + "
" + ], + "text/plain": [ + " filler filler_label phalanx \\\n", + "0 UBERON:0002387 pes UBERON:0001449 \n", + "1 UBERON:0009563 pastern region of limb UBERON:0009558 \n", + "2 UBERON:0002389 manual digit UBERON:0001436 \n", + "3 UBERON:0003622 manual digit 2 UBERON:0003636 \n", + "4 UBERON:0003623 manual digit 3 UBERON:0003637 \n", + "5 UBERON:0003624 manual digit 4 UBERON:0003638 \n", + "6 UBERON:0001463 manual digit 1 UBERON:0003620 \n", + "7 UBERON:0003625 manual digit 5 UBERON:0003639 \n", + "8 UBERON:0001466 pedal digit NaN \n", + "9 UBERON:0003632 pedal digit 2 UBERON:0003641 \n", + "10 UBERON:0003633 pedal digit 3 UBERON:0003642 \n", + "11 UBERON:0003634 pedal digit 4 UBERON:0003862 \n", + "12 UBERON:0003631 pedal digit 1 UBERON:0003640 \n", + "13 UBERON:0003635 pedal digit 5 UBERON:0003863 \n", + "14 UBERON:0012137 pedal digit 7 UBERON:4100009 \n", + "15 UBERON:0006049 digit 2 NaN \n", + "16 UBERON:0006050 digit 3 NaN \n", + "17 UBERON:0006051 digit 4 NaN \n", + "18 UBERON:0006048 digit 1 NaN \n", + "19 UBERON:0006052 digit 5 NaN \n", + "\n", + " phalanx_label proximal_phalanx \\\n", + "0 phalanx of pes NaN \n", + "1 pastern bone NaN \n", + "2 phalanx of manus UBERON:0002234 \n", + "3 manual digit 2 phalanx UBERON:0004328 \n", + "4 manual digit 3 phalanx UBERON:0004329 \n", + "5 manual digit 4 phalanx UBERON:0004330 \n", + "6 manual digit 1 phalanx UBERON:0004338 \n", + "7 manual digit 5 phalanx UBERON:0004331 \n", + "8 NaN UBERON:0003868 \n", + "9 pedal digit 2 phalanx UBERON:0004333 \n", + "10 pedal digit 3 phalanx UBERON:0004334 \n", + "11 pedal digit 4 phalanx UBERON:0004335 \n", + "12 pedal digit 1 phalanx UBERON:0004332 \n", + "13 pedal digit 5 phalanx UBERON:0004336 \n", + "14 pedal digit 7 phalanx NaN \n", + "15 NaN UBERON:0014502 \n", + "16 NaN UBERON:0014503 \n", + "17 NaN UBERON:0014504 \n", + "18 NaN UBERON:0014501 \n", + "19 NaN UBERON:0014505 \n", + "\n", + " proximal_phalanx_label middle_phalanx \\\n", + "0 NaN NaN \n", + "1 NaN NaN \n", + "2 proximal phalanx of manus UBERON:0003864 \n", + "3 proximal phalanx of manual digit 2 UBERON:0004320 \n", + "4 proximal phalanx of manual digit 3 UBERON:0004321 \n", + "5 proximal phalanx of manual digit 4 UBERON:0004322 \n", + "6 proximal phalanx of manual digit 1 NaN \n", + "7 proximal phalanx of manual digit 5 UBERON:0004323 \n", + "8 proximal phalanx of pes UBERON:0003866 \n", + "9 proximal phalanx of pedal digit 2 UBERON:0004324 \n", + "10 proximal phalanx of pedal digit 3 UBERON:0004325 \n", + "11 proximal phalanx of pedal digit 4 UBERON:0004326 \n", + "12 proximal phalanx of pedal digit 1 NaN \n", + "13 proximal phalanx of pedal digit 5 UBERON:0004327 \n", + "14 NaN NaN \n", + "15 proximal phalanx of digit 2 UBERON:0014488 \n", + "16 proximal phalanx of digit 3 UBERON:0014489 \n", + "17 proximal phalanx of digit 4 UBERON:0014490 \n", + "18 proximal phalanx of digit 1 NaN \n", + "19 proximal phalanx of digit 5 UBERON:0014491 \n", + "\n", + " middle_phalanx_label distal_phalanx \\\n", + "0 NaN NaN \n", + "1 NaN NaN \n", + "2 middle phalanx of manus UBERON:0003865 \n", + "3 middle phalanx of manual digit 2 UBERON:0004311 \n", + "4 middle phalanx of manual digit 3 UBERON:0004312 \n", + "5 middle phalanx of manual digit 4 UBERON:0004313 \n", + "6 NaN UBERON:0004337 \n", + "7 middle phalanx of manual digit 5 UBERON:0004314 \n", + "8 middle phalanx of pes UBERON:0003867 \n", + "9 middle phalanx of pedal digit 2 UBERON:0004316 \n", + "10 middle phalanx of pedal digit 3 UBERON:0004317 \n", + "11 middle phalanx of pedal digit 4 UBERON:0004318 \n", + "12 NaN UBERON:0004315 \n", + "13 middle phalanx of pedal digit 5 UBERON:0004319 \n", + "14 NaN NaN \n", + "15 middle phalanx of digit 2 UBERON:0014484 \n", + "16 middle phalanx of digit 3 UBERON:0014485 \n", + "17 middle phalanx of digit 4 UBERON:0014486 \n", + "18 NaN UBERON:0014483 \n", + "19 middle phalanx of digit 5 UBERON:0014487 \n", + "\n", + " distal_phalanx_label bone_element bone_element_label \n", + "0 NaN NaN NaN \n", + "1 NaN NaN NaN \n", + "2 distal phalanx of manus UBERON:0004249 manual digit bone \n", + "3 distal phalanx of manual digit 2 NaN NaN \n", + "4 distal phalanx of manual digit 3 NaN NaN \n", + "5 distal phalanx of manual digit 4 NaN NaN \n", + "6 distal phalanx of manual digit 1 NaN NaN \n", + "7 distal phalanx of manual digit 5 NaN NaN \n", + "8 distal phalanx of pes UBERON:0004248 pedal digit bone \n", + "9 distal phalanx of pedal digit 2 NaN NaN \n", + "10 distal phalanx of pedal digit 3 NaN NaN \n", + "11 distal phalanx of pedal digit 4 NaN NaN \n", + "12 distal phalanx of pedal digit 1 NaN NaN \n", + "13 distal phalanx of pedal digit 5 NaN NaN \n", + "14 NaN NaN NaN \n", + "15 distal phalanx of digit 2 NaN NaN \n", + "16 distal phalanx of digit 3 NaN NaN \n", + "17 distal phalanx of digit 4 NaN NaN \n", + "18 distal phalanx of digit 1 NaN NaN \n", + "19 distal phalanx of digit 5 NaN NaN " + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df = pd.read_csv(\"output/uberon-digit-defs-fg.tsv\", sep=\"\\t\")\n", + "df" + ] + }, + { + "cell_type": "markdown", + "id": "9b6213e2", + "metadata": {}, + "source": [ + "Note that this view immediately shows the *density* of the lattice. We can identify what might potentially be gaps;\n", + "\n", + "for example, the cells for \"middle phalanx\" and digit 1 of the hand (manual) and foot (pedal) are empty. We might think\n", + "this means we left out a potential term. \n", + "\n", + "However, this omission is actually intentional due to the lack of a middle/intermediate phalanx on the thumb / big toe:\n", + " \n", + "![img](https://upload.wikimedia.org/wikipedia/commons/thumb/a/ab/Scheme_human_hand_bones-en.svg/800px-Scheme_human_hand_bones-en.svg.png)\n", + "\n", + "(it may be the case this phalanx is present in other species, in which case a term may be added with negative taxon constraints)" + ] + }, + { + "cell_type": "markdown", + "id": "78894237", + "metadata": {}, + "source": [ + "## Analyzing and gap filling\n", + "\n", + "OAK has experimental features for analyzing and gap-filling logical definitions; these are not yet exposed on the command line" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "54e9d757", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/src/oaklib/cli.py b/src/oaklib/cli.py index 4bd7d14f6..2fd39b94e 100644 --- a/src/oaklib/cli.py +++ b/src/oaklib/cli.py @@ -43,6 +43,7 @@ from linkml_runtime.dumpers import json_dumper, yaml_dumper from linkml_runtime.utils.introspection import package_schemaview from prefixmaps.io.parser import load_multi_context +from pydantic import BaseModel from sssom.parsers import parse_sssom_table, to_mapping_set_document import oaklib.datamodels.taxon_constraints as tcdm @@ -56,6 +57,7 @@ Edge, Graph, GraphDocument, + LogicalDefinitionAxiom, Meta, PrefixDeclaration, ) @@ -141,12 +143,17 @@ from oaklib.utilities import table_filler from oaklib.utilities.apikey_manager import set_apikey_value from oaklib.utilities.associations.association_differ import AssociationDiffer +from oaklib.utilities.axioms import ( + logical_definition_analyzer, + logical_definition_summarizer, +) from oaklib.utilities.iterator_utils import chunk from oaklib.utilities.kgcl_utilities import ( generate_change_id, parse_kgcl_files, write_kgcl, ) +from oaklib.utilities.lexical import patternizer from oaklib.utilities.lexical.lexical_indexer import ( DEFAULT_QUALIFIER, add_labels_from_uris, @@ -571,9 +578,14 @@ def curies_from_file(file: IO) -> Iterator[CURIE]: :param file: :return: """ + line_no = 0 for line in file.readlines(): + line_no += 1 m = re.match(r"^(\S+)", line) - yield m.group(1) + curie = m.group(1) + if curie == "id" and line_no == 1: + continue + yield curie def query_terms_iterator(query_terms: NESTED_LIST, impl: BasicOntologyInterface) -> Iterator[CURIE]: @@ -1126,6 +1138,10 @@ def obsoletes( runoak -i obolibrary:go.obo obsoletes --show-migration-relationships GO:0000187 GO:0000188 + More examples: + + https://github.com/INCATools/ontology-access-kit/blob/main/notebooks/Commands/TaxonConstraints.ipynb + Python API: https://incatools.github.io/ontology-access-kit/interfaces/basic @@ -1846,9 +1862,9 @@ def tree( output: TextIO, ): """ - Display an ancestor graph as an ascii/markdown tree + Display an ancestor graph as an ascii/markdown tree. - For general instructions, see the viz command, which this is analogous too + For general instructions, see the viz command, which this is analogous too. Example: @@ -1936,7 +1952,7 @@ def tree( if isinstance(impl, OboGraphInterface): graph = impl.relationships_to_graph(rels) else: - raise AssertionError(f"{impl} needs to of type OboGraphInterface") + raise AssertionError(f"{impl} needs to be of type OboGraphInterface") else: raise NotImplementedError(f"{impl} needs to implement Subsetter for --gap-fill") else: @@ -2148,7 +2164,11 @@ def paths( go paths -p i,p 'nuclear membrane' --target cytoplasm --narrow | go viz --fill-gaps - This visualizes the path by first exporting the path as a flat list, then passing the - results to viz, using the fill-gaps option + results to viz, using the fill-gaps option. + + More examples: + + https://github.com/INCATools/ontology-access-kit/blob/main/notebooks/Commands/Paths.ipynb """ impl = settings.impl writer = _get_writer(output_type, impl, StreamingCsvWriter) @@ -3158,6 +3178,10 @@ def relationships( runoak -i uberon.db relationships -p RO:0002178 .desc//p=i "artery" .and .desc//p=i,p "limb" + More examples: + + https://github.com/INCATools/ontology-access-kit/blob/main/notebooks/Commands/Relationships.ipynb + Python API: https://incatools.github.io/ontology-access-kit/interfaces/basic @@ -3232,6 +3256,10 @@ def relationships( @click.option( "--unmelt/--no-unmelt", default=False, show_default=True, help="Flatten to a wide table" ) +@click.option( + "--matrix-axes", + help="If specified, transform results to matrix using these row and column axes. Examples: d,p; f,g", +) @predicates_option @autolabel_option @output_type_option @@ -3246,6 +3274,7 @@ def logical_definitions( output: str, if_absent: bool, unmelt: bool, + matrix_axes: str, set_value: str, ): """ @@ -3285,14 +3314,21 @@ def logical_definitions( pato logical-definitions .all --output-type csv - You can optionally choose to "unmelt" or flatten this, such that: + You can optionally choose to "--matrix-axes" to transform the output to a matrix form. + This is a comma-separated pair of axes, where each element is a logical definition element + type: "f" for filler, "p" for predicate, "g" for genus, "d" for defined class. + + Example: - Each property/predicate is a column - For repeated properties, columns of the form prop_1, prop_2, ... are generated Example: - pato logical-definitions .all --unmelt --output-type csv + pato logical-definitions .all --matrix-axes d,p --output-type csv + + This will generate a row for each defined class with a logical definition, with columns + for each predicate ("genus" is treated as a predicate here). Limitations: @@ -3301,6 +3337,10 @@ def logical_definitions( Consider using the "axioms" command for inspection of complex nested OWL axioms. + More examples: + + https://github.com/INCATools/ontology-access-kit/blob/main/notebooks/Commands/LogicalDefinitions.ipynb + Python API: https://incatools.github.io/ontology-access-kit/interfaces/obograph @@ -3314,6 +3354,13 @@ def logical_definitions( writer.output = output writer.autolabel = autolabel actual_predicates = _process_predicates_arg(predicates) + + def _exclude_ldef(ldef: LogicalDefinitionAxiom) -> bool: + if actual_predicates: + if not any(r for r in ldef.restrictions if r.propertyId in actual_predicates): + return True + return False + if set_value: raise NotImplementedError label_fields = [ @@ -3323,39 +3370,54 @@ def logical_definitions( "restrictionsPropertyIds", "restrictionsFillerIds", ] - if isinstance(impl, OboGraphInterface): - # curies = list(query_terms_iterator(terms, impl)) - has_relationships = defaultdict(bool) - curies = [] - if unmelt: - ldef_flattener = LogicalDefinitionFlattener( - labeler=lambda x: impl.label(x), curie_converter=impl.converter - ) - writer.heterogeneous_keys = True + if not isinstance(impl, OboGraphInterface): + raise NotImplementedError(f"Cannot execute this using {type(impl)}") + # curies = list(query_terms_iterator(terms, impl)) + has_relationships = defaultdict(bool) + curies = [] + if matrix_axes: + config = logical_definition_summarizer.parse_axes_to_config(matrix_axes) + ldefs = [] for curie_it in chunk(query_terms_iterator(terms, impl)): - curie_chunk = list(curie_it) - curies += curie_chunk - for ldef in impl.logical_definitions(curie_chunk): - if actual_predicates: - if not any(r for r in ldef.restrictions if r.propertyId in actual_predicates): - continue - if ldef.definedClassId: - has_relationships[ldef.definedClassId] = True - if if_absent and if_absent == IfAbsent.absent_only.value: - continue - if unmelt: - flat_obj = ldef_flattener.convert(ldef) - writer.emit(flat_obj, label_fields=list(flat_obj.keys())) - else: - writer.emit(ldef, label_fields=label_fields) - if if_absent and if_absent == IfAbsent.absent_only.value: - for curie in curies: - if not has_relationships.get(curie, False): - writer.emit({"noLogicalDefinition": curie}) + ldefs.extend(list(impl.logical_definitions(curie_it))) + ldefs = [ldef for ldef in ldefs if not _exclude_ldef(ldef)] + objs = logical_definition_summarizer.logical_definitions_to_matrix(impl, ldefs, config) + writer.heterogeneous_keys = True + label_fields = None + for obj in objs: + if label_fields is None: + label_fields = list(obj.keys()) + writer.emit(obj, label_fields=label_fields) writer.finish() writer.file.close() - else: - raise NotImplementedError(f"Cannot execute this using {impl} of type {type(impl)}") + return + if unmelt: + logging.warning("Deprecated: use --matrix-type d,p instead") + ldef_flattener = LogicalDefinitionFlattener( + labeler=lambda x: impl.label(x), curie_converter=impl.converter + ) + writer.heterogeneous_keys = True + for curie_it in chunk(query_terms_iterator(terms, impl)): + curie_chunk = list(curie_it) + curies += curie_chunk + for ldef in impl.logical_definitions(curie_chunk): + if _exclude_ldef(ldef): + continue + if ldef.definedClassId: + has_relationships[ldef.definedClassId] = True + if if_absent and if_absent == IfAbsent.absent_only.value: + continue + if unmelt: + flat_obj = ldef_flattener.convert(ldef) + writer.emit(flat_obj, label_fields=list(flat_obj.keys())) + else: + writer.emit(ldef, label_fields=label_fields) + if if_absent and if_absent == IfAbsent.absent_only.value: + for curie in curies: + if not has_relationships.get(curie, False): + writer.emit({"noLogicalDefinition": curie}) + writer.finish() + writer.file.close() @main.command() @@ -3920,6 +3982,10 @@ def taxon_constraints( runoak -i sqlite:obo:uberon taxon-constraints UBERON:0003884 UBERON:0003941 -p i,p + More examples: + + https://github.com/INCATools/ontology-access-kit/blob/main/notebooks/Commands/TaxonConstraints.ipynb + This command is a wrapper onto taxon_constraints_utils: - https://incatools.github.io/ontology-access-kit/src/oaklib.utilities.taxon.taxon_constraints_utils @@ -3989,6 +4055,10 @@ def apply_taxon_constraints( runoak -i db/go.db eval-taxon-constraints -p i,p -E tests/input/go-evo-gains-losses.csv + More examples: + + https://github.com/INCATools/ontology-access-kit/blob/main/notebooks/Commands/Apply.ipynb + """ actual_predicates = _process_predicates_arg(predicates) impl = settings.impl @@ -4101,6 +4171,9 @@ def associations( runoak --i src/oaklib/conf/go-dictybase-input-spec.yaml associations -p i,p GO:0008104 + More examples: + + https://github.com/INCATools/ontology-access-kit/blob/main/notebooks/Commands/Associations.ipynb """ impl = settings.impl writer = _get_writer(output_type, impl, StreamingCsvWriter) @@ -4564,7 +4637,9 @@ def diff_associations( ) @output_option @output_type_option +@click.argument("terms", nargs=-1) def validate( + terms: List[str], output: str, cutoff: int, skip_structural_validation: bool, @@ -4603,32 +4678,41 @@ def validate( writer.output = output if rule: skip_ontology_rules = False - if isinstance(impl, ValidatorInterface): - if not skip_structural_validation: - counts = defaultdict(int) - for result in impl.validate(): - key = (result.type, result.predicate) - n = counts[key] - n += 1 - counts[key] = n - if n % 1000 == 0: - logging.info(f"Reached {n} results with {key}") - if n == cutoff: - print(f"**TRUNCATING RESULTS FOR {key} at {cutoff}") - elif n < cutoff: - writer.emit(result) - # print(yaml_dumper.dumps(result)) - for k, v in counts.items(): - print(f"{k}:: {v}") - if not skip_ontology_rules: - rr = RuleRunner() - if rule: - rr.set_rules(rule) - for result in rr.run(impl): - writer.emit(result) - writer.finish() - else: + if not isinstance(impl, ValidatorInterface): raise NotImplementedError(f"Cannot execute this using {impl} of type {type(impl)}") + if terms: + # note: currently the validate interface doesn't supported filtered lists, + # so we post-hoc filter. This is potentially inefficient. + entities = list(query_terms_iterator(terms, impl)) + else: + entities = None + if not skip_structural_validation: + counts = defaultdict(int) + for result in impl.validate(): + if entities and result.subject not in entities: + continue + key = (result.type, result.predicate) + n = counts[key] + n += 1 + counts[key] = n + if n % 1000 == 0: + logging.info(f"Reached {n} results with {key}") + if n == cutoff: + print(f"**TRUNCATING RESULTS FOR {key} at {cutoff}") + elif n < cutoff: + writer.emit(result) + # print(yaml_dumper.dumps(result)) + for k, v in counts.items(): + print(f"{k}:: {v}") + if not skip_ontology_rules: + rr = RuleRunner() + if rule: + rr.set_rules(rule) + for result in rr.run(impl): + if entities and result.subject not in entities: + continue + writer.emit(result) + writer.finish() @main.command() @@ -5286,9 +5370,15 @@ def apply( show_default=True, help="if true, expand complex changes to atomic changes", ) +@click.option( + "--ignore-invalid-changes/--no-ignore-invalid-changes", + default=False, + show_default=True, + help="if true, ignore invalid changes, e.g. obsoletions of dependent entities", +) @output_type_option @click.argument("terms", nargs=-1) -def apply_obsolete(output, output_type, expand: bool, terms): +def apply_obsolete(output, output_type, expand: bool, terms, **kwargs): """ Sets an ontology element to be obsolete @@ -5308,25 +5398,25 @@ def apply_obsolete(output, output_type, expand: bool, terms): This command is partially redundant with the more general "apply" command """ impl = settings.impl - if isinstance(impl, PatcherInterface): - impl.autosave = settings.autosave - for term in query_terms_iterator(terms, impl): - change = kgcl.NodeObsoletion(id=generate_change_id(), about_node=term) - if expand: - changes = impl.expand_change(change) - else: - changes = [change] - for change in changes: - impl.apply_patch(change) - if not settings.autosave and not output: - logging.warning("--autosave not passed, changes are NOT saved") - if output: - if output == "-": - output = sys.stdout - impl.dump(output, output_type) - # impl.save() - else: + if not isinstance(impl, PatcherInterface): raise NotImplementedError + impl.autosave = settings.autosave + for k, v in kwargs.items(): + setattr(impl, k, v) + for term in query_terms_iterator(terms, impl): + change = kgcl.NodeObsoletion(id=generate_change_id(), about_node=term) + if expand: + changes = impl.expand_change(change) + else: + changes = [change] + for change in changes: + impl.apply_patch(change) + if not settings.autosave and not output: + logging.warning("--autosave not passed, changes are NOT saved") + if output: + if output == "-": + output = sys.stdout + impl.dump(output, output_type) @main.command() @@ -5796,5 +5886,120 @@ def generate_synonyms(terms, rules_file, apply_patch, patch, patch_format, outpu _apply_changes(impl, change_list) +@main.command() +@click.argument("terms", nargs=-1) +@click.option( + "--patterns-file", + "-P", + multiple=True, + help="path to patterns file", +) +@click.option( + "--show-extract/--no-show-extract", + default=False, + show_default=True, + help="Show the original extracted object.", +) +@click.option( + "--parse/--no-parse", + default=True, + show_default=True, + help="Parse the input terms according to the patterns.", +) +@click.option( + "--fill/--no-fill", + default=False, + show_default=True, + help="If true, fill in descendant logical definitions.", +) +@click.option( + "--analyze/--no-analyze", + default=False, + show_default=True, + help="Analyze consistency of logical definitions (in progress).", +) +@click.option( + "--unmelt/--no-unmelt", + default=False, + show_default=True, + help="Use a wide table for display.", +) +@autolabel_option +@output_option +@output_type_option +def generate_logical_definitions( + terms, patterns_file, show_extract, unmelt, autolabel, parse, fill, analyze, output, output_type +): + """ + Generate logical definitions based on patterns file. + """ + impl = settings.impl + writer = _get_writer(output_type, impl, StreamingYamlWriter, kgcl) + writer.output = output + writer.autolabel = autolabel + if not isinstance(impl, OboGraphInterface): + raise NotImplementedError + curies = list(query_terms_iterator(terms, impl)) + pattern_collection = None + for pf in patterns_file: + if pattern_collection is None: + pattern_collection = patternizer.load_pattern_collection(pf) + else: + pattern_collection.patterns.extend(patternizer.load_pattern_collection(pf).patterns) + if show_extract: + results = patternizer.lexical_pattern_instances(impl, pattern_collection.patterns, curies) + # label_fields = [] + if unmelt: + results = patternizer.as_matrix(results, pattern_collection) + # label_fields = [p.name for p in pattern_collection.patterns] + for result in results: + if isinstance(result, BaseModel): + result = result.dict() + writer.emit(result) + else: + label_fields = [ + "definedClassId", + "genusIds", + "restrictionFillerIds", + "restrictionsPropertyIds", + "restrictionsFillerIds", + ] + if parse: + if not pattern_collection: + raise ValueError("Must specify -P if --parse is set") + results = patternizer.lexical_pattern_instances( + impl, pattern_collection.patterns, curies + ) + ldefs = list(patternizer.as_logical_definitions(results)) + else: + ldefs = list(impl.logical_definitions(curies)) + if fill: + for ldef in ldefs: + for ( + filled_ldef + ) in logical_definition_analyzer.generate_descendant_logical_definitions( + impl, ldef + ): + writer.emit(filled_ldef, label_fields=label_fields) + if analyze: + logging.warning("Analyzing logical definitions is incomplete") + reports = logical_definition_analyzer.analyze_logical_definitions(impl, ldefs) + for report in reports: + print(report) + if unmelt: + ldef_flattener = LogicalDefinitionFlattener( + labeler=lambda x: impl.label(x), curie_converter=impl.converter + ) + writer.heterogeneous_keys = True + for ldef in ldefs: + flat_obj = ldef_flattener.convert(ldef) + writer.emit(flat_obj, label_fields=list(flat_obj.keys())) + else: + for ldef in ldefs: + writer.emit(ldef, label_fields=label_fields) + writer.finish() + writer.file.close() + + if __name__ == "__main__": main() diff --git a/src/oaklib/conf/obograph-style.json b/src/oaklib/conf/obograph-style.json index 6920198a0..1efeac4e8 100644 --- a/src/oaklib/conf/obograph-style.json +++ b/src/oaklib/conf/obograph-style.json @@ -152,6 +152,9 @@ "FBdv": { "fillcolor": "mediumturquoise" }, + "PR": { + "fillcolor": "mediumturquoise" + }, "RO": { "fillcolor": "pink" }, diff --git a/src/oaklib/datamodels/search.py b/src/oaklib/datamodels/search.py index 868d74738..071b8c9b7 100644 --- a/src/oaklib/datamodels/search.py +++ b/src/oaklib/datamodels/search.py @@ -25,7 +25,7 @@ def create_search_configuration(term: str) -> "SearchConfiguration": term is either a plaintext search term, or a search term prefixed by - 1. a property packages, one of t, ., l (for term, anything, label) - - 2. a match type indicator, one of "~","/","=","^" + - 2. a match type indicator, one of "~","/","=","^","@" For more documentation, see `Search docs `_ diff --git a/src/oaklib/datamodels/vocabulary.py b/src/oaklib/datamodels/vocabulary.py index e21148a0c..25f191c97 100644 --- a/src/oaklib/datamodels/vocabulary.py +++ b/src/oaklib/datamodels/vocabulary.py @@ -118,6 +118,9 @@ NEVER_IN_TAXON = "RO:0002161" IN_TAXON = "RO:0002162" PRESENT_IN_TAXON = "RO:0002175" +NEGATIVELY_REGULATES = "RO:0002212" +POSITIVELY_REGULATES = "RO:0002213" +REGULATES = "RO:0002211" OBO_PURL = "http://purl.obolibrary.org/obo/" diff --git a/src/oaklib/implementations/aggregator/aggregator_implementation.py b/src/oaklib/implementations/aggregator/aggregator_implementation.py index 6b79bf078..758c7f8e0 100644 --- a/src/oaklib/implementations/aggregator/aggregator_implementation.py +++ b/src/oaklib/implementations/aggregator/aggregator_implementation.py @@ -78,7 +78,7 @@ class AggregatorImplementation( implementations: List[BasicOntologyInterface] = None - def _delegate_iterator(self, func: Callable) -> Iterable: + def _delegate_iterator(self, func: Callable) -> Iterator: for i in self.implementations: for v in func(i): yield v @@ -113,16 +113,17 @@ def simple_mappings_by_curie(self, curie: CURIE) -> Iterable[Tuple[PRED_CURIE, C def get_sssom_mappings_by_curie(self, curie: CURIE) -> Iterable[Mapping]: return self._delegate_iterator(lambda i: i.get_sssom_mappings_by_curie(curie)) - def label(self, curie: CURIE) -> str: - return self._delegate_first(lambda i: i.label(curie)) + def label(self, curie: CURIE, **kwargs) -> str: + return self._delegate_first(lambda i: i.label(curie, **kwargs)) - def definition(self, curie: CURIE) -> str: - return self._delegate_first(lambda i: i.definition(curie)) + def curies_by_label(self, label: str) -> List[CURIE]: + return list(self._delegate_iterator(lambda i: i.curies_by_label(label))) - def definitions( - self, curies: Iterable[CURIE], include_metadata=False, include_missing=False - ) -> Iterator[DEFINITION]: - return self._delegate_iterator(lambda i: i.definitions(curies)) + def definition(self, curie: CURIE, **kwargs) -> str: + return self._delegate_first(lambda i: i.definition(curie, **kwargs)) + + def definitions(self, curies: Iterable[CURIE], **kwargs) -> Iterator[DEFINITION]: + return self._delegate_iterator(lambda i: i.definitions(curies, **kwargs)) def entity_alias_map(self, curie: CURIE) -> ALIAS_MAP: return self._delegate_simple_tuple_map(lambda i: i.entity_alias_map(curie)) diff --git a/src/oaklib/implementations/obograph/obograph_implementation.py b/src/oaklib/implementations/obograph/obograph_implementation.py index 3a5224f6b..35ddc2cad 100644 --- a/src/oaklib/implementations/obograph/obograph_implementation.py +++ b/src/oaklib/implementations/obograph/obograph_implementation.py @@ -48,6 +48,9 @@ from oaklib.interfaces.validator_interface import ValidatorInterface from oaklib.resource import OntologyResource from oaklib.types import CURIE, PRED_CURIE, SUBSET_CURIE, URI +from oaklib.utilities.axioms.logical_definition_utilities import ( + logical_definition_matches, +) from oaklib.utilities.basic_utils import pairs_as_dict RDFLIB_FORMAT_MAP = { @@ -433,12 +436,21 @@ def node( def as_obograph(self) -> Graph: return self._entire_graph() - def logical_definitions(self, subjects: Iterable[CURIE]) -> Iterable[LogicalDefinitionAxiom]: - subjects = list(subjects) + def logical_definitions( + self, + subjects: Iterable[CURIE], + predicates: Iterable[PRED_CURIE] = None, + objects: Iterable[CURIE] = None, + **kwargs, + ) -> Iterable[LogicalDefinitionAxiom]: + if subjects: + subjects = list(subjects) for g in self.obograph_document.graphs: - for lda in g.logicalDefinitionAxioms: - if lda.definedClassId in subjects: - yield lda + for ldef in g.logicalDefinitionAxioms: + if logical_definition_matches( + ldef, subjects=subjects, predicates=predicates, objects=objects + ): + yield ldef # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ # Implements: SearchInterface diff --git a/src/oaklib/implementations/pronto/pronto_implementation.py b/src/oaklib/implementations/pronto/pronto_implementation.py index b8825ed2a..dc68630ab 100644 --- a/src/oaklib/implementations/pronto/pronto_implementation.py +++ b/src/oaklib/implementations/pronto/pronto_implementation.py @@ -49,6 +49,7 @@ from oaklib.interfaces import TextAnnotatorInterface from oaklib.interfaces.basic_ontology_interface import ( ALIAS_MAP, + DEFINITION, LANGUAGE_TAG, METADATA_MAP, PRED_CURIE, @@ -74,6 +75,9 @@ from oaklib.interfaces.validator_interface import ValidatorInterface from oaklib.resource import OntologyResource from oaklib.types import CURIE, SUBSET_CURIE +from oaklib.utilities.axioms.logical_definition_utilities import ( + logical_definition_matches, +) from oaklib.utilities.kgcl_utilities import tidy_change_object from oaklib.utilities.mapping.sssom_utils import inject_mapping_sources @@ -490,6 +494,27 @@ def definition(self, curie: CURIE, lang: Optional[LANGUAGE_TAG] = None) -> Optio e = self._entity(curie) return e.definition if e else None + def definitions( + self, + curies: Iterable[CURIE], + include_metadata=False, + include_missing=False, + lang: Optional[LANGUAGE_TAG] = None, + ) -> Iterator[DEFINITION]: + for curie in curies: + e = self._entity(curie) + if not e: + continue + defn = e.definition + if not defn and not include_missing: + continue + metadata = {} + if include_metadata: + metadata[HAS_DBXREF] = [] + for x in defn.xrefs: + metadata[HAS_DBXREF].append(x.id) + yield curie, defn, metadata + def comments(self, curies: Iterable[CURIE]) -> Iterable[Tuple[CURIE, str]]: for curie in curies: e = self._entity(curie) @@ -702,10 +727,11 @@ def as_obograph(self, expand_curies=False) -> Graph: Edge(sub=r[0], pred="is_a" if r[1] == IS_A else r[1], obj=r[2]) for r in self.relationships() ] + ldefs = list(self.logical_definitions(entities)) graph_id = om.ontology if not graph_id: graph_id = self.resource.slug - return Graph(id=graph_id, nodes=nodes, edges=edges) + return Graph(id=graph_id, nodes=nodes, edges=edges, logicalDefinitionAxioms=ldefs) def synonym_property_values( self, subject: Union[CURIE, Iterable[CURIE]] @@ -725,8 +751,14 @@ def synonym_property_values( yield curie, spv def logical_definitions( - self, subjects: Optional[Iterable[CURIE]] + self, + subjects: Optional[Iterable[CURIE]], + predicates: Iterable[PRED_CURIE] = None, + objects: Iterable[CURIE] = None, + **kwargs, ) -> Iterable[obograph.LogicalDefinitionAxiom]: + if not subjects: + subjects = self.entities() for s in subjects: term = self._entity(s) if term and term.intersection_of: @@ -741,7 +773,8 @@ def logical_definitions( propertyId=rel, fillerId=filler.id ) ldef.restrictions.append(er) - yield ldef + if logical_definition_matches(ldef, predicates=predicates, objects=objects): + yield ldef # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ # Implements: SearchInterface diff --git a/src/oaklib/implementations/simpleobo/simple_obo_implementation.py b/src/oaklib/implementations/simpleobo/simple_obo_implementation.py index c8a51b85c..698336c9e 100644 --- a/src/oaklib/implementations/simpleobo/simple_obo_implementation.py +++ b/src/oaklib/implementations/simpleobo/simple_obo_implementation.py @@ -120,6 +120,9 @@ from oaklib.interfaces.validator_interface import ValidatorInterface from oaklib.resource import OntologyResource from oaklib.types import CURIE, PRED_CURIE, SUBSET_CURIE +from oaklib.utilities.axioms.logical_definition_utilities import ( + logical_definition_matches, +) from oaklib.utilities.kgcl_utilities import tidy_change_object from oaklib.utilities.mapping.sssom_utils import inject_mapping_sources @@ -412,13 +415,13 @@ def create_entity( for filler in fillers: self.add_relationship(curie, pred, filler) - def add_relationship(self, curie: CURIE, predicate: PRED_CURIE, filler: CURIE): + def add_relationship(self, curie: CURIE, predicate: PRED_CURIE, filler: CURIE, **kwargs): t = self._stanza(curie) if predicate == IS_A: - t.add_tag_value(TAG_IS_A, filler) + t.add_tag_value(TAG_IS_A, filler, **kwargs) else: predicate_code = self.map_curie_to_shorthand(predicate) - t.add_tag_value_pair(TAG_RELATIONSHIP, predicate_code, filler) + t.add_tag_value_pair(TAG_RELATIONSHIP, predicate_code, filler, **kwargs) self._clear_relationship_index() def remove_relationship(self, curie: CURIE, predicate: Optional[PRED_CURIE], filler: CURIE): @@ -703,13 +706,29 @@ def node(self, curie: CURIE, strict=False, include_metadata=False) -> obograph.N meta.synonyms.append(syn) return obograph.Node(id=curie, lbl=self.label(curie), type=typ, meta=meta) - def as_obograph(self) -> Graph: - nodes = [self.node(curie) for curie in self.entities()] - edges = [Edge(sub=r[0], pred=r[1], obj=r[2]) for r in self.relationships()] - return Graph(id="TODO", nodes=nodes, edges=edges) + def as_obograph(self, expand_curies=False) -> Graph: + def expand(curie: CURIE) -> CURIE: + if expand_curies: + uri = self.curie_to_uri(curie, strict=False) + return uri if uri is not None else curie + else: + return curie + + entities = list(self.entities()) + nodes = [self.node(expand(curie)) for curie in entities] + edges = [ + Edge(sub=expand(r[0]), pred=expand(r[1]), obj=expand(r[2])) + for r in self.relationships() + ] + ldefs = list(self.logical_definitions(entities)) + return Graph(id="TODO", nodes=nodes, edges=edges, logicalDefinitionAxioms=ldefs) def logical_definitions( - self, subjects: Optional[Iterable[CURIE]] = None + self, + subjects: Optional[Iterable[CURIE]] = None, + predicates: Iterable[PRED_CURIE] = None, + objects: Iterable[CURIE] = None, + **kwargs, ) -> Iterable[LogicalDefinitionAxiom]: for s in subjects: t = self._stanza(s, strict=False) @@ -727,7 +746,8 @@ def logical_definitions( ) else: ldef.genusIds.append(m1) - yield ldef + if logical_definition_matches(ldef, predicates=predicates, objects=objects): + yield ldef # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ # Implements: SearchInterface @@ -865,7 +885,10 @@ def apply_patch( ) # Handling a bug where quotes are accidentally introduced. t.remove_tag_quoted_value(TAG_SYNONYM, v) elif isinstance(patch, kgcl.EdgeCreation): - self.add_relationship(patch.subject, patch.predicate, patch.object) + description = patch.change_description + self.add_relationship( + patch.subject, patch.predicate, patch.object, description=description + ) modified_entities.append(patch.subject) elif isinstance(patch, kgcl.EdgeDeletion): self.remove_relationship(patch.subject, patch.predicate, patch.object) diff --git a/src/oaklib/implementations/simpleobo/simple_obo_parser.py b/src/oaklib/implementations/simpleobo/simple_obo_parser.py index 323a877ca..b188ca0db 100644 --- a/src/oaklib/implementations/simpleobo/simple_obo_parser.py +++ b/src/oaklib/implementations/simpleobo/simple_obo_parser.py @@ -521,7 +521,20 @@ def remove_pairwise_tag_value(self, tag: TAG, val1: str, val2: str) -> None: logging.warning(f"No values to remove for {tag} = {val1} {val2} // {self}") self.tag_values = tvs - def add_tag_value(self, tag: TAG, val: str) -> None: + def _kwargs_to_qualifiers_string(self, **kwargs) -> str: + """ + Converts a set of kwargs to a qualifier string + + :param kwargs: + :return: + """ + if not kwargs: + return "" + quals = [f'{k}="{v}"' for k, v in kwargs.items()] + quals_str = ", ".join(quals) + return f" {{{quals_str}}}" + + def add_tag_value(self, tag: TAG, val: str, **kwargs) -> None: """ Adds a tag-value pair @@ -529,6 +542,8 @@ def add_tag_value(self, tag: TAG, val: str) -> None: :param val: :return: """ + if kwargs: + val += " " + self._kwargs_to_qualifiers_string(**kwargs) self.tag_values.append(TagValue(tag, val)) def add_quoted_tag_value(self, tag: TAG, val: str, xrefs: List[str]) -> None: @@ -541,7 +556,7 @@ def add_quoted_tag_value(self, tag: TAG, val: str, xrefs: List[str]) -> None: """ self.tag_values.append(TagValue(tag, f"\"{val}\" [{','.join(xrefs)}]")) - def add_tag_value_pair(self, tag: TAG, val1: str, val2: str) -> None: + def add_tag_value_pair(self, tag: TAG, val1: str, val2: str, **kwargs) -> None: """ Adds a tag-value pair where the value is a pair @@ -550,7 +565,10 @@ def add_tag_value_pair(self, tag: TAG, val1: str, val2: str) -> None: :param val2: :return: """ - self.tag_values.append(TagValue(tag, f"{val1} {val2}")) + v = f"{val1} {val2}" + if kwargs: + v += " " + self._kwargs_to_qualifiers_string(**kwargs) + self.tag_values.append(TagValue(tag, v)) def get_boolean_value(self, tag: TAG, strict=False) -> bool: """ diff --git a/src/oaklib/implementations/sqldb/sql_implementation.py b/src/oaklib/implementations/sqldb/sql_implementation.py index 958e2a41b..d2e150216 100644 --- a/src/oaklib/implementations/sqldb/sql_implementation.py +++ b/src/oaklib/implementations/sqldb/sql_implementation.py @@ -148,6 +148,9 @@ from oaklib.interfaces.taxon_constraint_interface import TaxonConstraintInterface from oaklib.interfaces.validator_interface import ValidatorInterface from oaklib.types import CATEGORY_CURIE, CURIE, SUBSET_CURIE +from oaklib.utilities.axioms.logical_definition_utilities import ( + logical_definition_matches, +) from oaklib.utilities.graph.relationship_walker import walk_down, walk_up from oaklib.utilities.identifier_utils import ( string_as_base64_curie, @@ -207,6 +210,11 @@ def regex_to_sql_like(regex: str) -> str: """ convert a regex to a LIKE + * ``.*`` => ``%`` + * ``.`` => ``_`` + * ``^`` => ``%`` (at start of string) + * ``$`` => ``%`` (at end of string) + TODO: implement various different DBMS flavors https://stackoverflow.com/questions/20794860/regex-in-sql-to-detect-one-or-more-digit @@ -1515,21 +1523,34 @@ def _ixn_definition(self, ixn: str, subject: CURIE) -> Optional[LogicalDefinitio return ldef def logical_definitions( - self, subjects: Optional[Iterable[CURIE]] = None + self, + subjects: Optional[Iterable[CURIE]] = None, + predicates: Iterable[PRED_CURIE] = None, + objects: Iterable[CURIE] = None, + **kwargs, ) -> Iterable[LogicalDefinitionAxiom]: logging.info("Getting logical definitions") q = self.session.query(OwlEquivalentClassStatement) + if predicates is not None: + predicates = list(predicates) + if objects is not None: + objects = list(objects) if subjects is None: - for ldef in self._logical_definitions_from_eq_query(q): + for ldef in self._logical_definitions_from_eq_query(q, predicates, objects): yield ldef return for curie_it in chunk(subjects, self.max_items_for_in_clause): logging.info(f"Getting logical definitions for {curie_it} from {subjects}") q = q.filter(OwlEquivalentClassStatement.subject.in_(tuple(curie_it))) - for ldef in self._logical_definitions_from_eq_query(q): + for ldef in self._logical_definitions_from_eq_query(q, predicates, objects): yield ldef - def _logical_definitions_from_eq_query(self, query) -> Iterable[LogicalDefinitionAxiom]: + def _logical_definitions_from_eq_query( + self, + query, + predicates: Iterable[PRED_CURIE] = None, + objects: Iterable[CURIE] = None, + ) -> Iterable[LogicalDefinitionAxiom]: for eq_row in query: ixn_q = self.session.query(Statements).filter( and_( @@ -1540,6 +1561,8 @@ def _logical_definitions_from_eq_query(self, query) -> Iterable[LogicalDefinitio for ixn in ixn_q: ldef = self._ixn_definition(ixn.object, eq_row.subject) if ldef: + if not logical_definition_matches(ldef, predicates=predicates, objects=objects): + continue yield ldef # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/src/oaklib/implementations/ubergraph/ubergraph_implementation.py b/src/oaklib/implementations/ubergraph/ubergraph_implementation.py index b16e6bded..b650f60b5 100644 --- a/src/oaklib/implementations/ubergraph/ubergraph_implementation.py +++ b/src/oaklib/implementations/ubergraph/ubergraph_implementation.py @@ -18,7 +18,7 @@ from oaklib.interfaces import SubsetterInterface from oaklib.interfaces.basic_ontology_interface import RELATIONSHIP, RELATIONSHIP_MAP from oaklib.interfaces.mapping_provider_interface import MappingProviderInterface -from oaklib.interfaces.obograph_interface import OboGraphInterface +from oaklib.interfaces.obograph_interface import GraphTraversalMethod, OboGraphInterface from oaklib.interfaces.rdf_interface import TRIPLE from oaklib.interfaces.relation_graph_interface import RelationGraphInterface from oaklib.interfaces.search_interface import SearchInterface @@ -282,8 +282,14 @@ def relationships_to_graph(self, relationships: Iterable[RELATIONSHIP]) -> obogr return obograph.Graph(id="query", nodes=list(nodes.values()), edges=edges) def ancestors( - self, start_curies: Union[CURIE, List[CURIE]], predicates: List[PRED_CURIE] = None + self, + start_curies: Union[CURIE, List[CURIE]], + predicates: List[PRED_CURIE] = None, + reflexive=True, + method: Optional[GraphTraversalMethod] = None, ) -> Iterable[CURIE]: + if method and method == GraphTraversalMethod.HOP: + raise NotImplementedError("HOP not implemented for ubergraph") # TODO: DRY if not isinstance(start_curies, list): start_curies = [start_curies] @@ -303,8 +309,14 @@ def ancestors( yield self.uri_to_curie(row["o"]["value"]) def descendants( - self, start_curies: Union[CURIE, List[CURIE]], predicates: List[PRED_CURIE] = None + self, + start_curies: Union[CURIE, List[CURIE]], + predicates: List[PRED_CURIE] = None, + reflexive=True, + method: Optional[GraphTraversalMethod] = None, ) -> Iterable[CURIE]: + if method and method == GraphTraversalMethod.HOP: + raise NotImplementedError("HOP not implemented for ubergraph") # TODO: DRY query_uris = [self.curie_to_sparql(curie) for curie in start_curies] where = ["?s ?p ?o", "?s a owl:Class", f'VALUES ?o {{ {" ".join(query_uris)} }}'] diff --git a/src/oaklib/interfaces/basic_ontology_interface.py b/src/oaklib/interfaces/basic_ontology_interface.py index bfd52699f..1838af7bf 100644 --- a/src/oaklib/interfaces/basic_ontology_interface.py +++ b/src/oaklib/interfaces/basic_ontology_interface.py @@ -232,6 +232,10 @@ def curie_to_uri(self, curie: CURIE, strict: bool = False) -> Optional[URI]: :param strict: (Default is False) if True, exceptions will be raised if curie cannot be expanded :return: """ + if ":" not in curie: + if strict: + raise ValueError(f"Invalid CURIE: {curie}") + return None rv = self.converter.expand(curie) if rv is None and strict: prefix_map_text = "\n".join( diff --git a/src/oaklib/interfaces/obograph_interface.py b/src/oaklib/interfaces/obograph_interface.py index 0ec904f26..6fefea325 100644 --- a/src/oaklib/interfaces/obograph_interface.py +++ b/src/oaklib/interfaces/obograph_interface.py @@ -461,6 +461,14 @@ def paths( """ Returns all paths between sources and targets. + >>> from oaklib import get_adapter + >>> adapter = get_adapter("tests/input/go-nucleus.db", implements=OboGraphInterface) + >>> for path in sorted(list(adapter.paths(["GO:0005634"], ["GO:0005773"]))): + ... print(path) + ('GO:0005634', 'GO:0005773', 'GO:0005634') + ('GO:0005634', 'GO:0005773', 'GO:0005773') + ('GO:0005634', 'GO:0005773', 'GO:0043231') + :param start_curies: :param start_curies: :param predicates: @@ -491,12 +499,33 @@ def paths( yield s, o, intermediate def logical_definitions( - self, subjects: Optional[Iterable[CURIE]] = None + self, + subjects: Optional[Iterable[CURIE]] = None, + predicates: Iterable[PRED_CURIE] = None, + objects: Iterable[CURIE] = None, + **kwargs, ) -> Iterable[LogicalDefinitionAxiom]: """ - Yields all logical definitions for input subjects + Yields all logical definitions for input subjects. + + >>> from oaklib import get_adapter + >>> adapter = get_adapter("tests/input/go-nucleus.db", implements=OboGraphInterface) + >>> for ldef in adapter.logical_definitions(["GO:0009892"]): + ... print(f"Genus: {adapter.label(ldef.genusIds[0])}") + ... for r in ldef.restrictions: + ... print(f" Differentia: {adapter.label(r.propertyId)} SOME {adapter.label(r.fillerId)}") + Genus: biological regulation + Differentia: negatively regulates SOME metabolic process + + Leaving the subjects parameter as None will yield all logical definitions in the ontology. + + >>> len(list(adapter.logical_definitions())) + 50 + + :param subjects: If specified, defined class must be in this set + :param predicates: If specified, only yields logical definitions with these predicates + :param objects: If specified, only yields logical definitions with genus or filler in this list - :param subjects: :return: """ return iter(()) diff --git a/src/oaklib/interfaces/patcher_interface.py b/src/oaklib/interfaces/patcher_interface.py index 9a3656584..2cae83b6f 100644 --- a/src/oaklib/interfaces/patcher_interface.py +++ b/src/oaklib/interfaces/patcher_interface.py @@ -34,6 +34,7 @@ class PatcherInterface(BasicOntologyInterface, ABC): If this is set then the recommended value is dct:contributor""" ignore_invalid_changes: bool = False + """If True, then invalid changes are ignored. If False, then invalid changes raise an exception""" def apply_patch( self, @@ -172,8 +173,13 @@ def expand_change(self, change: Change, configuration: Configuration = None) -> # edge previously existed continue new_edges.append(e) + desc = f"Rewired from link to {about_node} {self.label(about_node)}" ch = EdgeCreation( - generate_change_id(), subject=s, predicate=pred, object=o + generate_change_id(), + subject=s, + predicate=pred, + object=o, + change_description=desc, ) changes.append(ch) logging.info(f"Rewiring {s} {p1} {about_node} to {s} {pred} {o}") diff --git a/src/oaklib/interfaces/subsetter_interface.py b/src/oaklib/interfaces/subsetter_interface.py index f424cf918..bbf7c1a25 100644 --- a/src/oaklib/interfaces/subsetter_interface.py +++ b/src/oaklib/interfaces/subsetter_interface.py @@ -57,7 +57,7 @@ def gap_fill_relationships( self, seed_curies: List[CURIE], predicates: List[PRED_CURIE] = None ) -> Iterator[RELATIONSHIP]: """ - Given a term subset as a list of curies, find all non-redundant relationships connecting them + Given a term subset as a list of curies, find all non-redundant relationships connecting them. This assumes relation-graph entailed edges, so currently only implemented for ubergraph and sqlite diff --git a/src/oaklib/io/streaming_obo_writer.py b/src/oaklib/io/streaming_obo_writer.py index 9dbdc85ef..52f0be59b 100644 --- a/src/oaklib/io/streaming_obo_writer.py +++ b/src/oaklib/io/streaming_obo_writer.py @@ -7,7 +7,7 @@ from oaklib.converters.obo_graph_to_obo_format_converter import ( OboGraphToOboFormatConverter, ) -from oaklib.datamodels.obograph import GraphDocument +from oaklib.datamodels.obograph import GraphDocument, LogicalDefinitionAxiom from oaklib.datamodels.vocabulary import IS_A, RDF_TYPE, SYNONYM_PRED_TO_SCOPE_MAP from oaklib.interfaces.metadata_interface import MetadataInterface from oaklib.interfaces.obograph_interface import OboGraphInterface @@ -84,3 +84,16 @@ def emit_multiple(self, entities: Iterable[CURIE], **kwargs): obodoc.dump(self.file) else: super().emit_multiple(entities, **kwargs) + + def emit_obj(self, obj: Any, **kwargs): + oi = self.ontology_interface + if isinstance(obj, CURIE): + self.emit_curie(obj) + elif isinstance(obj, LogicalDefinitionAxiom): + self.line("[Term]") + self.line(f"id: {obj.definedClassId} ! {oi.label(obj.definedClassId)}") + for genus in obj.genusIds: + self.line(f"intersection_of: {genus} ! {oi.label(genus)}") + for r in obj.restrictions: + self.line(f"intersection_of: {r.propertyId} {r.fillerId} ! {oi.label(r.fillerId)}") + self.line("\n") diff --git a/src/oaklib/io/streaming_writer.py b/src/oaklib/io/streaming_writer.py index cffc92d31..b630ffdb9 100644 --- a/src/oaklib/io/streaming_writer.py +++ b/src/oaklib/io/streaming_writer.py @@ -121,6 +121,11 @@ def add_labels(self, obj_as_dict: Dict, label_fields: Optional[List[str]] = None :param label_fields: :return: """ + + def _label(c: CURIE) -> str: + lbl = self.ontology_interface.label(c, lang=self.settings.preferred_language) + return str(lbl) if lbl else "" + if label_fields and self.autolabel: for f in label_fields: curie = obj_as_dict.get(f, None) @@ -131,14 +136,7 @@ def add_labels(self, obj_as_dict: Dict, label_fields: Optional[List[str]] = None if delim and isinstance(curie, str) and delim in curie: curie = curie.split("|") if isinstance(curie, list): - label = [ - str( - self.ontology_interface.label( - c, lang=self.settings.preferred_language - ) - ) - for c in curie - ] + label = [_label(c) for c in curie] if delim: label = delim.join(label) else: diff --git a/src/oaklib/mappers/base_mapper.py b/src/oaklib/mappers/base_mapper.py index 30e28ae58..e1a4a4da6 100644 --- a/src/oaklib/mappers/base_mapper.py +++ b/src/oaklib/mappers/base_mapper.py @@ -2,7 +2,7 @@ from abc import ABC from collections import defaultdict from dataclasses import dataclass -from typing import Dict, Iterable, Iterator, List, Tuple +from typing import Any, Dict, Iterable, Iterator, List, Tuple from curies import Converter from sssom_schema import SEMAPV, Mapping @@ -27,6 +27,12 @@ class Mapper(ABC): _mappings_by_source: Dict[CURIE, List[CURIE]] = None + axiom_annotations_to_mapping_predicates: Dict[Tuple[CURIE, Any], CURIE] = None + """ + Maps axiom annotations to predicates. + GO, Mondo and other ontologies may use axiom annotations to encode mapping predicates. + """ + def __post_init__(self): self._mappings_by_source = defaultdict(list) self.add_mappings(self.mappings) diff --git a/src/oaklib/mappers/ontology_metadata_mapper.py b/src/oaklib/mappers/ontology_metadata_mapper.py index 04daf9523..8001f53ed 100644 --- a/src/oaklib/mappers/ontology_metadata_mapper.py +++ b/src/oaklib/mappers/ontology_metadata_mapper.py @@ -54,3 +54,7 @@ def is_a_uri(self) -> URI: def use_skos_profile(self): """Sets the profile to SKOS.""" self.add_mappings(load_default_sssom("omo-to-skos")) + + def skos_encodings(self): + """Maps SKOS encodings.""" + self.add_mappings(load_default_sssom("skos-encodings")) diff --git a/src/oaklib/selector.py b/src/oaklib/selector.py index b163223ae..9c1e39bde 100644 --- a/src/oaklib/selector.py +++ b/src/oaklib/selector.py @@ -3,7 +3,7 @@ import logging import os from pathlib import Path -from typing import List, Optional, Type, Union +from typing import List, Optional, Type, TypeVar, Union import requests from deprecation import deprecated @@ -62,10 +62,15 @@ ), } +T = TypeVar("T", bound=BasicOntologyInterface) + def get_adapter( - descriptor: Union[str, Path, InputSpecification], format: str = None, **kwargs -) -> BasicOntologyInterface: + descriptor: Union[str, Path, InputSpecification], + format: str = None, + implements: Optional[Type[T]] = None, + **kwargs, +) -> T: """ Gets an adapter (implementation) for a given descriptor. @@ -119,7 +124,7 @@ def get_adapter( >>> from oaklib import get_adapter >>> from gilda import get_grounder - >>> grounder = get_grounder("~/.data/gilda/0.11.1/grounding_terms.tsv.gz") + >>> grounder = get_grounder() >>> adapter = get_adapter("gilda:", grounder=grounder) >>> annotations = adapter.annotate_text("nucleus") diff --git a/src/oaklib/utilities/axioms/__init__.py b/src/oaklib/utilities/axioms/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/oaklib/utilities/axioms/logical_definition_analyzer.py b/src/oaklib/utilities/axioms/logical_definition_analyzer.py new file mode 100644 index 000000000..1b8aaa481 --- /dev/null +++ b/src/oaklib/utilities/axioms/logical_definition_analyzer.py @@ -0,0 +1,111 @@ +import base64 +import logging +from copy import deepcopy +from itertools import product +from random import shuffle +from typing import Iterator, List, Set, Tuple + +from oaklib import BasicOntologyInterface +from oaklib.datamodels.obograph import ( + ExistentialRestrictionExpression, + LogicalDefinitionAxiom, +) +from oaklib.datamodels.vocabulary import IS_A +from oaklib.interfaces import OboGraphInterface +from oaklib.types import CURIE +from oaklib.utilities.lexical.patternizer import LexicalPattern + + +def logical_definition_to_set(ldef: LogicalDefinitionAxiom) -> Set[CURIE]: + return set( + ldef.genusIds + + [r.propertyId for r in ldef.restrictions] + + [r.fillerId for r in ldef.restrictions] + ) + + +def set_delta(s1: Set[CURIE], s2: Set[CURIE]) -> Tuple[Tuple[CURIE], Tuple[CURIE]]: + return tuple(s1.difference(s2)), tuple(s2.difference(s1)) + + +def reflexive_logical_definition(curie: CURIE) -> LogicalDefinitionAxiom: + return LogicalDefinitionAxiom(definedClassId=curie, genusIds=[curie], restrictions=[]) + + +def logical_definition_signature(ldef: LogicalDefinitionAxiom) -> List[CURIE]: + return ( + [ldef.definedClassId] + + ldef.genusIds + + [r.propertyId for r in ldef.restrictions] + + [r.fillerId for r in ldef.restrictions] + ) + + +def analyze_logical_definitions( + adapter: BasicOntologyInterface, ldefs: List[LogicalDefinitionAxiom], reflexive=False +) -> Iterator: + if reflexive: + ldefs = deepcopy(ldefs) + signature = [] + for ldef in ldefs: + signature.extend(logical_definition_signature(ldef)) + for curie in signature: + ldefs.append(reflexive_logical_definition(curie)) + lmap = {ldef.definedClassId: logical_definition_to_set(ldef) for ldef in ldefs} + curies = list(lmap.keys()) + rels = [rel for rel in adapter.relationships(curies) if rel[2] in curies] + logging.info(f"Found {len(rels)} relationships") + abduced = [] + amap = {} + for rel in rels: + sx = lmap[rel[0]] + ox = lmap[rel[2]] + d = set_delta(sx, ox) + abduced.append((rel[1], d)) + amap[d] = rel + logging.debug(f"Indexing amap[{d}] = {rel}") + for i, ix in lmap.items(): + irels = [rel for rel in rels if rel[0] == i] + for j, jx in lmap.items(): + ijrels = [rel for rel in irels if rel[2] == j] + if i != j: + d = set_delta(ix, jx) + logging.debug(f"Checking if {i}, {j} = {d} in amap") + if d in amap: + if not ijrels: + print(i, j, d, amap[d]) + yield ("abduced", i, j, amap[d]) + + +def generate_descendant_logical_definitions( + adapter: OboGraphInterface, + ldef: LogicalDefinitionAxiom, + pattern: LexicalPattern = None, + random_sample=False, +) -> Iterator[LogicalDefinitionAxiom]: + existing = list(adapter.logical_definitions()) + dc_to_ldef = {ldef.definedClassId: ldef for ldef in existing} + ldef_to_dc = {str(ldef): dc for dc, ldef in dc_to_ldef.items()} + terms = ldef.genusIds + [r.fillerId for r in ldef.restrictions] + num_genus_ids = len(ldef.genusIds) + props = [r.propertyId for r in ldef.restrictions] + candidates_list = [adapter.descendants([t], [IS_A], reflexive=True) for t in terms] + if random_sample: + candidates_list = [shuffle(cs) for cs in candidates_list] + for tpl in product(*candidates_list): + if tpl == tuple(terms): + continue + restrictions = [ + ExistentialRestrictionExpression(propertyId=pred, fillerId=filler) + for pred, filler in zip(props, tpl[num_genus_ids:]) + ] + curie = base64.b64encode(str(tpl).encode("ascii")).decode("utf-8") + new_ldef = LogicalDefinitionAxiom( + definedClassId=curie, + genusIds=list(tpl[0:num_genus_ids]), + restrictions=restrictions, + ) + if str(new_ldef) in ldef_to_dc: + logging.debug(f"Skipping {new_ldef} because it already exists") + continue + yield new_ldef diff --git a/src/oaklib/utilities/axioms/logical_definition_summarizer.py b/src/oaklib/utilities/axioms/logical_definition_summarizer.py new file mode 100644 index 000000000..774e36fdf --- /dev/null +++ b/src/oaklib/utilities/axioms/logical_definition_summarizer.py @@ -0,0 +1,224 @@ +from collections import defaultdict +from enum import Enum +from typing import Any, Dict, List, Optional, Union + +from pydantic import BaseModel + +from oaklib.datamodels.obograph import ( + ExistentialRestrictionExpression, + LogicalDefinitionAxiom, +) +from oaklib.datamodels.vocabulary import IS_A +from oaklib.interfaces import OboGraphInterface +from oaklib.types import CURIE, PRED_CURIE +from oaklib.utilities.obograph_utils import depth_first_ordering + + +class LogicalDefinitionElementRole(Enum): + DEFINED_CLASS = "defined_class" + GENUS = "genus" + PREDICATE = "predicate" + FILLER = "filler" + SIGNATURE = "signature" + META = "meta" + + +class Config(BaseModel): + row_represents: Optional[List[LogicalDefinitionElementRole]] = None + column_represents: Optional[List[LogicalDefinitionElementRole]] = None + cell_represents: Optional[List[LogicalDefinitionElementRole]] = None + + +def parse_axes_to_config(config: str) -> Config: + sep = "," + parts = [parse_config_element(x) for x in config.split(sep)] + row_represents = parts[0] + column_represents = parts[1] if len(parts) > 1 else None + cell_represents = parts[2] if len(parts) > 2 else None + return Config( + row_represents=row_represents, + column_represents=column_represents, + cell_represents=cell_represents, + ) + + +def parse_config_element(config: str) -> [LogicalDefinitionElementRole]: + sep = "+" + if sep in config: + return [parse_config_element(v)[0] for v in config.split("+")] + for x in LogicalDefinitionElementRole: + if x.value.startswith(config): + return [x] + + +def sort_entities( + adapter: OboGraphInterface, + entities: List[CURIE], + traversal_order_predicates: Optional[List[PRED_CURIE]] = None, +) -> List[CURIE]: + graph = adapter.ancestor_graph(entities, predicates=traversal_order_predicates) + sorted_entities = [x for x in depth_first_ordering(graph) if x in entities] + return sorted_entities + [x for x in entities if x not in sorted_entities] + + +def logical_definitions_to_matrix( + adapter: OboGraphInterface, + ldefs: List[LogicalDefinitionAxiom], + config: Config = None, + traversal_order_predicates: Optional[List[PRED_CURIE]] = None, + sort_values: bool = True, +) -> List[Dict[str, List[Any]]]: + """ + Converts a list of logical definition axioms to a table. + + The axes are determined by the configuration object. The user can control both + what each row corresponds to and what each column corresponds to. + + :param adapter: OAK adapter for performing lookups + :param ldefs: list of logical definition axioms to summarize + :param config: axis configuration + :param sort_values: sort values in each cell + :return: list of row objects + """ + if not config: + config = Config() + row_represents, column_represents = config.row_represents, config.column_represents + rows = [] + + def _cell_val( + ldef: LogicalDefinitionAxiom, + element: Optional[Union[CURIE, ExistentialRestrictionExpression]] = None, + cell_represents: List[LogicalDefinitionElementRole] = None, + ) -> Any: + if element is not None: + return element + else: + # if LogicalDefinitionElementRole.FILLER in cell_represents: + # return ldef.definedClassId + return ldef.definedClassId + + curie_to_col_name = {} + + def _col_name(curie: CURIE) -> str: + lbl = adapter.label(curie) + if lbl: + cn = lbl.replace(" ", "_") + else: + cn = curie + curie_to_col_name[curie] = cn + return cn + + if not row_represents: + row_represents = [LogicalDefinitionElementRole.DEFINED_CLASS] + + pk = None + if LogicalDefinitionElementRole.DEFINED_CLASS in row_represents: + if len(row_represents) > 1: + raise ValueError( + f"Cannot have more than one row_representing for defined_class: {row_represents}" + ) + for ldef in ldefs: + row = defaultdict(list) + pk = "defined_class" + row[pk] = [ldef.definedClassId] + ok = False + if LogicalDefinitionElementRole.PREDICATE in column_represents: + for x in ldef.genusIds: + row["genus"].append(x) + for x in ldef.restrictions: + row[_col_name(x.propertyId)].append(x.fillerId) + ok = True + if LogicalDefinitionElementRole.FILLER in column_represents: + for x in ldef.genusIds: + row["genus"].append(x) + for x in ldef.restrictions: + row[_col_name(x.fillerId)].append(x.propertyId) + ok = True + if LogicalDefinitionElementRole.GENUS in column_represents: + # for x in ldef.restrictions: + # row[_col_name(x.fillerId)].extend([x.propertyId, x.fillerId]) + for x in ldef.genusIds: + row[_col_name(x)].append(_cell_val(ldef)) + ok = True + if LogicalDefinitionElementRole.META in column_represents: + for x in ldef.genusIds: + row["genus"].append(x) + for x in ldef.restrictions: + row["differentia"].append((x.propertyId, x.fillerId)) + ok = True + if not ok: + raise ValueError( + f"Invalid column_represents: {column_represents} for row: {row_represents}" + ) + rows.append(row) + else: + row_ix = {} + for ldef in ldefs: + pk_vals = [] + if LogicalDefinitionElementRole.GENUS in row_represents: + pk_vals = ldef.genusIds + if LogicalDefinitionElementRole.PREDICATE in row_represents: + pk_vals = [x.propertyId for x in ldef.restrictions] + if LogicalDefinitionElementRole.FILLER in row_represents: + pk_vals = [x.fillerId for x in ldef.restrictions] + if column_represents is None: + column_represents = [LogicalDefinitionElementRole.PREDICATE] + if not pk_vals: + raise ValueError(f"Invalid row_represents: {row_represents}") + for pk_val in pk_vals: + if pk_val not in row_ix: + row = defaultdict(list) + row_ix[pk_val] = row + row = row_ix[pk_val] + pk = row_represents[0].value + row[pk] = [pk_val] + ok = False + if LogicalDefinitionElementRole.GENUS in column_represents: + genus_ids = ldef.genusIds if ldef.genusIds else ["NO_GENUS"] + for x in genus_ids: + row[_col_name(x)].append(_cell_val(ldef)) + ok = True + if LogicalDefinitionElementRole.FILLER in column_represents: + fillers = [x.fillerId for x in ldef.restrictions] + if not fillers: + fillers = ["NO_FILLER"] + for x in fillers: + row[_col_name(x)].append(_cell_val(ldef)) + ok = True + if LogicalDefinitionElementRole.PREDICATE in column_represents: + preds = [x.propertyId for x in ldef.restrictions] + if not preds: + preds = ["NO_PREDICATE"] + for x in preds: + row[_col_name(x)].append(_cell_val(ldef)) + ok = True + if not ok: + raise ValueError( + f"Invalid column_represents: {column_represents} for row: {row_represents}" + ) + rows = list(row_ix.values()) + cols = [] + for row in rows: + for k in row.keys(): + if k not in cols: + cols.append(k) + for row in rows: + for col in cols: + if col not in row: + row[col] = [""] + if sort_values: + row[col] = sorted(row[col]) + if traversal_order_predicates is None: + traversal_order_predicates = [IS_A] + if traversal_order_predicates: + sorted_row_ids = sort_entities( + adapter, [row[pk][0] for row in rows], traversal_order_predicates + ) + rows = sorted(rows, key=lambda row: sorted_row_ids.index(row[pk][0])) + sorted_col_ids = sort_entities( + adapter, list(curie_to_col_name.keys()), traversal_order_predicates + ) + fixed_cols = [col for col in cols if col not in curie_to_col_name.values()] + ordered_cols = fixed_cols + [curie_to_col_name[col] for col in sorted_col_ids] + rows = [{col: row[col] for col in ordered_cols} for row in rows] + return rows diff --git a/src/oaklib/utilities/axioms/logical_definition_utilities.py b/src/oaklib/utilities/axioms/logical_definition_utilities.py new file mode 100644 index 000000000..99e9bd031 --- /dev/null +++ b/src/oaklib/utilities/axioms/logical_definition_utilities.py @@ -0,0 +1,59 @@ +from typing import List, Optional + +from oaklib.datamodels.obograph import LogicalDefinitionAxiom +from oaklib.datamodels.vocabulary import IS_A +from oaklib.types import CURIE, PRED_CURIE + + +def logical_definition_matches( + ldef: LogicalDefinitionAxiom, + subjects: Optional[List[CURIE]] = None, + predicates: Optional[List[PRED_CURIE]] = None, + objects: Optional[List[CURIE]] = None, +) -> bool: + """ + Check if a logical definition matches a filter criteria. + + >>> from oaklib.datamodels.obograph import LogicalDefinitionAxiom, ExistentialRestrictionExpression + >>> from oaklib.utilities.axioms.logical_definition_utilities import logical_definition_matches + >>> from oaklib.datamodels.vocabulary import IS_A + >>> differentia1 = ExistentialRestrictionExpression(propertyId="R:1", fillerId="X:Filler1") + >>> differentia2 = ExistentialRestrictionExpression(propertyId="R:1", fillerId="X:Filler2") + >>> ldef = LogicalDefinitionAxiom(definedClassId="X:1", + ... genusIds=["X:Genus"], restrictions=[differentia1, differentia2]) + >>> logical_definition_matches(ldef) + True + >>> logical_definition_matches(ldef, subjects=["X:Genus"]) + False + >>> logical_definition_matches(ldef, objects=["X:Genus"]) + True + >>> logical_definition_matches(ldef, subjects=["X:Filler1"]) + False + >>> logical_definition_matches(ldef, predicates=["R:1"]) + True + >>> logical_definition_matches(ldef, predicates=[IS_A]) + True + >>> logical_definition_matches(ldef, objects=["X:Filler1"]) + True + + :param ldef: + :param subjects: if specified, the logical definition must have one of these subjects + :param predicates: if specified, the logical definition must have one of these predicates + :param objects: if specified, the logical definition must have one of these objects + :return: + """ + if predicates or objects: + class_signature = set(ldef.genusIds + [r.fillerId for r in ldef.restrictions]) + pred_signature = set([r.propertyId for r in ldef.restrictions]) + if ldef.genusIds: + pred_signature.add(IS_A) + if predicates: + if not pred_signature.intersection(predicates): + return False + if objects: + if not class_signature.intersection(objects): + return False + if subjects: + if ldef.definedClassId not in subjects: + return False + return True diff --git a/src/oaklib/utilities/lexical/patternizer.py b/src/oaklib/utilities/lexical/patternizer.py new file mode 100644 index 000000000..4d82965c7 --- /dev/null +++ b/src/oaklib/utilities/lexical/patternizer.py @@ -0,0 +1,284 @@ +"""Detect logical definitions from lexical elements in an ontology.""" +import logging +import urllib +from typing import Dict, Iterator, List, Optional + +import yaml +from pydantic import BaseModel + +import oaklib.datamodels.obograph as obograph +from oaklib import BasicOntologyInterface +from oaklib.datamodels.vocabulary import IS_A +from oaklib.interfaces import OboGraphInterface +from oaklib.types import CURIE + + +class LexicalPattern(BaseModel): + """ + A lexical pattern is a string that is used to detect a logical definition. + + The data model here is similar to DOSDPs, but is geared towards parsing of lexical elements + in ontologies. + """ + + name: str + """Name of lexical pattern. Typically corresponds to pattern.""" + + pattern: Optional[str] = None + """String pattern to match. If None, defaults to name.""" + + is_regex: Optional[bool] = False + """If True, pattern is a regular expression. + If False, pattern is a string. Defaults to False. NOT IMPLEMENTED YET.""" + + pattern_position: Optional[int] = None + """If 0, then pattern must be at the beginning of the label. + If -1, then pattern must be at the end of the label. + If None, then pattern can be anywhere in the label. + No other options are supported. Defaults to None.""" + + description: Optional[str] = None + """Description of pattern.""" + + curie: Optional[CURIE] = None + """If the pattern maps to a logical definition, then this is the curie + of the term that is a fixed element of the definition (genus or differentia filler).""" + + curie_is_genus: Optional[bool] = True + """If True, then the curie is the genus and the extracted term is the differentia filler.""" + + differentia_predicate: Optional[CURIE] = None + """If the pattern maps to a logical definition, then this is the predicate + that is used in the differentia.""" + + +class Differentia(BaseModel): + """Discriminating relationship in a logical definition.""" + + predicate: CURIE + filler: CURIE + + +class LogicalDefinition(BaseModel): + """Logical definition of a term, following genus-differentia format.""" + + genus: CURIE + differentia: List[Differentia] + + +class Term(BaseModel): + curie: CURIE + label: str + logical_definition: Optional[LogicalDefinition] = None + pattern: Optional[str] = None + genus_not_in_descendants: Optional[bool] = False + differentia_not_in_descendants: Optional[bool] = False + + +class ExtractedConcept(BaseModel): + """ + A concept extracted from a lexical pattern. + + For example, in a pattern like "nuclear X", the concept is "X". + """ + + label: str + curies: List[CURIE] + in_ontology: Optional[bool] = None + instances: Dict[str, Optional[Term]] + + +class LexicalPatternCollection(BaseModel): + """Collection of lexical patterns""" + + patterns: List[LexicalPattern] + + +def match_and_extract(pattern: LexicalPattern, label: str) -> Optional[str]: + """ + Given a lexical pattern and a label, return the label with the pattern removed. + + >>> from oaklib.utilities.lexical.patternizer import LexicalPattern, match_and_extract + >>> pattern = LexicalPattern(name="nuclear X", pattern="nuclear") + >>> print(match_and_extract(pattern, "nuclear membrane")) + membrane + + :param pattern: + :param label: + :return: + """ + if pattern.pattern in label: + if pattern.pattern_position is not None: + if pattern.pattern_position == 0: + if not label.startswith(pattern.pattern): + return None + elif pattern.pattern_position == -1: + if not label.endswith(pattern.pattern): + return None + else: + raise NotImplementedError("Pattern position must be 0 or -1.") + return label.replace(pattern.pattern, "").strip() + + +def lexical_pattern_instances( + adapter: BasicOntologyInterface, + patterns: List[LexicalPattern], + curies: Optional[List[CURIE]] = None, + new_concept_prefix=None, + strict=False, +) -> List[ExtractedConcept]: + """ + Given a list of lexical patterns, return a list of ExtractedConcepts. + + Each ExtractedConcepts contains a label and a dictionary of instances, keyed by the pattern name. + + :param adapter: + :param patterns: + :param curies: + :param new_concept_prefix: + :return: + """ + if curies is None: + curies = list(adapter.entities()) + id_labels = list(adapter.labels(curies, allow_none=False)) + ecs = {} + injected_curies = [] + for pattern in patterns: + if pattern.pattern is None: + pattern.pattern = pattern.name + logging.info(f"Processing pattern {pattern.name} on {len(id_labels)} labels.") + for id, label in id_labels: + concept_label = match_and_extract(pattern, label) + if concept_label: + # concept_label = label.replace(pattern.pattern, "").strip() + if concept_label not in ecs: + concept_ids = adapter.curies_by_label(concept_label) + if len(concept_ids) > 1: + candidate_concept_ids = [id for id in concept_ids if id in curies] + if len(candidate_concept_ids) > 0: + concept_ids = candidate_concept_ids + in_ontology = len(concept_ids) == 1 + if not concept_ids: + if new_concept_prefix: + # make label safe by url encoding using library + concept_ids = [ + f"{new_concept_prefix}:{urllib.parse.quote(concept_label)}" + ] + injected_curies.append(concept_ids[0]) + ecs[concept_label] = ExtractedConcept( + label=concept_label, + curies=concept_ids, + in_ontology=in_ontology, + instances={}, + ) + concept_ids = ecs[concept_label].curies + if pattern.curie is not None and not concept_ids and strict: + raise ValueError( + f"Pattern {pattern.name} matched {concept_label} but no curie was found." + ) + if pattern.curie is not None and concept_ids: + if pattern.curie_is_genus: + genus = pattern.curie + differentia = concept_ids[0] + else: + genus = concept_ids[0] + differentia = pattern.curie + ldef = LogicalDefinition( + genus=genus, + differentia=[ + Differentia(predicate=pattern.differentia_predicate, filler=differentia) + ], + ) + else: + ldef = None + term = Term(curie=id, label=label, logical_definition=ldef, pattern=pattern.name) + if ldef: + if isinstance(adapter, OboGraphInterface): + if ldef.genus not in injected_curies: + if ldef.genus not in adapter.ancestors(id, [IS_A]): + term.genus_not_in_descendants = True + differentia0 = ldef.differentia[0] + filler = differentia0.filler + if filler not in injected_curies: + # pred_closure = [IS_A, differentia0.predicate] + pred_closure = None + if filler not in adapter.ancestors(id, pred_closure): + term.differentia_not_in_descendants = True + ecs[concept_label].instances[pattern.name] = term + return list(ecs.values()) + + +def as_matrix( + ecs: List[ExtractedConcept], + pattern_collection: Optional[LexicalPatternCollection] = None, + fields: Optional[List[str]] = None, +) -> Iterator[dict]: + """ + Given a list of ExtractedConcepts, a matrix representation as a list of dicts. + + Each row is an ExtractedConcept, and each column is a pattern. + + :param ecs: + :return: + """ + if not fields: + if pattern_collection: + fields = [p.name for p in pattern_collection.patterns] + if not fields: + fields = set() + for ec in ecs: + fields.update(ec.instances.keys()) + fields = list(fields) + + def cell_value(ec: ExtractedConcept, field: str) -> Optional[str]: + if field in ec.instances: + inst = ec.instances[field] + v = inst.curie + if inst.genus_not_in_descendants: + v = f"*{v}/GEN" + if inst.differentia_not_in_descendants: + v = f"+{v}/DF" + else: + v = "" + return v + + for ec in ecs: + curie = ec.curies[0] if ec.curies else None + row = {"id": curie, "label": ec.label, **{f: cell_value(ec, f) for f in fields}} + n = len([v for v in row.values() if v]) - 1 + row["num_concepts"] = n + yield row + + +def as_logical_definitions( + ecs: List[ExtractedConcept], +) -> Iterator[obograph.LogicalDefinitionAxiom]: + """ + Given a list of ExtractedConcepts, return a list of LogicalDefinitionAxioms. + + :param ecs: + :return: + """ + for ec in ecs: + for instance in ec.instances.values(): + if instance.logical_definition is not None: + yield obograph.LogicalDefinitionAxiom( + definedClassId=instance.curie, + genusIds=[instance.logical_definition.genus], + restrictions=[ + obograph.ExistentialRestrictionExpression( + propertyId=r.predicate, fillerId=r.filler + ) + for r in instance.logical_definition.differentia + ], + ) + + +def load_pattern_collection(patterns_file: str): + """ + Load a pattern collection from a file. + + :param patterns_file: + :return: + """ + return LexicalPatternCollection(**yaml.safe_load(open(patterns_file))) diff --git a/src/oaklib/utilities/mapping/cross_ontology_diffs.py b/src/oaklib/utilities/mapping/cross_ontology_diffs.py index 0e582e4c2..3d0193d69 100644 --- a/src/oaklib/utilities/mapping/cross_ontology_diffs.py +++ b/src/oaklib/utilities/mapping/cross_ontology_diffs.py @@ -200,6 +200,8 @@ def calculate_pairwise_relational_diff( left_oi_entities = entities else: left_oi_entities = left_oi.entities() + # main diff calculation loop: + # iterate through all entities in left/subject ontology, treat as subject/child for subject_child in left_oi_entities: logging.info(f"Subject child: {subject_child}") if not curie_has_prefix(subject_child, sources): @@ -207,6 +209,7 @@ def calculate_pairwise_relational_diff( for pred, subject_parent in relation_dict_as_tuples( left_oi.outgoing_relationship_map(subject_child) ): + logging.debug(f"left edge: {subject_child} {pred} {subject_parent}") if entities is not None: if subject_child not in entities and subject_parent not in entities: continue diff --git a/src/oaklib/utilities/obograph_utils.py b/src/oaklib/utilities/obograph_utils.py index 001f4a8c5..37c775a8b 100644 --- a/src/oaklib/utilities/obograph_utils.py +++ b/src/oaklib/utilities/obograph_utils.py @@ -204,7 +204,10 @@ def as_multi_digraph( def as_digraph( - graph: Graph, reverse: bool = True, filter_reflexive: bool = True + graph: Graph, + reverse: bool = True, + filter_reflexive: bool = True, + predicates: Optional[List[PRED_CURIE]] = None, ) -> nx.MultiDiGraph: """ Convert to a networkx :class:`.DiGraph` @@ -213,8 +216,10 @@ def as_digraph( :param reverse: :return: """ - dg = nx.DiGraph() + dg = nx.MultiDiGraph() for edge in graph.edges: + if predicates is not None and edge.pred not in predicates: + continue if filter_reflexive and reflexive(edge): continue edge_attrs = {"predicate": edge.pred} @@ -231,7 +236,7 @@ def as_graph( filter_reflexive: bool = True, predicate_weights: PREDICATE_WEIGHT_MAP = None, default_weight=1.0, -) -> nx.MultiDiGraph: +) -> nx.Graph: """ Convert to a networkx :class:`.DiGraph` @@ -338,6 +343,26 @@ def shortest_paths( logging.info(f"No path between {start_curie} and {end_curie}") +def depth_first_ordering(graph: Graph) -> List[CURIE]: + """ + Return a depth-first ordering of the nodes in the graph. + + :param graph: + :return: + """ + six = index_graph_edges_by_subject(graph) + oix = index_graph_edges_by_object(graph) + stack = list(set(oix.keys()) - set(six.keys())) + visited = [] + while stack: + node = stack.pop() + visited.append(node) + for edge in oix[node]: + if edge.sub not in visited and edge.sub not in stack: + stack.append(edge.sub) + return visited + + def remove_nodes_from_graph(graph: Graph, node_ids: List[CURIE]): """ Remove the specified nodes from the graph, and cascade to any edges diff --git a/tests/test_implementations/__init__.py b/tests/test_implementations/__init__.py index b001e72cb..ecc0da4f3 100644 --- a/tests/test_implementations/__init__.py +++ b/tests/test_implementations/__init__.py @@ -64,6 +64,7 @@ ClassEnrichmentCalculationInterface, ) from oaklib.interfaces.differ_interface import DifferInterface +from oaklib.interfaces.dumper_interface import DumperInterface from oaklib.interfaces.merge_interface import MergeInterface from oaklib.interfaces.metadata_interface import MetadataInterface from oaklib.interfaces.obograph_interface import ( @@ -735,7 +736,7 @@ def _check(syns: List[obograph.SynonymPropertyValue]): syns = list(oi.synonym_property_values(NUCLEUS)) _check([syn[1] for syn in syns]) - def test_dump_obograph(self, oi: BasicOntologyInterface): + def test_dump_obograph(self, oi: DumperInterface): """ Tests conformance of dump method with obograph json syntax. @@ -972,6 +973,20 @@ def test_diff(self, oi: DifferInterface, oi_modified: DifferInterface): for typ, expected in cases: test.assertEqual(expected, residual[typ]) + def test_as_obograph(self, oi: OboGraphInterface): + """ + Tests as_obograph in OboGraphInterface + + :param oi: + :return: + """ + test = self.test + for expand in [False, True]: + g = oi.as_obograph(expand_curies=expand) + test.assertGreater(len(g.nodes), 0) + test.assertGreater(len(g.edges), 0) + test.assertGreater(len(g.logicalDefinitionAxioms), 0) + def test_subgraph_from_traversal(self, oi: OboGraphInterface): """ Tests subgraph_from_traversal in OboGraphInterface @@ -1027,8 +1042,8 @@ def test_subgraph_from_traversal(self, oi: OboGraphInterface): ) = case traversal = TraversalConfiguration(up_distance=up_dist, down_distance=down_dist) graph = oi.subgraph_from_traversal(seeds, predicates=predicates, traversal=traversal) - test.assertEqual(expected_num_nodes, len(graph.nodes)) - test.assertEqual(expected_num_edges, len(graph.edges)) + test.assertEqual(expected_num_nodes, len(graph.nodes), f"Failed for case: {case}") + test.assertEqual(expected_num_edges, len(graph.edges), f"Failed for case: {case}") node_ids = [n.id for n in graph.nodes] for node_id in expected_nodes_subset: test.assertIn(node_id, node_ids, f"Failed for case: {case}") diff --git a/tests/test_implementations/test_aggregator.py b/tests/test_implementations/test_aggregator.py index 5284a0283..03c37c130 100644 --- a/tests/test_implementations/test_aggregator.py +++ b/tests/test_implementations/test_aggregator.py @@ -19,6 +19,7 @@ TISSUE, VACUOLE, ) +from tests.test_implementations import ComplianceTester TEST_ONT = INPUT_DIR / "go-nucleus.obo" TEST_ONT2 = INPUT_DIR / "interneuron.obo" @@ -35,6 +36,7 @@ def setUp(self) -> None: oi1 = ProntoImplementation(resource1) oi2 = ProntoImplementation(resource2) self.oi = AggregatorImplementation(implementations=[oi1, oi2]) + self.compliance_tester = ComplianceTester(self) def test_relationships(self): oi = self.oi @@ -71,6 +73,9 @@ def test_metadata(self): assert "https://github.com/geneontology/go-ontology/issues/17776" in m["term_tracker_item"] def test_labels(self): + self.compliance_tester.test_labels(self.oi) + + def test_labels_extra(self): """ Tests labels can be retrieved, and no label is retrieved when a term does not exist :return: @@ -105,6 +110,13 @@ def test_synonyms(self): ["tissue", "simple tissue", "tissue portion", "portion of tissue"], ) + def test_definitions(self): + self.compliance_tester.test_definitions(self.oi, include_metadata=True) + + @unittest.skip("TODO") + def test_owl_types(self): + self.compliance_tester.test_owl_types(self.oi, skip_oio=True) + def test_subsets(self): oi = self.oi subsets = list(oi.subsets()) diff --git a/tests/test_implementations/test_pronto.py b/tests/test_implementations/test_pronto.py index 384d520cc..b89b30377 100644 --- a/tests/test_implementations/test_pronto.py +++ b/tests/test_implementations/test_pronto.py @@ -251,7 +251,7 @@ def test_subontology(self): ) def test_definitions(self): - self.compliance_tester.test_definitions(self.oi) + self.compliance_tester.test_definitions(self.oi, include_metadata=True) def test_store_associations(self): self.compliance_tester.test_store_associations(self.oi) @@ -336,6 +336,9 @@ def test_entailed_edges(self): def test_subgraph_from_traversal(self): self.compliance_tester.test_subgraph_from_traversal(self.oi) + def test_as_obograph(self): + self.compliance_tester.test_as_obograph(self.oi) + def test_save_extract(self): g = self.oi.ancestor_graph(VACUOLE) oi = ProntoImplementation() diff --git a/tests/test_implementations/test_simple_obo.py b/tests/test_implementations/test_simple_obo.py index aecedf266..3fa7315c9 100644 --- a/tests/test_implementations/test_simple_obo.py +++ b/tests/test_implementations/test_simple_obo.py @@ -304,6 +304,13 @@ def test_obograph(self): def test_extract_graph(self): self.compliance_tester.test_extract_graph(self.oi, test_metadata=False) # TODO + @unittest.skip("TODO") + def test_subgraph_from_traversal(self): + self.compliance_tester.test_subgraph_from_traversal(self.oi) + + def test_as_obograph(self): + self.compliance_tester.test_as_obograph(self.oi) + def test_ancestors_descendants(self): self.compliance_tester.test_ancestors_descendants(self.oi) diff --git a/tests/test_utilities/test_logical_definition_summarizer.py b/tests/test_utilities/test_logical_definition_summarizer.py new file mode 100644 index 000000000..0c91a2bb0 --- /dev/null +++ b/tests/test_utilities/test_logical_definition_summarizer.py @@ -0,0 +1,104 @@ +import unittest + +from oaklib.implementations.pronto.pronto_implementation import ProntoImplementation +from oaklib.resource import OntologyResource +from oaklib.utilities.axioms.logical_definition_summarizer import ( + logical_definitions_to_matrix, + parse_axes_to_config, +) +from tests import INPUT_DIR + +TEST_ONT = INPUT_DIR / "go-nucleus.obo" + +CASES = [ + ( + "d,f", + [ + { + "defined_class": ["GO:0009893"], + "genus": ["GO:0065007"], + "metabolic_process": ["RO:0002213"], + } + ], + ), + ("d,g", [{"defined_class": ["GO:0009893"], "biological_regulation": ["GO:0009893"]}]), + ( + "d,p", + [ + { + "defined_class": ["GO:0009893"], + "genus": ["GO:0065007"], + "positively_regulates": ["GO:0008152"], + } + ], + ), + ( + "f,g", + [ + { + "filler": ["GO:0008152"], + "biological_regulation": ["GO:0009892", "GO:0009893", "GO:0019222"], + } + ], + ), + ( + "f,p", + [ + { + "filler": ["GO:0008152"], + "positively_regulates": ["GO:0009893"], + "negatively_regulates": ["GO:0009892"], + "regulates": ["GO:0019222"], + } + ], + ), + ( + "f", + [ + { + "filler": ["GO:0008152"], + "positively_regulates": ["GO:0009893"], + "negatively_regulates": ["GO:0009892"], + "regulates": ["GO:0019222"], + } + ], + ), + # use organelle + ( + "g,f", + [ + { + "genus": ["GO:0043226"], + "membrane": ["GO:0043227"], + "intracellular_anatomical_structure": ["GO:0043229"], + } + ], + ), + ( + "g,p", + [ + {"genus": ["GO:0043226"], "has_part": ["GO:0043227"], "part_of": ["GO:0043229"]}, + {"genus": ["GO:0016020"], "part_of": ["GO:0031090", "GO:0031965", "GO:0098590"]}, + ], + ), +] + + +class TestLogicalDefinitionSummarizer(unittest.TestCase): + def setUp(self) -> None: + resource = OntologyResource(slug="go-nucleus.obo", directory=INPUT_DIR, local=True) + oi = ProntoImplementation(resource) + self.oi = oi + self.ldefs = list(oi.logical_definitions(oi.entities())) + + def test_summarizer(self): + ldefs = self.ldefs + for case in CASES: + (cfg_str, expected) = case + cfg = parse_axes_to_config(cfg_str) + rows = logical_definitions_to_matrix(self.oi, ldefs, cfg) + for row in rows: + slim_row = {k: v for k, v in row.items() if v and v != [""]} + if slim_row in expected: + expected.remove(slim_row) + self.assertEqual([], expected, f"Expected rows not found in output in {case}") diff --git a/tests/test_utilities/test_obograph_utils.py b/tests/test_utilities/test_obograph_utils.py index 5f9795ea3..235244976 100644 --- a/tests/test_utilities/test_obograph_utils.py +++ b/tests/test_utilities/test_obograph_utils.py @@ -12,6 +12,7 @@ from oaklib.utilities.obograph_utils import ( as_multi_digraph, compress_all_graph_ids, + depth_first_ordering, expand_all_graph_ids, filter_by_predicates, graph_as_dict, @@ -23,6 +24,7 @@ ) from tests import ( CELLULAR_ANATOMICAL_ENTITY, + CELLULAR_COMPONENT, CELLULAR_ORGANISMS, CYTOPLASM, HUMAN, @@ -31,6 +33,7 @@ INTRACELLULAR, NUCLEAR_MEMBRANE, NUCLEUS, + ORGANELLE, OUTPUT_DIR, VACUOLE, ) @@ -183,3 +186,19 @@ def test_shortest_paths(self): self.assertIn(x, path) for x in excludes: self.assertNotIn(x, path) + + def test_depth_first_ordering(self): + oi = self.oi + graph = oi.descendant_graph([CELLULAR_COMPONENT], predicates=[IS_A, PART_OF]) + ordered = depth_first_ordering(graph) + self.assertEqual(ordered[0], CELLULAR_COMPONENT) + expected_order = [ + (CELLULAR_COMPONENT, CELLULAR_ANATOMICAL_ENTITY), + (CELLULAR_ANATOMICAL_ENTITY, ORGANELLE), + (ORGANELLE, NUCLEUS), + # (CYTOPLASM, NUCLEUS), + (IMBO, NUCLEUS), + (NUCLEUS, NUCLEAR_MEMBRANE), + ] + for parent, child in expected_order: + self.assertLess(ordered.index(parent), ordered.index(child), f"{parent} -> {child}") diff --git a/tests/test_utilities/test_patternizer.py b/tests/test_utilities/test_patternizer.py new file mode 100644 index 000000000..a377a08b2 --- /dev/null +++ b/tests/test_utilities/test_patternizer.py @@ -0,0 +1,95 @@ +import unittest + +import yaml + +from oaklib.datamodels.vocabulary import ( + NEGATIVELY_REGULATES, + PART_OF, + POSITIVELY_REGULATES, +) +from oaklib.implementations.pronto.pronto_implementation import ProntoImplementation +from oaklib.resource import OntologyResource +from oaklib.utilities.lexical.patternizer import ( + Differentia, + LexicalPattern, + LexicalPatternCollection, + LogicalDefinition, + Term, + lexical_pattern_instances, + load_pattern_collection, +) +from tests import INPUT_DIR, MEMBRANE, NUCLEAR_MEMBRANE, NUCLEUS, OUTPUT_DIR + +TEST_ONT = INPUT_DIR / "go-nucleus.obo" +TEST_PATTERNS_OUT = OUTPUT_DIR / "go-patterns.yaml" + +PATTERNS = [ + LexicalPattern( + name="nucleus", + pattern="nuclear", + description="A nuclear X is an X that is part of the nucleus.", + curie=NUCLEUS, + curie_is_genus=False, + differentia_predicate=PART_OF, + ), + LexicalPattern( + name="negative regulation", + pattern="negative regulation of", + curie="GO:0065007", + differentia_predicate=NEGATIVELY_REGULATES, + ), + LexicalPattern( + name="positive regulation", + pattern="positive regulation of", + curie="GO:0065007", + differentia_predicate=POSITIVELY_REGULATES, + ), +] + + +class TestPatternizer(unittest.TestCase): + def setUp(self) -> None: + resource = OntologyResource(slug="go-nucleus.obo", directory=INPUT_DIR, local=True) + oi = ProntoImplementation(resource) + self.oi = oi + self.pattern_collection = LexicalPatternCollection(patterns=PATTERNS) + + def test_patternizer(self): + """Test that the patternizer works by extracting nucleus and regulation concepts.""" + expected = [ + Term( + curie=NUCLEAR_MEMBRANE, + label="nuclear membrane", + logical_definition=LogicalDefinition( + genus=MEMBRANE, differentia=[Differentia(predicate=PART_OF, filler=NUCLEUS)] + ), + pattern="nucleus", + ), + Term( + curie="GO:0009892", + label="negative regulation of metabolic process", + logical_definition=LogicalDefinition( + genus="GO:0065007", + differentia=[Differentia(predicate=NEGATIVELY_REGULATES, filler="GO:0008152")], + ), + pattern="negative regulation", + ), + ] + for new_concept_prefix in [None, "TEST"]: + todo = [yaml.dump(ec.dict()) for ec in expected] + ecs = lexical_pattern_instances( + self.oi, PATTERNS, new_concept_prefix=new_concept_prefix + ) + for ec in ecs: + print(yaml.dump(ec.dict())) + for inst in ec.instances.values(): + inst_yaml = yaml.dump(inst.dict()) + if inst_yaml in todo: + todo.remove(inst_yaml) + self.assertEqual(todo, []) + + def test_write_patterns(self): + """Test that the patternizer works by extracting nucleus and regulation concepts.""" + with open(TEST_PATTERNS_OUT, "w") as outf: + yaml.dump(self.pattern_collection.dict(), outf) + load_pattern_collection(TEST_PATTERNS_OUT)