diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml new file mode 100644 index 00000000..3897b183 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -0,0 +1,33 @@ +name: Report a bug +description: Describe any wrong or unexpected behavior of mOWL +labels: [ "bug" ] +body: + - type: textarea + id: description + attributes: + label: Describe the bug + description: Please provide a clear and concise description of the unexpected or erroneous behavior in mOWL that you experienced and what you expected to happen instead. + validations: + required: true + - type: textarea + id: reproduction + attributes: + label: How to reproduce + description: Please provide a example of the code that you ran to encounter the bug. This is a crucial aspect in order to find the source of error. + validations: + required: true + - type: textarea + id: environment + attributes: + label: Environment + description: Please, describe the OS, the Python version, mOWL version and JDK version where you ran the code. + validations: + required: true + - type: textarea + id: extras + attributes: + label: Additional information + description: Add any other information about the problem here. + validations: + required: false + diff --git a/.github/ISSUE_TEMPLATE/model.yml b/.github/ISSUE_TEMPLATE/model.yml new file mode 100644 index 00000000..bef830db --- /dev/null +++ b/.github/ISSUE_TEMPLATE/model.yml @@ -0,0 +1,45 @@ +name: Add a new model +description: Suggest a new model for mOWL +labels: [ "model" ] +body: + - type: dropdown + id: models + attributes: + label: mOWL support different types of models. Select which type fits better for your suggestion. + multiple: false + options: + - Ontology graph projection + - Random walker + - Syntactic model + - Model-theoretic model + - Other + - type: input + id: arxiv + attributes: + label: Publication Link + description: Link to the academic text of record where the model was first proposed (e.g., from _arXiv_) + placeholder: e.g., https://arxiv.org/abs/1902.10499 + validations: + required: true + - type: input + id: reference_implementation + attributes: + label: Reference Implementation + description: Link to the publicly available reference implementation, preferrably from the authors of the publication + placeholder: e.g., https://github.com/bio-ontology-research-group/el-embeddings + validations: + required: false + - type: textarea + id: additional_implementations + attributes: + label: Additional Implementations + description: Links to any additional publicly available implementations + validations: + required: false + - type: textarea + id: context + attributes: + label: Additional Information + description: Additional information related to this model. + validations: + required: false diff --git a/.gitignore b/.gitignore index ae0c3c5a..2b680efd 100644 --- a/.gitignore +++ b/.gitignore @@ -39,4 +39,6 @@ dist/ .coverage *.pickle *.zip -*.log \ No newline at end of file +*.log + +docs/source/api/mowl.* \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 49e5c774..4b5964f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed ### Security +## [0.3.0] +### Added +- Implemented `CategoricalProjector` based on [https://arxiv.org/abs/2305.07163](https://arxiv.org/abs/2305.07163). ([#59][i59]) + +### Removed +- Removed redundant class `based_models.EmbeddingModel` + + ## [0.2.1] ### Fixed Fixed issue related to importing graph-based models due to missing `__init__.py` files. ([#60][i60]) @@ -78,7 +86,9 @@ Fixed issue related to importing graph-based models due to missing `__init__.py` - Walking methods accept optional `outfile` parameter and corpus extraction methods do not append by default. - Documentation updated and fixed some typos. -[unreleased]: https://github.com/bio-ontology-research-group/mowl/compare/v0.2.0...HEAD +[unreleased]: https://github.com/bio-ontology-research-group/mowl/compare/v0.3.0...HEAD +[0.3.0]: https://github.com/bio-ontology-research-group/mowl/releases/tag/v0.3.0 +[0.2.1]: https://github.com/bio-ontology-research-group/mowl/releases/tag/v0.2.1 [0.2.0]: https://github.com/bio-ontology-research-group/mowl/releases/tag/v0.2.0 [0.1.1]: https://github.com/bio-ontology-research-group/mowl/releases/tag/v0.1.1 [0.1.0]: https://github.com/bio-ontology-research-group/mowl/releases/tag/v0.1.0 @@ -89,4 +99,6 @@ Fixed issue related to importing graph-based models due to missing `__init__.py` [i36]: https://github.com/bio-ontology-research-group/mowl/issues/36 [i42]: https://github.com/bio-ontology-research-group/mowl/issues/42 [i43]: https://github.com/bio-ontology-research-group/mowl/issues/43 -[i43]: https://github.com/bio-ontology-research-group/mowl/issues/60 +[i59]: https://github.com/bio-ontology-research-group/mowl/issues/59 +[i60]: https://github.com/bio-ontology-research-group/mowl/issues/60 + diff --git a/README.md b/README.md index f14877a6..3a3a1994 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ mainly in Python, but we have integrated the functionalities of [OWLAPI](https:/ ### System dependencies - - JDK version 17 + - JDK version >= 11 - Python version: 3.8, 3.9, 3.10, 3.11 - Conda version >= 4.x.x diff --git a/docs/environment.yml b/docs/environment.yml index d00fb74c..af4458e0 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -4,13 +4,9 @@ dependencies: - python=3.8 - click - deprecated - - gensim + - gensim>=4.3.0 - gradle - - JPype1=1.3.0 - - matplotlib - - networkx - - numpy - - openjdk + - JPype1=1.4.1 - pandas - pytorch - pyyaml @@ -21,10 +17,14 @@ dependencies: - urllib3 - pip - pip: - - pykeen==1.9.0 + - pykeen>=1.10.1 - temp - deprecated - nbsphinx - sphinx-gallery + - sphinx-rtd-theme - ipython - - memory-profiler \ No newline at end of file + - memory-profiler + - matplotlib + - sphinx-automodapi + - mock \ No newline at end of file diff --git a/docs/source/api/base_models/base.rst b/docs/source/api/base_models/base.rst deleted file mode 100644 index 33b1c970..00000000 --- a/docs/source/api/base_models/base.rst +++ /dev/null @@ -1,6 +0,0 @@ -Base model -============= - -.. automodule:: mowl.base_models.model - :members: - :show-inheritance: diff --git a/docs/source/api/base_models/base_el.rst b/docs/source/api/base_models/base_el.rst deleted file mode 100644 index 32f84f40..00000000 --- a/docs/source/api/base_models/base_el.rst +++ /dev/null @@ -1,7 +0,0 @@ -Base :math:`\mathcal{EL}` model -================================= -.. automodule:: mowl.base_models.elmodel - :members: - :show-inheritance: - - diff --git a/docs/source/api/base_models/base_graph.rst b/docs/source/api/base_models/base_graph.rst deleted file mode 100644 index 01f2376e..00000000 --- a/docs/source/api/base_models/base_graph.rst +++ /dev/null @@ -1,5 +0,0 @@ -Base graph models -================================= -.. automodule:: mowl.base_models.graph_model - :members: - :show-inheritance: diff --git a/docs/source/api/base_models/base_syntactic.rst b/docs/source/api/base_models/base_syntactic.rst deleted file mode 100644 index 91b151a5..00000000 --- a/docs/source/api/base_models/base_syntactic.rst +++ /dev/null @@ -1,5 +0,0 @@ -Base syntactic model -================================= -.. automodule:: mowl.base_models.syntactic_model - :members: - :show-inheritance: diff --git a/docs/source/api/base_models/index.rst b/docs/source/api/base_models/index.rst index 0b4cdbb3..14031d37 100644 --- a/docs/source/api/base_models/index.rst +++ b/docs/source/api/base_models/index.rst @@ -1,20 +1,9 @@ mowl.base_models ===================== -* :doc:`base` -* :doc:`base_graph` -* :doc:`base_syntactic` -* :doc:`base_el` - +.. automodapi:: mowl.base_models + :no-heading: + :headings: -- -.. toctree:: - :maxdepth: 0 - :hidden: - :glob: - - base - base_graph - base_syntactic - base_el diff --git a/docs/source/api/datasets/index.rst b/docs/source/api/datasets/index.rst index bddb7982..0761ad36 100644 --- a/docs/source/api/datasets/index.rst +++ b/docs/source/api/datasets/index.rst @@ -1,22 +1,20 @@ mowl.datasets ===================== -Base dataset -------------------------- +Dataset constructors +---------------------- -.. automodule:: mowl.datasets.base - :members: - :show-inheritance: +.. automodapi:: mowl.datasets + :no-heading: + :headings: -- + +| +| Built-in datasets ------------------- -.. automodule:: mowl.datasets.builtin - :members: - :show-inheritance: -Dataset for :math:`\mathcal{EL}` language ------------------------------------------- -.. automodule:: mowl.datasets.el - :members: - :show-inheritance: +.. automodapi:: mowl.datasets.builtin + :no-heading: + :headings: -- diff --git a/docs/source/api/evaluation/index.rst b/docs/source/api/evaluation/index.rst index ce2f9af4..b7552c1a 100644 --- a/docs/source/api/evaluation/index.rst +++ b/docs/source/api/evaluation/index.rst @@ -1,13 +1,6 @@ mowl.evaluation ============================ -.. automodule:: mowl.evaluation.base - :members: - :show-inheritance: - - - -.. automodule:: mowl.evaluation.rank_based - :members: - :show-inheritance: - +.. automodapi:: mowl.evaluation + :no-heading: + :headings: -- diff --git a/docs/source/api/projection/dl2vec.rst b/docs/source/api/extra/dl2vec.rst similarity index 62% rename from docs/source/api/projection/dl2vec.rst rename to docs/source/api/extra/dl2vec.rst index bb92017e..2a1c6272 100644 --- a/docs/source/api/projection/dl2vec.rst +++ b/docs/source/api/extra/dl2vec.rst @@ -1,5 +1,4 @@ - -The DL2Vec graph follows the rules described in the paper `Predicting candidate genes from phenotypes, functions, and anatomical site of expression (2020) `__. The parsing rules are shown in the table below: +The parsing rules are shown in the table below: +-------------------------------------------------------+---------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------------+ @@ -14,3 +13,17 @@ The DL2Vec graph follows the rules described in the paper `Predicting candidate | :math:`A \equiv B` | | :math:`\left\langle A, EquivalentTo, B \right\rangle` | +-------------------------------------------------------+---------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------------+ +Initially, DL2Vec projection rules are intended to parse TBox axioms. However, for some cases, useful information might be present as ABox axioms of the form :math:`C(a)`, :math:`\exists R.C (a)` and :math:`R(a,b)` where :math:`C` is an atomic concept, :math:`R` is a role and :math:`a, b` are individuals. The extended rules are the following: + + ++---------------------------+-----------------------------------------------------------+ +| Condition | Triple | ++===========================+===========================================================+ +| :math:`C(a)` | :math:`\left\langle a, http://type, C \right\rangle` | ++---------------------------+-----------------------------------------------------------+ +| :math:`\exists R.C (a)` | :math:`\left\langle a, R, C \right\rangle` | ++---------------------------+-----------------------------------------------------------+ +| :math:`R(a,b)` | :math:`\left\langle a, R, b \right\rangle` | ++---------------------------+-----------------------------------------------------------+ + + diff --git a/docs/source/api/projection/owl2vec.rst b/docs/source/api/extra/owl2vec.rst similarity index 100% rename from docs/source/api/projection/owl2vec.rst rename to docs/source/api/extra/owl2vec.rst diff --git a/docs/source/api/models/elboxembeddings.rst b/docs/source/api/models/elboxembeddings.rst deleted file mode 100644 index c0ccf25c..00000000 --- a/docs/source/api/models/elboxembeddings.rst +++ /dev/null @@ -1,24 +0,0 @@ -ELBoxEmbeddings -================= - -ELBoxEmbeddings ------------------------------- - -.. automodule:: mowl.models.elboxembeddings.model - :members: - :show-inheritance: - -Protein-Protein interaction example ---------------------------------------- - -.. automodule:: mowl.models.elboxembeddings.examples.model_ppi - :members: - :show-inheritance: - - -Gene-Disease association example ------------------------------------- - -.. automodule:: mowl.models.elboxembeddings.examples.model_gda - :members: - :show-inheritance: diff --git a/docs/source/api/models/elembeddings.rst b/docs/source/api/models/elembeddings.rst deleted file mode 100644 index 150c7ae3..00000000 --- a/docs/source/api/models/elembeddings.rst +++ /dev/null @@ -1,25 +0,0 @@ -EL-Embeddings -=============== - - -EL-Embeddings ------------------------------- - -.. automodule:: mowl.models.elembeddings.model - :members: - :show-inheritance: - -Protein-Protein interaction example ---------------------------------------- - -.. automodule:: mowl.models.elembeddings.examples.model_ppi - :members: - :show-inheritance: - - -Gene-Disease association example ------------------------------------- - -.. automodule:: mowl.models.elembeddings.examples.model_gda - :members: - :show-inheritance: diff --git a/docs/source/api/models/graphs.rst b/docs/source/api/models/graphs.rst deleted file mode 100644 index 409cd8fb..00000000 --- a/docs/source/api/models/graphs.rst +++ /dev/null @@ -1,19 +0,0 @@ -Graph-based models -======================= - -Graphs, random walks and Word2Vec ----------------------------------- - -.. automodule:: mowl.models.graph_random_walk.random_walk_w2v_model - :members: - :show-inheritance: - - - -Graphs and KGE methods ----------------------------- - -.. automodule:: mowl.models.graph_kge.graph_pykeen_model - :members: - :show-inheritance: - diff --git a/docs/source/api/models/index.rst b/docs/source/api/models/index.rst index 9e326568..35b56671 100644 --- a/docs/source/api/models/index.rst +++ b/docs/source/api/models/index.rst @@ -1,19 +1,6 @@ mowl.models ============ -* :doc:`graphs` -* :doc:`syntactic` -* :doc:`elembeddings` -* :doc:`elboxembeddings` - - -.. toctree:: - :maxdepth: 0 - :hidden: - :glob: - - graphs - syntactic - elembeddings - elboxembeddings - +.. automodapi:: mowl.models + :no-heading: + :headings: -- diff --git a/docs/source/api/models/syntactic.rst b/docs/source/api/models/syntactic.rst deleted file mode 100644 index 1106551a..00000000 --- a/docs/source/api/models/syntactic.rst +++ /dev/null @@ -1,10 +0,0 @@ -Syntactic models -================== - -Syntactic plus Word2Vec ------------------------------- - -.. automodule:: mowl.models.syntactic.w2v_model - :members: - :show-inheritance: - diff --git a/docs/source/api/nn/index.rst b/docs/source/api/nn/index.rst index 617173f2..811c7bf3 100644 --- a/docs/source/api/nn/index.rst +++ b/docs/source/api/nn/index.rst @@ -1,34 +1,7 @@ mowl.nn ===================== -EL Module ------------------------------- +.. automodapi:: mowl.nn + :no-heading: + :headings: -- -.. automodule:: mowl.nn.el.elmodule - :members: - :show-inheritance: - - - -EL-Embeddings ------------------------------- - -.. automodule:: mowl.nn.el.elem.module - :members: - :show-inheritance: - - -ELBoxEmbeddings ------------------------------- - -.. automodule:: mowl.nn.el.elbox.module - :members: - :show-inheritance: - - -Box :math:`^2` EL ------------------------------- - -.. automodule:: mowl.nn.el.boxsquaredel.module - :members: - :show-inheritance: diff --git a/docs/source/api/projection/index.rst b/docs/source/api/projection/index.rst index 828607ba..9d18c3ed 100644 --- a/docs/source/api/projection/index.rst +++ b/docs/source/api/projection/index.rst @@ -1,71 +1,10 @@ mowl.projection ============================= -In this module we provide different methods for projecting an ontology into a graph: The methods we provide are: +In this module we provide different methods for projecting an ontology into a graph. -Ontology Projection ------------------------------------- - -.. automodule:: mowl.projection.base - :members: - :show-inheritance: - - -All the methods will return a list of edges corresponding to the **Edge** class: - -Edge ------ - -.. automodule:: mowl.projection.edge - :private-members: - :members: - :show-inheritance: - - -Subclass Hierarchy ---------------------- - -.. automodule:: mowl.projection.taxonomy.model - :members: - :no-inherited-members: - :show-inheritance: - - -Subclass Hierarchy With Relations ------------------------------------- - -.. automodule:: mowl.projection.taxonomy_rels.model - :members: - :show-inheritance: - - -DL2Vec Graph -------------- - -.. include:: dl2vec.rst - - - - -.. automodule:: mowl.projection.dl2vec.model - :members: - :show-inheritance: - - -OWL2Vec* Graph ----------------- - -The OWL2Vec* graph follows the rules described in the paper `OWL2Vec*: embedding of OWL ontologies (2021) `__. The parsing rules are shown in the table below: - - -.. include:: owl2vec.rst - - - - -.. automodule:: mowl.projection.owl2vec_star.model - :members: - :show-inheritance: - +.. automodapi:: mowl.projection + :no-heading: + :headings: -- diff --git a/docs/source/api/walking/index.rst b/docs/source/api/walking/index.rst index c3706f0c..ff03391b 100644 --- a/docs/source/api/walking/index.rst +++ b/docs/source/api/walking/index.rst @@ -1,23 +1,13 @@ mowl.walking ====================== -Walking ---------- +In this module we provide different methods for generating random walks given a graph. +The algorithms in mOWL are a variation from the original ones. Graphs obtained from ontologies always have labeled edges, therefore the **edge labels are included** in the random walks. -.. automodule:: mowl.walking.walking - :members: - :show-inheritance: +.. important:: + Random walks with size :math:`n` will include :math:`n` nodes with its edges (except in the last node). Therefore a random walk with size :math:`n` will be at most :math:`2n-1` long. -DeepWalk ----------- +.. automodapi:: mowl.walking + :no-heading: + :headings: -- -.. automodule:: mowl.walking.deepwalk.model - :members: - :show-inheritance: - -Node2Vec ----------- - -.. automodule:: mowl.walking.node2vec.model - :members: - :show-inheritance: diff --git a/docs/source/appendix/references.rst b/docs/source/appendix/references.rst new file mode 100644 index 00000000..2376ae9c --- /dev/null +++ b/docs/source/appendix/references.rst @@ -0,0 +1,18 @@ +References +================ + +.. [kulmanov2019] Kulmanov, M., Liu-Wei, W., Yan, Y., & Hoehndorf, R. (2019). `EL Embeddings: Geometric construction of models for the Description Logic EL ++ `_. ArXiv, abs/1902.10499. + +.. [chen2020] Jun Chen, Azza Althagafi, Robert Hoehndorf, `Predicting candidate genes from phenotypes, functions and anatomical site of expression `_, Bioinformatics, Volume 37, Issue 6, March 2021, Pages 853–860 + +.. [chen2020b] Chen, J., Hu, P., Jiménez-Ruiz, E., Holter, O., Antonyrajah, D., & Horrocks, I. (2020). `OWL2Vec*: embedding of OWL ontologies `_. Machine Learning, 110, 1813 - 1845. + +.. [zhapa2023] Zhapa-Camacho, F., & Hoehndorf, R. (2023). `CatE: Graph-Based Embeddings of ALC Ontologies Using Category-Theoretical Diagrams `_. ArXiv, abs/2305.07163. + +.. [perozzi2014] Perozzi, B., Al-Rfou, R., & Skiena, S.S. (2014). `DeepWalk: online learning of social representations `_. Proceedings of the 20th ACM SIGKDD international conference on Knowledge discovery and data mining. + +.. [grover2016] Grover, A., & Leskovec, J. (2016). `node2vec: Scalable Feature Learning for Networks `_. Proceedings of the 22nd ACM SIGKDD International Conference on Knowledge Discovery and Data Mining. + +.. [peng2020] Peng, X., Tang, Z., Kulmanov, M., Niu, K., & Hoehndorf, R. (2022). `Description Logic EL++ Embeddings with Intersectional Closure `_. ArXiv, abs/2202.14018. + +.. [jackermeier2023] Jackermeier, M., Chen, J., & Horrocks, I. (2023). `Box2EL: Concept and Role Box Embeddings for the Description Logic EL++ `_. ArXiv, abs/2301.11118. diff --git a/docs/source/conf.py b/docs/source/conf.py index b0dce44d..355ffa51 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -18,11 +18,11 @@ # -- Project information project = 'MOWL' -copyright = '2023, BORG' +copyright = '2023, Bio-Ontology Research Group' author = 'BORG' -release = '0.2.0' -version = '0.2.0' +release = '0.3.0' +version = '0.3.0' # -- General configuration extensions = [ @@ -36,6 +36,7 @@ 'sphinx.ext.todo', 'sphinx.ext.viewcode', 'sphinx_gallery.gen_gallery', + 'sphinx_automodapi.automodapi', # 'IPython.sphinxext.ipython_console_highlighting' # Matplotlib diff --git a/docs/source/datasets/index.rst b/docs/source/datasets/index.rst index 8e329c74..87cb94e3 100644 --- a/docs/source/datasets/index.rst +++ b/docs/source/datasets/index.rst @@ -21,15 +21,7 @@ mOWL is designed to handle input in OWL format. That is, you can input OWL ontol Built-in datasets ------------------- -There are several built-in datasets related to bioinformatics tasks such as protein-protein interactions prediction and gene-disease association prediction. This datasets are: - -* :class:`FamilyDataset ` -* :class:`PPIYeastDataset ` -* :class:`PPIYeastSlimDataset ` -* :class:`GDAHumanDataset ` -* :class:`GDAMouseDataset ` -* :class:`GDAHumanELDataset ` -* :class:`GDAMouseELDataset ` +There are several built-in datasets related to bioinformatics tasks such as protein-protein interactions prediction and gene-disease association prediction. Datasets can be found at :doc:`Datasets API docs <../../api/datasets/index>`. To access any of these datasets you can use: diff --git a/docs/source/examples/elmodels/index.rst b/docs/source/examples/elmodels/index.rst index cc36c9df..5e73fdef 100644 --- a/docs/source/examples/elmodels/index.rst +++ b/docs/source/examples/elmodels/index.rst @@ -14,12 +14,12 @@ Models embeddings the :math:`\mathcal{EL}` language .. raw:: html -
+
.. only:: html .. image:: /examples/elmodels/images/thumb/sphx_glr_plot_1_elembeddings_thumb.png - :alt: EL Embeddings + :alt: :ref:`sphx_glr_examples_elmodels_plot_1_elembeddings.py` @@ -31,12 +31,12 @@ Models embeddings the :math:`\mathcal{EL}` language .. raw:: html -
+
.. only:: html .. image:: /examples/elmodels/images/thumb/sphx_glr_plot_2_elboxembeddings_thumb.png - :alt: ELBoxEmbeddings + :alt: :ref:`sphx_glr_examples_elmodels_plot_2_elboxembeddings.py` diff --git a/docs/source/examples/graph_based/index.rst b/docs/source/examples/graph_based/index.rst index 353a0b90..3d9e69eb 100644 --- a/docs/source/examples/graph_based/index.rst +++ b/docs/source/examples/graph_based/index.rst @@ -14,12 +14,12 @@ Graph-based models .. raw:: html -
+
.. only:: html .. image:: /examples/graph_based/images/thumb/sphx_glr_plot_1_dl2vec_thumb.png - :alt: DL2Vec + :alt: :ref:`sphx_glr_examples_graph_based_plot_1_dl2vec.py` diff --git a/docs/source/examples/index.rst b/docs/source/examples/index.rst index e895b083..6adc9731 100644 --- a/docs/source/examples/index.rst +++ b/docs/source/examples/index.rst @@ -31,12 +31,12 @@ Models embeddings the :math:`\mathcal{EL}` language .. raw:: html -
+
.. only:: html .. image:: /examples/elmodels/images/thumb/sphx_glr_plot_1_elembeddings_thumb.png - :alt: EL Embeddings + :alt: :ref:`sphx_glr_examples_elmodels_plot_1_elembeddings.py` @@ -48,12 +48,12 @@ Models embeddings the :math:`\mathcal{EL}` language .. raw:: html -
+
.. only:: html .. image:: /examples/elmodels/images/thumb/sphx_glr_plot_2_elboxembeddings_thumb.png - :alt: ELBoxEmbeddings + :alt: :ref:`sphx_glr_examples_elmodels_plot_2_elboxembeddings.py` @@ -79,12 +79,12 @@ Graph-based models .. raw:: html -
+
.. only:: html .. image:: /examples/graph_based/images/thumb/sphx_glr_plot_1_dl2vec_thumb.png - :alt: DL2Vec + :alt: :ref:`sphx_glr_examples_graph_based_plot_1_dl2vec.py` @@ -110,12 +110,12 @@ Syntactic models .. raw:: html -
+
.. only:: html .. image:: /examples/syntactic/images/thumb/sphx_glr_plot_1_onto2vec_thumb.png - :alt: Onto2Vec + :alt: :ref:`sphx_glr_examples_syntactic_plot_1_onto2vec.py` @@ -127,12 +127,12 @@ Syntactic models .. raw:: html -
+
.. only:: html .. image:: /examples/syntactic/images/thumb/sphx_glr_plot_2_opa2vec_thumb.png - :alt: OPA2Vec + :alt: :ref:`sphx_glr_examples_syntactic_plot_2_opa2vec.py` @@ -158,12 +158,12 @@ Zero-shot learning based models .. raw:: html -
+
.. only:: html .. image:: /examples/zsl/images/thumb/sphx_glr_plot_1_deepgozero_thumb.png - :alt: DeepGOZero + :alt: :ref:`sphx_glr_examples_zsl_plot_1_deepgozero.py` @@ -182,6 +182,7 @@ Zero-shot learning based models :hidden: :includehidden: + /examples//elmodels/index.rst /examples//graph_based/index.rst /examples//syntactic/index.rst diff --git a/docs/source/examples/syntactic/index.rst b/docs/source/examples/syntactic/index.rst index db6d975d..b61eb5ce 100644 --- a/docs/source/examples/syntactic/index.rst +++ b/docs/source/examples/syntactic/index.rst @@ -14,12 +14,12 @@ Syntactic models .. raw:: html -
+
.. only:: html .. image:: /examples/syntactic/images/thumb/sphx_glr_plot_1_onto2vec_thumb.png - :alt: Onto2Vec + :alt: :ref:`sphx_glr_examples_syntactic_plot_1_onto2vec.py` @@ -31,12 +31,12 @@ Syntactic models .. raw:: html -
+
.. only:: html .. image:: /examples/syntactic/images/thumb/sphx_glr_plot_2_opa2vec_thumb.png - :alt: OPA2Vec + :alt: :ref:`sphx_glr_examples_syntactic_plot_2_opa2vec.py` diff --git a/docs/source/examples/zsl/index.rst b/docs/source/examples/zsl/index.rst index e58e8bc5..ae1aad6d 100644 --- a/docs/source/examples/zsl/index.rst +++ b/docs/source/examples/zsl/index.rst @@ -14,12 +14,12 @@ Zero-shot learning based models .. raw:: html -
+
.. only:: html .. image:: /examples/zsl/images/thumb/sphx_glr_plot_1_deepgozero_thumb.png - :alt: DeepGOZero + :alt: :ref:`sphx_glr_examples_zsl_plot_1_deepgozero.py` diff --git a/docs/source/graphs/projection.rst b/docs/source/graphs/projection.rst index 9e998bd2..8b452a1a 100644 --- a/docs/source/graphs/projection.rst +++ b/docs/source/graphs/projection.rst @@ -1,140 +1,27 @@ -Projecting ontologies -======================= +Projecting ontologies into graphs +================================== Ontologies contain adjacency information that can be projected into a graph. There are different ways of generating such graphs: -* :class:`Taxonomy ` -* :class:`Taxonomy with relations ` -* :class:`DL2Vec ` -* :class:`OWL2Vec* ` - -Each method follow different projection rules. In the case of ``Taxonomy``, only axioms of the form :math:`C \sqsubseteq D` will be considered (:math:`C,D` are atomic concepts) and each of them will form a graph edge ``(C, subclassOf, D)``. ``Taxonomy with relations`` is an extension of the previous one that also adds axioms of the form :math:`C \sqsubseteq \exists R. D` as edges ``(C, R, D)``. ``DL2Vec`` and ``OWL2Vec*`` contain more complex rules. Let's have a look at them: - -DL2Vec -------- -The DL2Vec graph follows the rules described in the paper `Predicting candidate genes from phenotypes, functions, and anatomical site of expression (2020) `__. The parsing rules are shown in the table below: - - -+-------------------------------------------------------+---------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------------+ -| Condition 1 | Condition 2 | Triple(s) | -+=======================================================+=======================================================================================+===============================================================================================+ -| :math:`A \sqsubseteq Q R_{0} \ldots Q R_{m} D` | :math:`D := B_{1} \sqcup \ldots \sqcup B_{n} | B_{1} \sqcap \ldots \sqcap B_{n}` | :math:`\left\langle A, (R_{0}...R_{m}), B_i \right\rangle` for :math:`i \in 1 \ldots n` | -+-------------------------------------------------------+ | | -| :math:`A \equiv Q R_{0} \ldots Q R_{m} D` | | | -+-------------------------------------------------------+---------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------------+ -| :math:`A \sqsubseteq B` | | :math:`\left\langle A, SubClassOf, B \right\rangle` | -+-------------------------------------------------------+---------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------------+ -| :math:`A \equiv B` | | :math:`\left\langle A, EquivalentTo, B \right\rangle` | -+-------------------------------------------------------+---------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------------+ - - - -OWL2Vec* ----------- - - -The OWL2Vec* graph follows the rules described in the paper `OWL2Vec*: embedding of OWL ontologies (2021) `__. The parsing rules are shown in the table below: - - -+-------------------------------------------------------+-------------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------+ -|Axiom of condition 1 | Axiom or triple(s) of condition 2 | Projected triple(s) | -+=======================================================+=======================================================================================================+=======================================================================+ -|:math:`A \sqsubseteq \square r . D` | :math:`D \equiv B\left|B_{1} \sqcup \ldots \sqcup B_{n}\right| B_{1} \sqcap \ldots \sqcap B_{n}` | :math:`\langle A, r, B\rangle` | -+-------------------------------------------------------+ | | -|or | | | -+-------------------------------------------------------+ | | -|:math:`\square r . D \sqsubseteq A` | | | -+-------------------------------------------------------+-------------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------+ -|:math:`\exists r . \top \sqsubseteq A` (domain) | :math:`\top \sqsubseteq \forall r . B` (range) | :math:`\langle A, r, B_{i}\rangle` for :math:`i \in 1, \ldots, n` | -+-------------------------------------------------------+-------------------------------------------------------------------------------------------------------+ | -|:math:`A \sqsubseteq \exists r .\{b\}` | :math:`B(b)` | | -+-------------------------------------------------------+-------------------------------------------------------------------------------------------------------+ | -|:math:`r \sqsubseteq r^{\prime}` | :math:`\left\langle A, r^{\prime}, B\right\rangle` has been projected | | -+-------------------------------------------------------+-------------------------------------------------------------------------------------------------------+ | -|:math:`r^{\prime} \equiv r^{-}` | :math:`\left\langle B, r^{\prime}, A\right\rangle` has been projected | | -+-------------------------------------------------------+-------------------------------------------------------------------------------------------------------+ | -|:math:`s_{1} \circ \ldots \circ s_{n} \sqsubseteq r` | :math:`\langle A, s_1, C_1\rangle \ldots \langle C_n, s_n, B\rangle` have been projected | | -+-------------------------------------------------------+-------------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------+ -|:math:`B \sqsubseteq A` | :math:`-` | :math:`\langle B, r d f s: s u b C l a s s O f, A\rangle` | -| | +-----------------------------------------------------------------------+ -| | | :math:`\left\langle A, rdfs:subClassOf^{-}, B\right\rangle` | -+-------------------------------------------------------+-------------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------+ -|:math:`A(a)` | :math:`-` | :math:`\langle a, r d f: t y p e, A\rangle` | -| | +-----------------------------------------------------------------------+ -| | | :math:`\left\langle A, r d f: t y p e^{-}, a\right\rangle` | -+-------------------------------------------------------+-------------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------+ -|:math:`r(a, b)` | :math:`-` | :math:`\langle a, r, b\rangle` | -+-------------------------------------------------------+-------------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------+ - - -To use any projector, we can initialize them in two ways: directly or through a factory method. - -Directly: - -.. testcode:: python - - from mowl.projection import TaxonomyProjector, TaxonomyWithRelationsProjector, OWL2VecStarProjector, DL2VecProjector - - projector = TaxonomyProjector(bidirectional_taxonomy=True) - - projector = TaxonomyWithRelationsProjector( - taxonomy = True, - bidirectional_taxonomy=False, - relations = ["name of relation 1", "name of relation 2", "..."]) - - projector = DL2VecProjector(bidirectional_taxonomy= True) - - projector = OWL2VecStarProjector( - bidirectional_taxonomy=True, - include_literals = False, - only_taxonomy = True) - - -Using a factory method: - -.. testcode:: python - - from mowl.projection.factory import projector_factory - projector = projector_factory("dl2vec", bidirectional_taxonomy = True) - - -Given any projector, the input for starting the graph generation is an OWLOntology. For example: - -.. testcode:: python +.. testcode:: from mowl.datasets.builtin import FamilyDataset + from mowl.projection import CategoricalProjector - dataset = FamilyDataset() - - edges = projector.project(dataset.ontology) - - -The output is stored in the variable ``edges``, which is a list of :class:`Edge ` instances. + ds = FamilyDataset() + projector = CategoricalProjector() + edges = projector.project(ds.ontology) +The ``edges`` generated is a list of :class:`mowl.projection.Edge` -DL2Vec extension -------------------- -Initially, DL2Vec projection rules are intended to parse TBox axioms. However, for some cases, useful information might be present as ABox axioms of the form :math:`C(a)`, :math:`\exists R.C (a)` and :math:`R(a,b)` where :math:`C` is an atomic concept, :math:`R` is a role and :math:`a, b` are individuals. The extended rules are the following: +.. tip:: + All the implemented projectors can be found in :doc:`Projectors API docs <../../api/projection/index>` -+---------------------------+-----------------------------------------------------------+ -| Condition | Triple | -+===========================+===========================================================+ -| :math:`C(a)` | :math:`\left\langle a, http://type, C \right\rangle` | -+---------------------------+-----------------------------------------------------------+ -| :math:`\exists R.C (a)` | :math:`\left\langle a, R, C \right\rangle` | -+---------------------------+-----------------------------------------------------------+ -| :math:`R(a,b)` | :math:`\left\langle a, R, b \right\rangle` | -+---------------------------+-----------------------------------------------------------+ + -To use the extension, use the ``with_individuals`` parameters in the ``project`` method: -.. testcode:: python - from mowl.projection import DL2VecProjector - projector = DL2VecProjector(bidirectional_taxonomy= True) - edges_with_individuals = projector.project(dataset. - ontology, with_individuals=True) diff --git a/docs/source/graphs/random_walks.rst b/docs/source/graphs/random_walks.rst index 56417497..e1bfbf2d 100644 --- a/docs/source/graphs/random_walks.rst +++ b/docs/source/graphs/random_walks.rst @@ -9,10 +9,7 @@ Embedding with Random Walks After |projection|, one possible next step is to generate random walks. -mOWL provides two different algorithms for random walks generation: - -* :class:`DeepWalk ` -* :class:`Node2Vec ` +mOWL provides two different algorithms for random walks generation. All the implemented projectors can be found in The algorithms in mOWL are a variation from the original ones. Graphs obtained from ontologies always have labeled edges, therefore the **edge labels are included** in the random walks. @@ -21,27 +18,11 @@ The algorithms in mOWL are a variation from the original ones. Graphs obtained f In |projection|, we saw that graphs were represented as an edge list and each edge was an instance of the :class:`Edge ` class. This edge list is the input or the random walk methods. -Deepwalk ------------- - -Deepwalk can be called in two ways: +For example, let's take :class:`DeepWalk `: .. testcode:: - from mowl.walking.factory import walker_factory - walker = walker_factory( - "deepwalk", - alpha = 0.1, - walk_length = 10, - num_walks = 8, - outfile = "/tmp/walks.txt", # optional/path/to/save/walks - workers = 4) - -or - -.. testcode:: - - from mowl.walking.deepwalk.model import DeepWalk + from mowl.walking import DeepWalk walker = DeepWalk( 10, #num_walks, 8, #walk_length, @@ -50,7 +31,7 @@ or workers = 4) .. tip:: - Information about each method can be found at :class:`Walking models API docs. ` + Information about each method can be found at :doc:`Walking models API docs <../../api/walking/index>`. After generating an edge list where each element is instance of :class:`Edge `, the walks can be generated by: @@ -61,39 +42,7 @@ After generating an edge list where each element is instance of :class:`Edge `_ or from `PyPi `_. For more details on installation check out the how to :doc:`install/index` section of the project. .. note:: - This version of documentation corresponds to mOWL-0.2.0. + This version of documentation corresponds to mOWL-0.3.0. mOWL, JPype and the JVM @@ -138,8 +138,14 @@ If you used mOWL in your work, please consider citing `this article `. All the annotations will be inserted into the ontology in the form :math:`C \sqsubseteq \exists R.D`, where :math:`C` is the annotating entity (it can be a new ontology class), :math:`D` is the annotated entity (usually is a class already existing in the ontology) and :math:`R` is the label of the relation. The annotation information must be stored in a ``.tsv`` file. +The method :func:`insert_annotations ` allows adding new axioms in the form :math:`C \sqsubseteq \exists R.D`. Entities :math:`C`, :math:`R` and :math:`D` must be stored in a ``.tsv`` file. -For example, let's say we have an ontology called :download:`MyOntology.owl` where there are ontology classes ``http://some_prefix/class_001``, ``http://some_prefix/class_002`` and ``http://some_prefix/class_003``. Furthermore, we have some other classes ``http://newclass1``, ``http://newclass2`` that are in relation with the already classes in the ontology. The relation must be a proper URI (let's use ``http://has_annotation``). +For example, let's say we have an ontology called :download:`MyOntology.owl` and we want to add new axioms with a relation ``http://has_annotation``. -The annotation information must be stored in an :download:`annotations file ` with the following format: +The axiom information must be stored in an :download:`annotations file ` with the following format: .. code:: text - http://newclass1 http://some_prefix/class:002 - http://newclass2 http://some_prefix/class:001 http://some_prefix/class:003 - + http://prefix1/class1 http://prefix3/class3 + http://prefix2/class2 http://prefix4/class4 http://prefix5/class5 + Then to add that information to the ontology we use the following instructions: .. testcode:: @@ -47,29 +47,29 @@ In our example, the axioms inserted in the ontology will be the following in XML .. code:: xml - + - + - + - + - + - + diff --git a/envs/environment_dev_3_8.yml b/envs/environment_dev_3_8.yml index d61595fb..d5f4afb1 100644 --- a/envs/environment_dev_3_8.yml +++ b/envs/environment_dev_3_8.yml @@ -28,6 +28,7 @@ dependencies: - build - pykeen>=1.10.1 - sphinx-rtd-theme + - sphinx-automodapi - nose - nose-exclude - coverage @@ -37,7 +38,7 @@ dependencies: - memory-profiler - jupyter - ipykernel - - sphinx + - sphinx<7 - sphinx-gallery - nbsphinx - mock diff --git a/gradle_install.sh b/gradle_install.sh index c0e5e364..48f613c2 100755 --- a/gradle_install.sh +++ b/gradle_install.sh @@ -1,3 +1,5 @@ +#This file is part of the ReadTheDocs workflow + wget https://services.gradle.org/distributions/gradle-7.4.2-bin.zip -P /tmp unzip -d ~/gradle /tmp/gradle-*.zip diff --git a/mowl/base_models/__init__.py b/mowl/base_models/__init__.py index 926c7869..df39fa48 100644 --- a/mowl/base_models/__init__.py +++ b/mowl/base_models/__init__.py @@ -1,4 +1,4 @@ -from .model import Model, EmbeddingModel +from .model import Model from .elmodel import EmbeddingELModel from .graph_model import GraphModel, RandomWalkModel, KGEModel from .syntactic_model import SyntacticModel diff --git a/mowl/base_models/alcmodel.py b/mowl/base_models/alcmodel.py index b83a1a37..5833320f 100644 --- a/mowl/base_models/alcmodel.py +++ b/mowl/base_models/alcmodel.py @@ -1,10 +1,10 @@ -from mowl.base_models.model import EmbeddingModel +from mowl.base_models.model import Model import torch as th from torch.utils.data import DataLoader, default_collate from mowl.datasets.alc import ALCDataset -class EmbeddingALCModel(EmbeddingModel): +class EmbeddingALCModel(Model): """Abstract class that provides basic functionalities for methods that aim to embed ALC \ language. diff --git a/mowl/base_models/elmodel.py b/mowl/base_models/elmodel.py index 89dc30d6..7205e117 100644 --- a/mowl/base_models/elmodel.py +++ b/mowl/base_models/elmodel.py @@ -1,5 +1,5 @@ from mowl.ontology.normalize import ELNormalizer -from mowl.base_models.model import EmbeddingModel +from mowl.base_models.model import Model import torch as th from torch.utils.data import DataLoader, default_collate from mowl.datasets.el import ELDataset @@ -12,9 +12,8 @@ import mowl.error.messages as msg import os -class EmbeddingELModel(EmbeddingModel): - """Abstract class that provides basic functionalities for methods that aim to embed EL \ - language. +class EmbeddingELModel(Model): + """Abstract class for :math:`\mathcal{EL}` embedding methods. :param extended: If `True`, the model is supposed with 7 EL normal forms. This will be \ reflected on the :class:`DataLoaders` that will be generated and also the model must \ diff --git a/mowl/base_models/graph_model.py b/mowl/base_models/graph_model.py index 51641ddb..feb56c58 100644 --- a/mowl/base_models/graph_model.py +++ b/mowl/base_models/graph_model.py @@ -6,6 +6,11 @@ class GraphModel(Model): + """ + Abstract class for graph embedding methods. + """ + + def __init__(self, *args, **kwargs): super(GraphModel, self).__init__(*args, **kwargs) @@ -93,8 +98,13 @@ def set_projector(self, projector): + class RandomWalkModel(GraphModel): + """ + Base class for graph embedding methods that use random walks. + """ + def __init__(self, *args, **kwargs): super(RandomWalkModel, self).__init__(*args, **kwargs) @@ -127,6 +137,10 @@ def train(self): class KGEModel(GraphModel): + """ + Base class for graph embedding methods that use knowledge graph embedding methods. + """ + def __init__(self, *args, **kwargs): super(KGEModel, self).__init__(*args, **kwargs) diff --git a/mowl/base_models/model.py b/mowl/base_models/model.py index 95b11524..ff1b6c51 100644 --- a/mowl/base_models/model.py +++ b/mowl/base_models/model.py @@ -9,14 +9,16 @@ @versionchanged(version="0.1.0", reason="Parameter ``model_filepath`` added in the base class for \ all models. Optional parameter that will use temporary files in case it is not set.") class Model(): - def __init__(self, dataset, model_filepath=None): - """Abstract model class - :param dataset: Dataset object. - :type dataset: mowl.datasets.base.Dataset - :param model_filepath: Path for saving the model. Defaults to a temporary file path. - :type model_filepath: str, optional - """ + """Abstract model class. + + :param dataset: Dataset object. + :type dataset: mowl.datasets.base.Dataset + :param model_filepath: Path for saving the model. Defaults to a temporary file path. + :type model_filepath: str, optional + """ + + def __init__(self, dataset, model_filepath=None): if not isinstance(dataset, Dataset): raise TypeError("Parameter dataset must be a mOWL Dataset.") @@ -136,10 +138,4 @@ def from_pretrained(self, file_name): """ raise NotImplementedError() -class EmbeddingModel(Model): - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - def get_embeddings_data(self): - raise NotImplementedError() diff --git a/mowl/datasets/__init__.py b/mowl/datasets/__init__.py index d16b9cc4..8f49ac2a 100644 --- a/mowl/datasets/__init__.py +++ b/mowl/datasets/__init__.py @@ -1,6 +1,7 @@ import mowl import jpype -from mowl.datasets.base import Dataset, PathDataset, RemoteDataset, TarFileDataset +from mowl.datasets.base import Dataset, PathDataset, RemoteDataset, TarFileDataset, OWLClasses, \ + OWLIndividuals, OWLObjectProperties from mowl.datasets.el import ELDataset from mowl.datasets.alc import ALCDataset diff --git a/mowl/datasets/alc/alc_dataset.py b/mowl/datasets/alc/alc_dataset.py index 12a7b7f3..cf3d0b7a 100644 --- a/mowl/datasets/alc/alc_dataset.py +++ b/mowl/datasets/alc/alc_dataset.py @@ -14,22 +14,28 @@ class ALCDataset(): """This class provides data-related methods to work with - :math:`\mathcal{ALC}` description logic language. In general, it + :math:`\mathcal{ALC}` description logic language. + + In general, it receives an ontology, groups axioms by similar patterns and returns a :class:`torch.utils.data.Dataset`. In the process, the classes and object properties names are mapped to an integer values to create the datasets and the corresponding dictionaries can be input or created from scratch. + .. warning:: + + This class is on development. + :param ontology: Input ontology :type ontology: :class:`org.semanticweb.owlapi.model.OWLOntology` - :param class_index_dict: Dictionary containing information `class - name --> index`. If not provided, a dictionary will be created + :param class_index_dict: Dictionary containing information `class \ + name --> index`. If not provided, a dictionary will be created \ from the ontology classes. Defaults to ``None``. :type class_index_dict: dict, optional - :param object_property_index_dict: Dictionary containing - information `object property name --> index`. If not provided, a - dictionary will be created from the ontology object + :param object_property_index_dict: Dictionary containing \ + information `object property name --> index`. If not provided, a \ + dictionary will be created from the ontology object \ properties. Defaults to ``None``. :type object_property_index_dict: dict, optional """ diff --git a/mowl/datasets/base.py b/mowl/datasets/base.py index 43e2a8fd..b9d7759a 100644 --- a/mowl/datasets/base.py +++ b/mowl/datasets/base.py @@ -103,6 +103,9 @@ def classes(self): @property def class_to_id(self): + """ + Dictionary mapping :class:`OWLClasses ` to integer ids. + """ return self.classes.as_index_dict @property @@ -124,6 +127,10 @@ def individuals(self): @property def individual_to_id(self): + """ + Dictionary mapping :class:`OWLIndividuals ` to integer ids. + """ + return self.individuals.as_index_dict @property @@ -151,6 +158,9 @@ def object_properties(self): @property def object_property_to_id(self): + """ + Dictionary mapping :class:`OWLObjectProperties ` to integer ids. + """ return self.object_properties.as_index_dict @property @@ -446,7 +456,9 @@ def as_index_dict(self): class OWLClasses(Entities): - """Class containing OWL classes indexed by they IRIs""" + """ + Iterable for :class:`org.semanticweb.owlapi.model.OWLClass` + """ def check_owl_type(self, collection): for item in collection: @@ -460,7 +472,9 @@ def to_str(self, owl_class): class OWLIndividuals(Entities): - """Class containing OWL individuals indexed by they IRIs""" + """ + Iterable for :class:`org.semanticweb.owlapi.model.OWLIndividual` + """ def check_owl_type(self, collection): for item in collection: @@ -474,7 +488,9 @@ def to_str(self, owl_individual): class OWLObjectProperties(Entities): - """Class containing OWL object properties indexed by they IRIs""" + """ + Iterable for :class:`org.semanticweb.owlapi.model.OWLObjectProperty` + """ def check_owl_type(self, collection): for item in collection: diff --git a/mowl/datasets/builtin/family.py b/mowl/datasets/builtin/family.py index b56bb9e0..3c55d236 100644 --- a/mowl/datasets/builtin/family.py +++ b/mowl/datasets/builtin/family.py @@ -4,8 +4,10 @@ class FamilyDataset(RemoteDataset): - r"""This dataset represents a family domain. It is a short ontology with 12 axioms describing \ -family relationships. The axioms are: + r"""This dataset represents a family domain. + + It is a short ontology with 12 axioms describing \ + family relationships. The axioms are: .. math:: @@ -21,10 +23,10 @@ class FamilyDataset(RemoteDataset): Male \sqcap Parent & \sqsubseteq Father \\ \exists hasChild.Person & \sqsubseteq Parent\\ Parent & \sqsubseteq Person \\ - Parent & \sqsubseteq \exists hasChild. \top + Parent & \sqsubseteq \exists hasChild.\top \end{align} -""" + """ def __init__(self, url=None): super().__init__(url=DATA_URL if not url else url) diff --git a/mowl/datasets/builtin/gda.py b/mowl/datasets/builtin/gda.py index de131cdf..e9acde46 100644 --- a/mowl/datasets/builtin/gda.py +++ b/mowl/datasets/builtin/gda.py @@ -15,7 +15,9 @@ class GDADataset(RemoteDataset): - """Abstract class for Gene--Disease association datasets. This dataset represent the \ + """Abstract class for gene-disease association datasets. + + This dataset represents the \ gene-disease association in a particular species. This dataset is built using phenotypic \ annotations of genes and diseases. For genes annotations we used the `Mouse/Human Orthology \ with Phenotype Annotations \ @@ -65,6 +67,11 @@ def get_evaluation_property(self): class GDAHumanDataset(GDADataset): + """ + Dataset containing gene-disease associations in human. + """ + + def __init__(self): super().__init__(url=DATA_HUMAN_URL) @@ -79,6 +86,10 @@ def __init__(self): class GDAMouseDataset(GDADataset): + """ + Dataset containing gene-disease associations in mouse. + """ + def __init__(self): super().__init__(url=DATA_MOUSE_URL) diff --git a/mowl/datasets/builtin/ppi_yeast.py b/mowl/datasets/builtin/ppi_yeast.py index 63eb4e0e..fec3f2e1 100644 --- a/mowl/datasets/builtin/ppi_yeast.py +++ b/mowl/datasets/builtin/ppi_yeast.py @@ -14,16 +14,18 @@ class PPIYeastDataset(RemoteDataset): """ - This dataset represent protein--protein interactions on the yeast species. The data used for \ - this dataset consists of the \ + Dataset containing protein-protein interactions in yeast. + + The data used for this dataset consists of the \ `Gene Ontology `_ released on 20-10-2021 and \ protein interaction data found in \ `String Database `_ version 11.5. \ Protein interaction data was randomly split 90:5:5 across training, validation and testing \ ontologies and Gene Ontology functional annotations of proteins is part of the training \ ontology only. Protein interactions are represented as an axiom of the form \ - :math:`protein_1 \sqsubseteq interacts\_with . protein_2.` -""" + :math:`protein_1 \sqsubseteq interacts\_with .protein_2.` + + """ def __init__(self, url=None): super().__init__(url=DATA_URL if not url else url) @@ -48,9 +50,11 @@ def get_evaluation_property(self): class PPIYeastSlimDataset(PPIYeastDataset): """ - Reduced version of :class:`PPIYeastDataset`. Tranining ontology is built from the Slim Yeast \ - subset of Gene Ontology. + Reduced version of :class:`PPIYeastDataset`. + + Tranining ontology is built from the Slim Yeast subset of Gene Ontology. + """ - def __init__(self, *args, **kwargs): + def __init__(self): super().__init__(url=SLIM_DATA_URL) diff --git a/mowl/evaluation/__init__.py b/mowl/evaluation/__init__.py index 0afa1f96..ad614b00 100644 --- a/mowl/evaluation/__init__.py +++ b/mowl/evaluation/__init__.py @@ -1,3 +1,4 @@ -from mowl.evaluation.base import AxiomsRankBasedEvaluator +from mowl.evaluation.base import Evaluator, AxiomsRankBasedEvaluator +from mowl.evaluation.rank_based import RankBasedEvaluator, ModelRankBasedEvaluator, EmbeddingsRankBasedEvaluator + -__all__ = ["AxiomsRankBasedEvaluator"] diff --git a/mowl/models/__init__.py b/mowl/models/__init__.py index 13e9f455..3dae4a10 100644 --- a/mowl/models/__init__.py +++ b/mowl/models/__init__.py @@ -1,5 +1,11 @@ from mowl.models.elembeddings.model import ELEmbeddings +from mowl.models.elembeddings.examples.model_ppi import ELEmPPI +from mowl.models.elembeddings.examples.model_gda import ELEmGDA + from mowl.models.elboxembeddings.model import ELBoxEmbeddings +from mowl.models.elboxembeddings.examples.model_ppi import ELBoxPPI +from mowl.models.elboxembeddings.examples.model_gda import ELBoxGDA + from mowl.models.graph_random_walk.random_walk_w2v_model import RandomWalkPlusW2VModel from mowl.models.graph_kge.graph_pykeen_model import GraphPlusPyKEENModel from mowl.models.syntactic.w2v_model import SyntacticPlusW2VModel diff --git a/mowl/models/elboxembeddings/examples/model_gda.py b/mowl/models/elboxembeddings/examples/model_gda.py index bc3877fd..04c176c5 100644 --- a/mowl/models/elboxembeddings/examples/model_gda.py +++ b/mowl/models/elboxembeddings/examples/model_gda.py @@ -13,7 +13,9 @@ class ELBoxGDA(ELBoxEmbeddings): - + """ + Example of ELBoxEmbeddings for gene-disease associations prediction. + """ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/mowl/models/elboxembeddings/examples/model_ppi.py b/mowl/models/elboxembeddings/examples/model_ppi.py index 743b4965..b40063fa 100644 --- a/mowl/models/elboxembeddings/examples/model_ppi.py +++ b/mowl/models/elboxembeddings/examples/model_ppi.py @@ -14,7 +14,9 @@ class ELBoxPPI(ELBoxEmbeddings): - + """ + Example of ELBoxEmbeddings for protein-protein interaction prediction. + """ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/mowl/models/elboxembeddings/model.py b/mowl/models/elboxembeddings/model.py index 5392eae2..57c79b93 100644 --- a/mowl/models/elboxembeddings/model.py +++ b/mowl/models/elboxembeddings/model.py @@ -1,7 +1,3 @@ -""" -This example is based on the paper `Description Logic EL++ Embeddings with Intersectional \ -Closure `_. -""" from mowl.nn import ELBoxModule from mowl.base_models.elmodel import EmbeddingELModel @@ -20,6 +16,9 @@ class ELBoxEmbeddings(EmbeddingELModel): + """ + Implementation based on [peng2020]_. + """ def __init__(self, dataset, diff --git a/mowl/models/elembeddings/examples/model_gda.py b/mowl/models/elembeddings/examples/model_gda.py index 6ec8b963..1aa934bb 100644 --- a/mowl/models/elembeddings/examples/model_gda.py +++ b/mowl/models/elembeddings/examples/model_gda.py @@ -7,7 +7,9 @@ from mowl.models import ELEmbeddings class ELEmGDA(ELEmbeddings): - + """ + Example of ELEmbeddings for gene-disease associations prediction. + """ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/mowl/models/elembeddings/examples/model_ppi.py b/mowl/models/elembeddings/examples/model_ppi.py index f4e0f191..ec125f35 100644 --- a/mowl/models/elembeddings/examples/model_ppi.py +++ b/mowl/models/elembeddings/examples/model_ppi.py @@ -9,7 +9,10 @@ from mowl.models import ELEmbeddings class ELEmPPI(ELEmbeddings): - + """ + Example of ELEmbeddings for protein-protein interaction prediction. + """ + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/mowl/models/elembeddings/model.py b/mowl/models/elembeddings/model.py index 83f22ae5..d9332ec0 100644 --- a/mowl/models/elembeddings/model.py +++ b/mowl/models/elembeddings/model.py @@ -1,12 +1,3 @@ -""" -This example corresponds to the paper `EL Embeddings: Geometric Construction of Models for the \ -Description Logic EL++ `_. - -The idea of this paper is to embed EL by modeling ontology classes as :math:`n`-dimensional \ -balls (:math:`n`-balls) and ontology object properties as transformations of those \ -:math:`n`-balls. For each of the normal forms, there is a distance function defined that will \ -work as loss functions in the optimization framework. -""" from mowl.base_models.elmodel import EmbeddingELModel @@ -17,7 +8,16 @@ from mowl.projection import projector_factory class ELEmbeddings(EmbeddingELModel): + """ + Implementation based on [kulmanov2019]_. + + The idea of this paper is to embed EL by modeling ontology classes as :math:`n`-dimensional \ + balls (:math:`n`-balls) and ontology object properties as transformations of those \ + :math:`n`-balls. For each of the normal forms, there is a distance function defined that will \ + work as loss functions in the optimization framework. + """ + def __init__(self, dataset, embed_dim=50, diff --git a/mowl/models/graph_kge/graph_pykeen_model.py b/mowl/models/graph_kge/graph_pykeen_model.py index 2ab0c256..ff142401 100644 --- a/mowl/models/graph_kge/graph_pykeen_model.py +++ b/mowl/models/graph_kge/graph_pykeen_model.py @@ -9,8 +9,9 @@ import os import mowl.error.messages as msg from pykeen.training import SLCWATrainingLoop +from deprecated.sphinx import versionadded - +@versionadded(version="0.2.0") class GraphPlusPyKEENModel(KGEModel): """ This is a wrapper class of :class:`pykeen.models.ERModel` that allows to use the PyKEEN models in the mOWL framework. diff --git a/mowl/models/graph_random_walk/random_walk_w2v_model.py b/mowl/models/graph_random_walk/random_walk_w2v_model.py index be73e0e4..03211c67 100644 --- a/mowl/models/graph_random_walk/random_walk_w2v_model.py +++ b/mowl/models/graph_random_walk/random_walk_w2v_model.py @@ -4,9 +4,14 @@ import mowl.error.messages as msg import os import time +from deprecated.sphinx import versionadded +@versionadded(version="0.2.0") class RandomWalkPlusW2VModel(RandomWalkModel): - + """ + Embedding model that combines graph projections + random walks. + """ + def __init__(self, *args, **kwargs): super(RandomWalkPlusW2VModel, self).__init__(*args, **kwargs) diff --git a/mowl/models/syntactic/w2v_model.py b/mowl/models/syntactic/w2v_model.py index fe89157e..3fe561b0 100644 --- a/mowl/models/syntactic/w2v_model.py +++ b/mowl/models/syntactic/w2v_model.py @@ -3,9 +3,14 @@ from gensim.models import Word2Vec from gensim.models.word2vec import LineSentence import mowl.error.messages as msg +from deprecated.sphinx import versionadded +@versionadded(version="0.2.0") class SyntacticPlusW2VModel(SyntacticModel): - + """ + Model that combines corpus generation with Word2Vec training. + """ + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.w2v_model = None diff --git a/mowl/nn/el/boxsquaredel/module.py b/mowl/nn/el/boxsquaredel/module.py index 4c20d87d..221dd22d 100644 --- a/mowl/nn/el/boxsquaredel/module.py +++ b/mowl/nn/el/boxsquaredel/module.py @@ -5,7 +5,10 @@ class BoxSquaredELModule(ELModule): - + """ + Implementation of Box :math:`^2` EL from [jackermeier2023]_. + """ + def __init__(self, nb_ont_classes, nb_rels, embed_dim=50, gamma=0, delta = 2, reg_factor = 0.05): super().__init__() self.nb_ont_classes = nb_ont_classes diff --git a/mowl/nn/el/elbox/module.py b/mowl/nn/el/elbox/module.py index 663ce648..e51d8ab1 100644 --- a/mowl/nn/el/elbox/module.py +++ b/mowl/nn/el/elbox/module.py @@ -5,7 +5,8 @@ class ELBoxModule(ELModule): - + """Implementation of ELBoxEmbeddings from [peng2020]_. + """ def __init__(self, nb_ont_classes, nb_rels, embed_dim=50, margin=0.1): super().__init__() self.nb_ont_classes = nb_ont_classes diff --git a/mowl/nn/el/elem/module.py b/mowl/nn/el/elem/module.py index fbfded27..9d589566 100644 --- a/mowl/nn/el/elem/module.py +++ b/mowl/nn/el/elem/module.py @@ -5,7 +5,12 @@ class ELEmModule(ELModule): + """ + Implementation of ELEmbeddings from [kulmanov2019]_. + """ + + def __init__(self, nb_ont_classes, nb_rels, embed_dim=50, margin=0.1, reg_norm=1): super().__init__() self.nb_ont_classes = nb_ont_classes diff --git a/mowl/nn/el/elmodule.py b/mowl/nn/el/elmodule.py index 98188dd9..40ced8e9 100644 --- a/mowl/nn/el/elmodule.py +++ b/mowl/nn/el/elmodule.py @@ -3,7 +3,9 @@ class ELModule(nn.Module): - """Subclass of :class:`torch.nn.Module` for :math:`\mathcal{EL}` models. This class provides \ + """Subclass of :class:`torch.nn.Module` for :math:`\mathcal{EL}` models. + + This class provides \ an interface for loss functions of the 7 possible normal forms existing in the \ :math:`\mathcal{EL}` language. In case a negative version of one of the loss function exist, \ it must be placed inside the original loss function and be accesed through the ``neg`` \ @@ -37,7 +39,7 @@ def gci1_loss(self, gci, neg=False): """Loss function for GCI1: :math:`C_1 \sqcap C_2 \sqsubseteq D`. :param gci: Input tensor of shape \(\*,3\) where ``C1`` classes will be at \ - ``gci[:,0]``, ``C2`` classes will be at gci[:,1] and ``D`` classes will be at \ + ``gci[:,0]``, ``C2`` classes will be at ``gci[:,1]`` and ``D`` classes will be at \ ``gci[:,2]``. It is recommended to use the :class:`ELDataset `. :type gci: :class:`torch.Tensor` :param neg: Parameter indicating that the negative version of this loss function must be \ @@ -48,12 +50,11 @@ def gci1_loss(self, gci, neg=False): raise NotImplementedError() def gci2_loss(self, gci, neg=False): - """Loss function for GCI2: :math:`C \sqsubseteq R. D`. - + """Loss function for GCI2: :math:`C \sqsubseteq \exists R.D`. + :math:`C \sqsubseteq \exists R. D`. :param gci: Input tensor of shape \(\*,3\) where ``C`` classes will be at \ ``gci[:,0]``, ``R`` object properties will be at ``gci[:,1]`` and ``D`` classes will be \ - at ``gci[:,2]``. It is recommended to use the \ - :class:`ELDataset `. + at ``gci[:,2]``. It is recommended to use the :class:`ELDataset `. :type gci: :class:`torch.Tensor` :param neg: Parameter indicating that the negative version of this loss function must be \ used. Defaults to ``False``. @@ -63,7 +64,7 @@ def gci2_loss(self, gci, neg=False): raise NotImplementedError() def gci3_loss(self, gci, neg=False): - """Loss function for GCI3: :math:`R. C \sqsubseteq D`. + """Loss function for GCI3: :math:`\exists R.C \sqsubseteq D`. :param gci: Input tensor of shape \(\*,3\) where ``R`` object properties will be at \ gci[:,0], ``C`` classes will be at ``gci[:,1]`` and ``D`` classes will be at \ @@ -94,7 +95,7 @@ def gci1_bot_loss(self, gci, neg=False): """Loss function for GCI1 with bottom concept: :math:`C_1 \sqcap C_2 \sqsubseteq \perp`. :param gci: Input tensor of shape \(\*,3\) where ``C1`` classes will be at ``gci[:,0]``, \ - ``C2`` classes will be at gci[:,1] and ``bottom`` classes will be at ``gci[:,2]``. It is \ + ``C2`` classes will be at ``gci[:,1] and`` ``bottom`` classes will be at ``gci[:,2]``. It is \ recommended to use the :class:`ELDataset `. :type gci: :class:`torch.Tensor` :param neg: Parameter indicating that the negative version of this loss function must be \ @@ -105,7 +106,7 @@ def gci1_bot_loss(self, gci, neg=False): raise NotImplementedError() def gci3_bot_loss(self, gci, neg=False): - """Loss function for GCI3 with bottom concept: :math:`R. C \sqsubseteq \perp`. + """Loss function for GCI3 with bottom concept: :math:`\exists R.C \sqsubseteq \perp`. :param gci: Input tensor of shape \(\*,3\) where ``R`` object properties will be at \ gci[:,0], ``C`` classes will be at ``gci[:,1]`` and ``bottom`` classes will be at \ @@ -119,6 +120,13 @@ def gci3_bot_loss(self, gci, neg=False): raise NotImplementedError() def get_loss_function(self, gci_name): + """ + This chooses the corresponding loss fuction given the name of the GCI. + + :param gci_name: Name of the GCI. Choices are ``gci0``, ``gci1``, ``gci2``, ``gci3``, \ + ``gci0_bot``, ``gci1_bot`` and ``gci3_bot``. + :type gci_name: str + """ if gci_name not in self.gci_names: raise ValueError( diff --git a/mowl/projection/__init__.py b/mowl/projection/__init__.py index 1cef37b9..09f760e1 100644 --- a/mowl/projection/__init__.py +++ b/mowl/projection/__init__.py @@ -3,4 +3,5 @@ from .owl2vec_star.model import OWL2VecStarProjector from .taxonomy.model import TaxonomyProjector from .taxonomy_rels.model import TaxonomyWithRelationsProjector +from .categorical.model import CategoricalProjector from .factory import projector_factory diff --git a/mowl/projection/categorical/__init__.py b/mowl/projection/categorical/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/mowl/projection/categorical/edge.py b/mowl/projection/categorical/edge.py new file mode 100644 index 00000000..9e2457cb --- /dev/null +++ b/mowl/projection/categorical/edge.py @@ -0,0 +1,419 @@ +import numpy as np +import torch as th +from org.semanticweb.owlapi.model import OWLClassExpression, OWLObjectProperty, OWLClass, OWLObjectUnionOf, OWLObjectIntersectionOf, OWLObjectSomeValuesFrom, OWLObjectAllValuesFrom, ClassExpressionType, OWLObjectComplementOf + + + +from org.semanticweb.owlapi.manchestersyntax.renderer import ManchesterOWLSyntaxOWLObjectRendererImpl +from jpype.types import JString +from org.mowl import MOWLShortFormProvider +from mowl.owlapi.defaults import BOT, TOP +from mowl.owlapi import OWLAPIAdapter +renderer = ManchesterOWLSyntaxOWLObjectRendererImpl() +short_form_provider = MOWLShortFormProvider() + +renderer.setShortFormProvider(short_form_provider) + +adapter = OWLAPIAdapter() +top_class = adapter.create_class(TOP) +bot_class = adapter.create_class(BOT) +import logging + +class Node(): + def __init__(self, owl_class = None, relation = None, domain=False, codomain=False, negated_domain=False, individual=False): + self.individual = individual + bad_class = False + bad_relation = False + if owl_class is not None and not isinstance(owl_class, (OWLClassExpression)): + bad_class = True + + if relation is not None and not isinstance(relation, OWLObjectProperty): + bad_relation = True + + #Simplify the node + ## Not Bot ---> Top and Not Top ---> Bot + if isinstance(owl_class, OWLObjectComplementOf): + owl_class_operand = owl_class.getOperand() + if owl_class_operand.isOWLNothing(): + owl_class = top_class + elif owl_class_operand.isOWLThing(): + owl_class = bot_class + + + + elif isinstance(owl_class, OWLObjectUnionOf): + operands = owl_class.getOperandsAsList() + if len(operands) == 1: + raise ValueError(f"Union of one element is not allowed\n{owl_class} \t{operands[0].toString()}") + for op in operands: + if op.isOWLNothing(): + logging.info(f"owl:Nothing existing in union. Removing it..") + operands.remove(op) + elif op.isOWLThing(): + logging.info(f"owl:Thing existing in union. Simplifying to owl:Thing..") + operands = None + break + if operands is None: + owl_class = top_class + elif len(operands) == 1: + owl_class = operands[0] + else: + owl_class = adapter.create_object_union_of(*operands) + + + + if isinstance(owl_class, OWLObjectIntersectionOf): + operands = owl_class.getOperandsAsList() + if len(operands) == 1: + raise ValueError(f"Intersection of one element is not allowed\n{owl_class} \t{operands[0].toString()}") + for op in operands: + if op.isOWLThing(): + logging.info(f"owl:Thing existing in intersection. Removing it..") + operands.remove(op) + if op.isOWLNothing(): + logging.info(f"owl:Nothing existing in intersection. Simplifying to owl:Nothing..") + operands = None + break + + if operands is None: + owl_class = bot_class + + elif len(operands) == 1: + owl_class = operands[0] + else: + owl_class = adapter.create_object_intersection_of(*operands) + + + + if bad_class or bad_relation: + raise TypeError(f"Wrong either owl_class or relation. Required owl_clas of type OWLClassExpression. Got {type(owl_class)}. Required relation of type OWLObjectProperty. Got {type(relation)}") + + if domain and codomain: + raise ValueError("Domain and codomain cannot be both True") + if negated_domain and not domain: + raise ValueError("Negated domain can only be True if domain is True") + + + if relation is not None and owl_class is not None: + owl_class = owl_class.getNNF() + + if relation is not None and not owl_class is None and not relation.equals(owl_class.getProperty()): + raise ValueError(f"Relation and owl_class do not match. Relation: {relation.toStringID()}. OWLClass: {owl_class.toStringID()}") + + + + + self.domain = domain + self.codomain = codomain + self.negated_domain = negated_domain + self.owl_class = owl_class + self.relation = relation + + + if not owl_class is None and self.relation is None and not self.domain and not self.codomain: + tmp_class = None + if self.owl_class.getClassExpressionType() == ClassExpressionType.OBJECT_COMPLEMENT_OF: + tmp_class = owl_class.getOperand() + if tmp_class.getClassExpressionType() == ClassExpressionType.OBJECT_COMPLEMENT_OF: + self.owl_class = tmp_class.getOperand() + + + + assert not (domain and codomain) + + def __eq__(self, other): + if not isinstance(other, Node): + return False + + eq = True + if self.owl_class is None: + eq = eq and other.owl_class is None + else: + eq = eq and self.owl_class.equals(other.owl_class) + + if self.relation is None: + eq = eq and other.relation is None + else: + eq = eq and self.relation.equals(other.relation) + + eq = eq and self.domain == other.domain + eq = eq and self.codomain == other.codomain + eq = eq and self.negated_domain == other.negated_domain + + return eq + + def __hash__(self): + return hash((self.owl_class, self.relation, self.domain, self.codomain, self.negated_domain)) + + def __repr__(self): + if self.relation is None: + expr_str = renderer.render(self.owl_class) + expr_str = expr_str.replaceAll(JString("[\\r\\n|\\r|\\n()|<|>]"), JString("")) + return str(expr_str) + else: + if self.owl_class is None: + repr_ = str(self.relation.toStringID()) + else: + cls_str = renderer.render(self.owl_class) + cls_str = cls_str.replaceAll(JString("[\\r\\n|\\r|\\n()|<|>]"), JString("")) + cls_str = str(cls_str) + rel_str = str(self.relation.toStringID()) + repr_ = rel_str + "_under_" + cls_str + + if self.domain: + repr_ = "DOMAIN_" + repr_ + elif self.codomain: + repr_ = "CODOMAIN_" + repr_ + + if self.negated_domain: + repr_ = "NOT_" + repr_ + + return repr_ + + + def is_individual(self): + return self.individual + + def is_negated(self): + is_negated = False + if self.owl_class is not None and self.relation is None: + if self.owl_class.getClassExpressionType() == ClassExpressionType.OBJECT_COMPLEMENT_OF: + is_negated = True + elif self.domain: + if self.negated_domain: + is_negated = True + + return is_negated + + def is_intersection(self): + is_intersection = False + if self.owl_class is not None: + if self.owl_class.getClassExpressionType() == ClassExpressionType.OBJECT_INTERSECTION_OF: + is_intersection = True + return is_intersection + + def is_union(self): + is_union = False + if self.owl_class is not None: + if self.owl_class.getClassExpressionType() == ClassExpressionType.OBJECT_UNION_OF: + is_union = True + return is_union + + + def negate(self): + if not self.in_object_category(): + raise ValueError("Cannot negate a relation node") + if self.domain: + new_node = Node(owl_class=self.owl_class, relation=self.relation, domain=self.domain, codomain=self.codomain, negated_domain= not self.negated_domain) + else: + if self.owl_class.getClassExpressionType() == ClassExpressionType.OBJECT_COMPLEMENT_OF: + new_node = Node(owl_class=self.owl_class.getOperand(), relation=self.relation, domain=self.domain, codomain=self.codomain, negated_domain=self.negated_domain) + elif self.owl_class.isOWLThing(): + new_node = Node(owl_class=bot_class, relation=self.relation, domain=self.domain, codomain=self.codomain, negated_domain=self.negated_domain) + elif self.owl_class.isOWLNothing(): + new_node = Node(owl_class=top_class, relation=self.relation, domain=self.domain, codomain=self.codomain, negated_domain=self.negated_domain) + else: + new_node = Node(owl_class=self.owl_class.getObjectComplementOf(), relation=self.relation, domain=self.domain, codomain=self.codomain, negated_domain=self.negated_domain) + return new_node + + def nnf(self): + if not self.in_object_category(): + return self + if self.owl_class is None: + return self + else: + new_node = Node(owl_class=self.owl_class.getNNF(), relation=self.relation, domain=self.domain, codomain=self.codomain, negated_domain=self.negated_domain) + return new_node + + def to_domain(self): + if not self.in_relation_category(): + raise ValueError("Cannot convert a class node to domain") + if self.domain: + raise ValueError("Cannot convert a domain node to domain") + if self.codomain: + raise ValueError("Cannot convert a codomain node to domain") + if self.negated_domain: + raise ValueError("Cannot convert a negated domain node to domain") + + new_node = Node(owl_class=self.owl_class, relation=self.relation, domain=True, codomain=False, negated_domain=False) + return new_node + + def to_codomain(self): + if not self.in_relation_category(): + raise ValueError("Cannot convert a class node to codomain") + if self.domain: + raise ValueError("Cannot convert a domain node to codomain") + if self.codomain: + raise ValueError("Cannot convert a codomain node to codomain") + if self.negated_domain: + raise ValueError("Cannot convert a negated domain node to codomain") + + new_node = Node(owl_class=self.owl_class, relation=self.relation, domain=False, codomain=True, negated_domain=False) + return new_node + + + + + + def in_object_category(self): + """Determines if the node is in the category of objects, not relations""" + in_object_category = False + + if self.individual: + return False + + if self.owl_class is not None: + if self.relation is not None: + if self.domain or self.codomain: + in_object_category = True + else: + in_object_category = False + else: + in_object_category = True + else: + if self.domain or self.codomain: + in_object_category = True + else: + in_object_category = False + + return in_object_category + + + def in_relation_category(self): + """Determines if the node is in the category of relations, not objects""" + return not self.in_object_category() + + + def is_whole_relation(self): + if self.owl_class is None and self.relation is not None and not (self.domain or self.codomain or self.negated_domain): + return True + else: + return False + + def is_existential(self): + is_existential = False + if self.in_object_category(): + if not self.domain and not self.codomain: + if self.owl_class is not None and self.relation is not None: + if self.owl_class.getClassExpressionType() == ClassExpressionType.OBJECT_SOME_VALUES_FROM: + is_existential = True + + return is_existential + + def is_owl_nothing(self): + is_bot = False + if self.owl_class is not None: + is_bot = self.owl_class.isOWLNothing() + return is_bot + + def is_owl_thing(self): + is_top = False + if self.owl_class is not None: + is_top = self.owl_class.isOWLThing() + return is_top + + def get_operand(self): + if not self.is_negated: + raise ValueError("Node is not negated") + if self.negated_domain: + new_node = Node(owl_class=self.owl_class, relation=self.relation, domain=self.domain, codomain=self.codomain, negated_domain=False) + elif self.owl_class.getClassExpressionType() == ClassExpressionType.OBJECT_COMPLEMENT_OF: + new_node = Node(owl_class=self.owl_class.getOperand(), relation=self.relation, domain=self.domain, codomain=self.codomain, negated_domain=self.negated_domain) + else: + raise ValueError("Node is not negated") + return new_node + + + + +class Edge: + """Class representing a graph edge. + """ + + def __init__(self, src, rel, dst, weight=1.): + + if not isinstance(src, Node): + raise TypeError("Parameter src must be a Node") + if not isinstance(rel, str): + raise TypeError("Parameter rel must be a str") + if not isinstance(dst, Node): + raise TypeError("Parameter dst must be a Node") + if not isinstance(weight, float): + raise TypeError("Optional parameter weight must be a float") + + src_str = str(src) + dst_str = str(dst) + + self._src = src + self._rel = rel + self._dst = "" if dst == "" else dst + self._weight = weight + + @property + def src(self): + """ + Getter method for ``_src`` attribute + + :rtype: str + """ + return self._src + + @property + def rel(self): + """ + Getter method for ``_rel`` attribute + + :rtype: str + """ + return self._rel + + @property + def dst(self): + """ + Getter method for ``_dst`` attribute + + :rtype: str + """ + return self._dst + + @property + def weight(self): + """ + Getter method for ``_weight`` attribute + + :rtype: str + """ + return self._weight + + def astuple(self): + return tuple(map(str, (self._src, self._rel, self._dst))) + + @staticmethod + def getEntitiesAndRelations(edges): + return Edge.get_entities_and_relations(edges) + + @staticmethod + def get_entities_and_relations(edges): + ''' + :param edges: list of edges + :type edges: :class:`Edge` + + :returns: Returns a 2-tuple containing the list of entities (heads and tails) and the \ + list of relations + :rtype: (Set of str, Set of str) + ''' + + entities = set() + relations = set() + + for edge in edges: + entities |= {edge.src, edge.dst} + relations |= {edge.rel} + + return (entities, relations) + + @staticmethod + def zip(edges): + return tuple(zip(*[x.astuple() for x in edges])) + + diff --git a/mowl/projection/categorical/model.py b/mowl/projection/categorical/model.py new file mode 100644 index 00000000..fa7210e1 --- /dev/null +++ b/mowl/projection/categorical/model.py @@ -0,0 +1,846 @@ +import jpype +@jpype.JImplementationFor('java.lang.Comparable') +class _JComparableHash(object): + def __hash__(self): + return self.hashCode() + +import logging +logger = logging.getLogger(__name__) +logger.setLevel(logging.INFO) + +from .edge import Edge, Node +from .utils import IGNORED_AXIOM_TYPES, IGNORED_EXPRESSION_TYPES, pairs + +from mowl.projection.base import ProjectionModel +from mowl.owlapi import OWLAPIAdapter +from mowl.owlapi.defaults import BOT, TOP + +from org.semanticweb.owlapi.model import AxiomType, EntityType, OWLObjectInverseOf, OWLOntology +from org.semanticweb.owlapi.model import ClassExpressionType as CT + +import networkx as nx +from deprecated.sphinx import versionadded + +from tqdm import tqdm + +adapter = OWLAPIAdapter() +top_node = Node(owl_class = adapter.create_class(TOP)) +bot_node = Node(owl_class = adapter.create_class(BOT)) + +class Graph(): + + + + + def __init__(self): + self._node_to_id = {} + self._id_to_node = {} + self._out_edges = dict() + self._in_edges = dict() + + @property + def node_to_id(self): + return self._node_to_id + + @property + def id_to_node(self): + return self._id_to_node + + @property + def nodes(self): + return set(self.node_to_id.keys()) + + @property + def out_edges(self): + return self._out_edges + + @property + def in_edges(self): + return self._in_edges + + def add_node(self, node): + + if node in self.node_to_id: + return + + if not isinstance(node, Node): + raise TypeError(f"Node must be of type Node. Got {type(node)}") + + if node.is_owl_thing(): + return + if node.is_owl_nothing(): + return + + if bot_node not in self.node_to_id: + self.node_to_id[bot_node] = len(self.node_to_id) + self.id_to_node[self.node_to_id[bot_node]] = bot_node + self.out_edges[bot_node] = set() + self.out_edges[bot_node].add(top_node) + self.in_edges[bot_node] = set() + + if top_node not in self.node_to_id: + self.node_to_id[top_node] = len(self.node_to_id) + self.id_to_node[self.node_to_id[top_node]] = top_node + self.out_edges[top_node] = set() + self.in_edges[top_node] = set() + self.in_edges[top_node].add(bot_node) + + + if not node in self.node_to_id: + self.node_to_id[node] = len(self.node_to_id) + self.id_to_node[self.node_to_id[node]] = node + self.out_edges[node] = set() + self.in_edges[node] = set() + + self.in_edges[node].add(node) + self.out_edges[node].add(node) + + self.in_edges[node].add(bot_node) + self.out_edges[bot_node].add(node) + + self.in_edges[top_node].add(node) + self.out_edges[node].add(top_node) + + + + if node.in_object_category() and not node.domain and not node.codomain and node.owl_class.getClassExpressionType() == CT.OWL_CLASS: + negated = node.negate() + and_node = adapter.create_object_intersection_of(node.owl_class, negated.owl_class) + and_node = Node(owl_class = and_node) + self.add_edge(Edge(and_node, "http://arrow", bot_node)) + self.add_edge(Edge(and_node, "http://arrow", node)) + self.add_edge(Edge(and_node, "http://arrow", negated)) + + or_node = adapter.create_object_union_of(node.owl_class, negated.owl_class) + or_node = Node(owl_class = or_node) + self.add_edge(Edge(top_node, "http://arrow", or_node)) + self.add_edge(Edge(node, "http://arrow", or_node)) + self.add_edge(Edge(negated, "http://arrow", or_node)) + + self.add_node(node.nnf()) + + + def add_edge(self, edge): + src = edge.src + dst = edge.dst + self.add_node(src) + self.add_node(dst) + + self.out_edges[src].add(dst) + self.in_edges[dst].add(src) + + def add_all_edges(self, *edges): + for edge in edges: + self.add_edge(edge) + + def as_edgelist(self): + edges = [] + for source, targets in self.out_edges.items(): + for target in targets: + edges.append((source, target)) + return edges + + def as_str_edgelist(self): + edges = [] + for source, targets in self.out_edges.items(): + source = str(source) + for target in targets: + target = str(target) + edges.append((source, "http://arrow", target)) + return edges + + def as_edges(self): + for source, targets in self.out_edges.items(): + for target in targets: + yield Edge(source, "http://arrow", target) + + def _lemma_6(self): + # First equation, nothing to do + # Second equation and left side of third equation and left side of fourth equation + negated_nodes = set() + for node in self.nodes: + if node.is_negated() and not node.negated_domain: + negated_nodes.add(node) + + for neg_node in negated_nodes: + in_edges = list(self.in_edges[neg_node]) + for in_node in in_edges: + if in_node.is_owl_nothing() or in_node.is_owl_thing(): + continue + if in_node.domain: + continue + if node == in_node: + continue + + node = neg_node.get_operand() + neg_in_node = in_node.negate() + op_edge = Edge(node, "saturation_lemma6", neg_in_node) + + intersection_owl = adapter.create_object_intersection_of(node.owl_class, in_node.owl_class) + if len(intersection_owl.getNNF().getOperandsAsList()) == 1: + continue + + intersection = Node(owl_class = intersection_owl) + + assert node.domain == intersection.domain, f"Domain of {node} is {node.domain} and domain of {intersection} is {intersection.domain}" + assert node.codomain == intersection.codomain, f"Codomain of {node} is {node.codomain} and codomain of {intersection} is {intersection.codomain}" + assert node.negated_domain == intersection.negated_domain, f"Negated domain of {node} is {node.negated_domain} and negated domain of {intersection} is {intersection.negated_domain}" + edge_int = Edge(intersection, "saturation_lemma6", bot_node) + + out_edges = list(self.out_edges[neg_node]) + for out_node in out_edges: + if out_node.is_owl_thing() or out_node.is_owl_nothing(): + continue + if out_node.domain: + continue + if node == out_node: + continue + node = neg_node.get_operand() + + union = adapter.create_object_union_of(node.owl_class, out_node.owl_class) + if len(union.getNNF().getOperandsAsList()) == 1: + continue + + union = Node(owl_class = union) + edge_un = Edge(top_node, "saturation_lemma6", union) + + self.add_all_edges(op_edge, edge_int, edge_un) + + #TODO last equation (Morgan law) + + # Left side of third equation + intersection_nodes = set() + + for node in self.nodes: + if node.is_intersection(): + intersection_nodes.add(node) + + for int_node in intersection_nodes: + if not bot_node in self.out_edges[int_node]: + continue + + operands = list(int_node.owl_class.getOperandsAsList()) + intersection_pairs = pairs(operands) + + for node1, node2 in intersection_pairs: + node1 = [n for n in node1 if not n.isOWLThing()] if len(node1) > 1 else node1 + node2 = [n for n in node2 if not n.isOWLThing()] if len(node2) > 1 else node2 + if len(node1) > 1: + node1 = adapter.create_object_intersection_of(*node1) + else: + node1 = node1[0] + node1 = Node(owl_class = node1) + if len(node2) > 1: + node2 = adapter.create_object_intersection_of(*node2) + else: + node2 = node2[0] + node2 = Node(owl_class = node2.getObjectComplementOf()) + edge = Edge(node1, "saturation_lemma6", node2) + self.add_edge(edge) + + # Left side of fourth equation + union_nodes = set() + for node in self.nodes: + if node.is_union(): + + union_nodes.add(node) + + for un_node in union_nodes: + + if not top_node in self.in_edges[un_node]: + continue + + operands = un_node.owl_class.getOperandsAsList() + union_pairs = pairs(operands) + for node1, node2 in union_pairs: + node1 = [n for n in node1 if not n.isOWLNothing()] if len(node1) > 1 else node1 + node2 = [n for n in node2 if not n.isOWLNothing()] if len(node2) > 1 else node2 + if len(node1) > 1: + node1 = adapter.create_object_union_of(*node1) + else: + node1 = node1[0] + node1 = Node(owl_class = node1.getObjectComplementOf()) + + if len(node2) > 1: + node2 = adapter.create_object_union_of(*node2) + else: + node2 = node2[0] + + node2 = Node(owl_class = node2) + + edge = Edge(node1, "saturation_lemma6", node2) + self.add_edge(edge) + + + + def _definition_6(self): + # Def 6. Although it is not defined explicitely in the paper, this definition will look for classes that are subclass of a disjointness. + for node in self.nodes: + if node.in_relation_category(): + continue + if node.is_owl_thing() or node.is_owl_nothing(): + continue + + node_to_neg = dict() + for out_node in self.out_edges[node]: + if out_node.domain or out_node.codomain: + continue + if out_node.is_owl_thing() or out_node.is_owl_nothing(): + continue + + + if not out_node in node_to_neg and not out_node.negate() in node_to_neg: + node_to_neg[out_node] = None + elif out_node in node_to_neg: + continue + elif out_node.negate() in node_to_neg: + node_to_neg[out_node.negate()] = out_node + + for node1, node2 in node_to_neg.items(): + if node2 is None: + continue + else: + intersection = adapter.create_object_intersection_of(node1.owl_class, node2.owl_class) + intersection = Node(owl_class = intersection) + edge = Edge(intersection, "saturation_lemma6", bot_node) + self.add_edge(edge) + edge2 = Edge(node, "saturation_lemma6", intersection) + self.add_edge(edge2) + + + def _definition_7(self): + #Last equation, other equations are covered in lemma 8 + relations = set() + for node in self.nodes: + if node.is_whole_relation(): + relations.add(node) + + for rel in relations: + #print("\tWhole relation: {}".format(rel)) + for in_rel in self.in_edges[rel]: + if not in_rel.in_relation_category(): + continue + in_rel_codomain = in_rel.to_codomain() + if not in_rel_codomain in self.nodes: + continue + + #print(f"\t\tIn relation: {in_rel}") + #print(f"\t\tIn relation codomain: {in_rel_codomain}") + for cod in self.out_edges[in_rel_codomain]: + if cod.domain or cod.codomain or cod.in_relation_category(): + continue + #print(f"\t\t\tCodomain: {cod}") + in_rel_domain = in_rel.to_domain() + + ex_rel_cod = adapter.create_object_some_values_from(rel.relation, cod.owl_class) + node = Node(owl_class = ex_rel_cod, relation = rel.relation, domain = True) + edge = Edge(in_rel_domain, "saturation_definition7", node) + #print(f"\t\t\t\tAdding edge: {edge.src} -> {edge.dst}") + self.add_edge(edge) + + + + + + def _lemma_8(self): + existential_nodes = set() + for node in self.nodes: + if node.is_existential(): + existential_nodes.add(node) + + for node in existential_nodes: + filler = node.owl_class.getFiller() + property_ = node.owl_class.getProperty() + filler_node = Node(owl_class = filler) + domain_node = Node(owl_class = node.owl_class, relation = property_, domain = True) + + + in_edges = list(self.in_edges[filler_node]) + for in_filler in in_edges: + ex_in_class = adapter.create_object_some_values_from(node.owl_class.getProperty(), in_filler.owl_class) + ex_in_node = Node(owl_class = ex_in_class) + edge = Edge(ex_in_node, "saturation_lemma8", node) + self.add_edge(edge) + + domain_in_node = Node(owl_class=ex_in_class, relation = property_, domain = True) + edge = Edge(domain_in_node, "saturation_lemma8", domain_node) + self.add_edge(edge) + edge = Edge(domain_in_node, "saturation_lemma8", ex_in_node) + self.add_edge(edge) + edge = Edge(ex_in_node, "saturation_lemma8", domain_in_node) + self.add_edge(edge) + + out_edges = list(self.out_edges[filler_node]) + for out_filler in out_edges: + if out_filler.owl_class is None: + continue + if out_filler.is_owl_nothing(): + continue + edge = Edge(node, "saturation_lemma8", bot_node) + self.add_edge(edge) + else: + ex_out_class = adapter.create_object_some_values_from(node.owl_class.getProperty(), out_filler.owl_class) + ex_out_node = Node(owl_class = ex_out_class) + edge = Edge(node, "saturation_lemma8", ex_out_node) + self.add_edge(edge) + + domain_out_node = Node(owl_class=ex_out_class, relation = property_, domain = True) + edge = Edge(domain_node, "saturation_lemma8", domain_out_node) + self.add_edge(edge) + edge = Edge(domain_out_node, "saturation_lemma8", ex_out_node) + self.add_edge(edge) + edge = Edge(ex_out_node, "saturation_lemma8", domain_out_node) + self.add_edge(edge) + + + def as_nx(self): + logger.debug("Converting to networkx") + G = nx.DiGraph() + G.add_nodes_from(list(self.node_to_id.values())) + for edge in self.as_edgelist(): + src, dst = edge + + if src.in_object_category() and dst.in_object_category(): + if src == bot_node or dst == top_node: + continue + G.add_edge(self.node_to_id[src], self.node_to_id[dst]) + logger.debug("Done converting to networkx") + return G + + def transitive_closure(self): + logger.debug("Computing transitive closure") + G = self.as_nx() + G = nx.transitive_closure(G) + logger.debug("Done computing transitive closure in NetworkX") + for src, dst in tqdm(G.edges()): + src = self.id_to_node[src] + dst = self.id_to_node[dst] + self.add_edge(Edge(src, "transitive_closure", dst)) + logger.debug("Done computing transitive closure") + + + def saturate(self): + self._definition_6() + self._lemma_6() + self._definition_7() + self._lemma_8() + + def is_unsatisfiable(self, node): + if not isinstance(node, Node): + raise TypeError("node must be of type Node") + if node not in self.nodes: + raise ValueError("Node is not in graph") + out_edges = self.out_edges[node] + if bot_node in out_edges: + return True + else: + return False + + def get_unsatisfiable_nodes(self): + + def is_trivial_unsat(node): + trivial = False + if node.domain or node.codomain or node.in_relation_category(): + return trivial + + owl_class = node.owl_class + if owl_class.getClassExpressionType() == CT.OBJECT_INTERSECTION_OF: + ops = owl_class.getOperandsAsList() + if len(ops) == 2: + op1, op2 = tuple(ops) + if op2.getClassExpressionType() == CT.OBJECT_COMPLEMENT_OF: + op2 = op2.getOperand() + if op1.equals(op2): + trivial = True + if op1.getClassExpressionType() == CT.OBJECT_COMPLEMENT_OF: + op1 = op1.getOperand() + if op2.equals(op1): + trivial = True + return trivial + + unsat = set() + for node in self._in_edges[bot_node]: + if is_trivial_unsat(node): + continue + unsat.add(node) + + return unsat + + + def write_to_file(self, outfile): + with open(outfile, "w") as f: + edges = self.as_str_edgelist() + for src, rel, dst in tqdm(edges): + f.write(f"{src}\t{rel}\t{dst}\n") + +@versionadded(version="0.3.0") +class CategoricalProjector(ProjectionModel): + """ + Implementation of projection rules defined in [zhapa2023]_. + + This class implements the projection of ALC axioms into a graph using categorical diagrams. + + :param saturation_steps: Number of saturation steps of the graph/category. + :type saturation_steps: int + :param transitive_closure: If ``True``, every saturation step computes the transitive edges. + :type transitive_closure: bool + """ + + def __init__(self, saturation_steps = 0, transitive_closure = False): + + if not isinstance(saturation_steps, int): + raise TypeError("Optional parameter saturation_steps must be of type int") + if saturation_steps < 0: + raise ValueError("Optional parameter saturation_steps must be non-negative") + if not isinstance(transitive_closure, bool): + raise TypeError("Optional parameter transitive_closure must be of type bool") + + + self.adapter = OWLAPIAdapter() + self.ont_manager = self.adapter.owl_manager + self.data_factory = self.adapter.data_factory + self.graph = Graph() + + def project(self, ontology): + r"""Generates the projection of the ontology. + + :param ontology: The ontology to be processed. + :type ontology: :class:`org.semanticweb.owlapi.model.OWLOntology` + """ + + + if not isinstance(ontology, OWLOntology): + raise TypeError( + "Parameter ontology must be of type org.semanticweb.owlapi.model.OWLOntology") + + + all_classes = ontology.getClassesInSignature() + for cls in tqdm(all_classes): + self.graph.add_node(Node(owl_class = cls)) + all_axioms = ontology.getAxioms(True) + + for axiom in tqdm(all_axioms, total = len(all_axioms)): + self.graph.add_all_edges(*list(self.process_axiom(axiom))) + + return self.graph.as_edges() + + def process_axiom(self, axiom): + """Process an OWLAxiom and return a list of edges. + + :param axiom: Axiom to be parsed. + :type axiom: :class:`org.semanticweb.owlapi.model.OWLAxiom` + """ + + axiom_type = axiom.getAxiomType() + if axiom_type == AxiomType.SUBCLASS_OF: + return self._process_subclassof(axiom) + elif axiom_type == AxiomType.EQUIVALENT_CLASSES: + return self._process_equivalent_classes(axiom) + elif axiom_type == AxiomType.DISJOINT_CLASSES: + return self._process_disjointness(axiom) + elif axiom_type == AxiomType.CLASS_ASSERTION: + return self._process_class_assertion(axiom) + elif axiom_type == AxiomType.OBJECT_PROPERTY_ASSERTION: + return self._process_object_property_assertion(axiom) + elif axiom_type in IGNORED_AXIOM_TYPES: + #Ignore these types of axioms + return [] + else: + print(f"process_axiom: Unknown axiom type: {axiom_type}") + return [] + + + def _process_equivalent_classes(self, axiom): + """Process an EquivalentClasses axiom and return a list of edges.""" + + subclass_axioms = axiom.asOWLSubClassOfAxioms() + edges = set() + for subclass_axiom in subclass_axioms: + edges |= self._process_subclassof(subclass_axiom) + + if edges == set(): + print(f"No edges found for EquivalentClasses axiom: {axiom}") + return edges + + def _process_disjointness(self, axiom): + """Process a disjointness axiom""" + subclass_axioms = axiom.asOWLSubClassOfAxioms() + edges = set() + for subclass_axiom in subclass_axioms: + edges |= self._process_subclassof(subclass_axiom) + + return edges + + def _process_class_assertion(self, axiom): + """Process a class assertion axiom""" + individual = axiom.getIndividual() + class_expression = axiom.getClassExpression() + + node, edges = self._process_expression_and_get_complex_node(class_expression) + ind_as_class = adapter.create_class(str(individual.toStringID())) + ind_node = Node(owl_class = ind_as_class, individual=True) + edges.add(self._assertion_arrow(ind_node, node)) + + return edges + + def _process_object_property_assertion(self, axiom): + """Process an object property assertion axiom""" + subject = axiom.getSubject() + object_ = axiom.getObject() + property_ = axiom.getProperty() + + subject_as_class = adapter.create_class(str(subject.toStringID())) + object_as_class = adapter.create_class(str(object_.toStringID())) + subject_node = Node(owl_class = subject_as_class, individual=True) + object_node = Node(owl_class = object_as_class, individual = True) + codomain_rel = Node(owl_class=None, relation=property_, codomain=True) + domain_rel = Node(owl_class=None, relation=property_, domain=True) + rel_node = Node(owl_class=None, relation=property_) + + edges = set() + edges.add(self._assertion_arrow(subject_node, domain_rel)) + edges.add(self._assertion_arrow(object_node, codomain_rel)) + edges.add(self._domain_functor(rel_node, domain_rel)) + edges.add(self._codomain_functor(rel_node, codomain_rel)) + return edges + + def _process_subclassof(self, axiom): + """Process a SubClassOf axiom and return a list of edges.""" + sub_class = axiom.getSubClass() + super_class = axiom.getSuperClass() + sub_edges = set() + super_edges = set() + + sub_node, sub_edges = self._process_expression_and_get_complex_node(sub_class) + #print(axiom) + super_node, super_edges = self._process_expression_and_get_complex_node(super_class) + + if (sub_node is None) or (super_node is None): + return set() + + edges = set() + edges.add(self._subsumption_arrow(sub_node, super_node)) + edges |= sub_edges + edges |= super_edges + + not_sub_class = self.adapter.create_complement_of(sub_class) + union = self.adapter.create_object_union_of(not_sub_class, super_class) + union_complex_node, union_edges = self._process_expression_and_get_complex_node(union) + + + + edges |= union_edges + edges.add(self._subsumption_arrow(top_node, union_complex_node)) + + return edges + + + def _process_expression_and_get_complex_node(self, expression): + + expr_type = expression.getClassExpressionType() + + edges = set() + + if expr_type == CT.OWL_CLASS: + return Node(owl_class=expression), edges + + if expr_type == CT.OBJECT_INTERSECTION_OF: + prod_complex_node = expression + operands = expression.getOperandsAsList() + + for op in operands: + op_complex_node, op_edges = self._process_expression_and_get_complex_node(op) + if op_complex_node is None: + return None, set() + + edges |= op_edges + edges.add(self._product_limit_arrow(prod_complex_node, op_complex_node)) + + #TODO: add arrows to fulfill distributivity + + return prod_complex_node, edges + + elif expr_type == CT.OBJECT_UNION_OF: + coprod_complex_node = expression + operands = expression.getOperandsAsList() + + for op in operands: + op_complex_node, op_edges = self._process_expression_and_get_complex_node(op) + if op_edges == None: + return None, set() + + edges |= op_edges + edges.add(self._coproduct_limit_arrow(op_complex_node, + coprod_complex_node)) + + + return coprod_complex_node, edges + + elif expr_type == CT.OBJECT_SOME_VALUES_FROM: + + existential_complex_node = expression + + property_ = expression.getProperty() + + if not isinstance(property_, OWLObjectInverseOf): + if property_.getEntityType() != EntityType.OBJECT_PROPERTY: + return None, set() + else: + if property_.isNamed(): + property_ = property_.asOWLObject() + else: + return None, set() + + filler = expression.getFiller() + filler_info = self._process_expression_and_get_complex_node(filler) + filler_node, filler_edges = filler_info + if filler_node is None: + return None, set() + + edges |= filler_edges + + + rel_exists_r_c = Node(owl_class = expression, relation=property_) + codomain_rel_exists_r_c = Node(owl_class=expression, relation=property_, codomain=True) + domain_rel_exists_r_c = Node(owl_class=expression, relation=property_, domain=True) + + edges.add(Edge(rel_exists_r_c, "http://general_arrow", Node(relation=property_))) + edges.add(Edge(codomain_rel_exists_r_c, "http://general_arrow", Node(owl_class=filler))) + + exist_node = Node(owl_class=existential_complex_node) + edges.add(Edge(domain_rel_exists_r_c, "http://general_arrow", exist_node)) + edges.add(Edge(exist_node, "http://general_arrow", domain_rel_exists_r_c)) + + return existential_complex_node, edges + + + elif expr_type == CT.OBJECT_ALL_VALUES_FROM: + universal_complex_node = expression + + property_ = expression.getProperty() + filler = expression.getFiller() + not_filler = self.adapter.create_complement_of(filler) + rel_not_filler = self.adapter.create_object_some_values_from(property_, not_filler) + not_rel_not_filler = self.adapter.create_complement_of(rel_not_filler) + + not_rel_not_filler_info = self._process_expression_and_get_complex_node(not_rel_not_filler) + not_rel_not_filler_node, not_rel_not_filler_edges = not_rel_not_filler_info + if not_rel_not_filler_node is None: + return None, set() + + filler_info = self._process_expression_and_get_complex_node(filler) + filler_node, filler_edges = filler_info + if filler_node is None: + return None, set() + + edges |= not_rel_not_filler_edges + edges |= filler_edges + + edges.add(self._subsumption_arrow(universal_complex_node, not_rel_not_filler_node)) + edges.add(self._subsumption_arrow(not_rel_not_filler_node, universal_complex_node)) + + not_domain_rel_n_filler = Node(owl_class=rel_not_filler, relation=property_, domain=True, negated_domain=True) + edges.add(self._subsumption_arrow(not_domain_rel_n_filler, universal_complex_node)) + edges.add(self._subsumption_arrow(universal_complex_node, not_domain_rel_n_filler)) + return universal_complex_node, edges + + elif expr_type == CT.OBJECT_COMPLEMENT_OF: + + negation_complex_node = expression + operand = expression.getOperand() + + operand_info = self._process_expression_and_get_complex_node(operand) + operand_node, operand_edges = operand_info + + union = self.adapter.create_object_union_of(expression, operand) + union = Node(owl_class = union) + intersection = self.adapter.create_object_intersection_of(expression, operand) + intersection = Node(owl_class=intersection) + edges |= operand_edges + + edges.add(self._product_limit_arrow(intersection, operand_node)) + edges.add(self._product_limit_arrow(intersection, negation_complex_node)) + edges.add(self._coproduct_limit_arrow(operand_node, union)) + edges.add(self._coproduct_limit_arrow(negation_complex_node, union)) + + edges.add(Edge(intersection, "http://general_arrow", bot_node)) + edges.add(Edge(top_node, "http://general_arrow", union)) + + return negation_complex_node, edges + + elif expr_type in IGNORED_EXPRESSION_TYPES: + return None, set() + else: + print("process expression and get complex node: Unknown super class type: {}".format(expression)) + + + ####################### MORPHISMS ########################### + + # decorator that transforms params as Node + def _node_params(func): + def wrapper(self, src, dst): + if not isinstance(src, Node): + src = Node(owl_class=src) + if not isinstance(dst, Node): + dst = Node(owl_class=dst) + return func(self, src, dst) + return wrapper + + @_node_params + def _subsumption_arrow(self, src, dst): + rel = "http://subsumption_arrow" + return Edge(src, rel, dst) + + @_node_params + def _coproduct_limit_arrow(self, src, dst): + rel = "http://injects" + return Edge(src, rel, dst) + + @_node_params + def _product_limit_arrow(self, src, dst): + rel = "http://projects" + return Edge(src, "http://projects", dst) + + @_node_params + def _assertion_arrow(self, src, dst): + rel = "http://type_arrow" + return Edge(src, rel, dst) + + @_node_params + def _domain_functor(self, src, dst): + rel = "http://domain_functor" + return Edge(src, rel, dst) + + @_node_params + def _codomain_functor(self, src, dst): + rel = "http://codomain_functor" + return Edge(src, rel, dst) + +def add_extra_existential_axioms(ontology): + adapter = OWLAPIAdapter() + manager = adapter.owl_manager + + classes = ontology.getClassesInSignature() + roles = ontology.getObjectPropertiesInSignature() + + bot_class = adapter.create_class(BOT) + top_class = adapter.create_class(TOP) + + + axioms = HashSet() + for role in roles: + for cls in classes: + existential = adapter.create_object_some_values_from(role, cls) + axiom1 = adapter.create_subclass_of(existential, top_class) + axiom2 = adapter.create_subclass_of(bot_class, existential) + axioms.add(axiom1) + axioms.add(axiom2) + + print(f"Axioms before: {len(ontology.getAxioms())}") + manager.addAxioms(ontology, axioms) + print(f"Axioms after: {len(ontology.getAxioms())}") + + + + + diff --git a/mowl/projection/categorical/utils.py b/mowl/projection/categorical/utils.py new file mode 100644 index 00000000..33af1e71 --- /dev/null +++ b/mowl/projection/categorical/utils.py @@ -0,0 +1,139 @@ +import torch as th +import random +import os +import numpy as np +from itertools import chain, combinations, product + +from org.semanticweb.owlapi.model import AxiomType as ax +from org.semanticweb.owlapi.model import ClassExpressionType as ct + + +def powerset(iterable): + "powerset([1,2,3]) --> () (1,) (2,) (3,) (1,2) (1,3) (2,3) (1,2,3)" + s = list(iterable) + return chain.from_iterable(combinations(s, r) for r in range(len(s)+1)) + +def pairs(iterable): + num_items = len(iterable) + power_set = list(powerset(iterable)) + product_set = list(product(power_set, power_set)) + + curated_set = [] + for i1, i2 in product_set: + if i1 == i2: + continue + if len(i1) + len(i2) != num_items: + continue + if len(i1) == 0 or len(i1) == num_items: + continue + if len(i2) == 0 or len(i2) == num_items: + continue + curated_set.append((i1, i2)) + + return curated_set + +def seed_everything(seed=42): + random.seed(seed) + os.environ['PYTHONHASHSEED'] = str(seed) + np.random.seed(seed) + th.manual_seed(seed) + th.cuda.manual_seed(seed) + th.cuda.manual_seed_all(seed) + th.backends.cudnn.deterministic = True + th.backends.cudnn.benchmark = False + + + +IGNORED_AXIOM_TYPES = [ax.ANNOTATION_ASSERTION, + ax.ASYMMETRIC_OBJECT_PROPERTY, + ax.DECLARATION, + ax.EQUIVALENT_OBJECT_PROPERTIES, + ax.FUNCTIONAL_OBJECT_PROPERTY, + ax.INVERSE_FUNCTIONAL_OBJECT_PROPERTY, + ax.INVERSE_OBJECT_PROPERTIES, + ax.IRREFLEXIVE_OBJECT_PROPERTY, + ax.OBJECT_PROPERTY_DOMAIN, + ax.OBJECT_PROPERTY_RANGE, + ax.REFLEXIVE_OBJECT_PROPERTY, + ax.SUB_PROPERTY_CHAIN_OF, + ax.SUB_ANNOTATION_PROPERTY_OF, + ax.SUB_OBJECT_PROPERTY, + ax.SWRL_RULE, + ax.SYMMETRIC_OBJECT_PROPERTY, + ax.TRANSITIVE_OBJECT_PROPERTY + ] + +IGNORED_EXPRESSION_TYPES = [ct.OBJECT_EXACT_CARDINALITY, + ct.OBJECT_MIN_CARDINALITY, + ct.OBJECT_HAS_SELF, + ct.OBJECT_HAS_VALUE, + ct.OBJECT_ONE_OF, + ct.DATA_EXACT_CARDINALITY, + ct.DATA_MIN_CARDINALITY, + ct.DATA_HAS_VALUE, + ct.DATA_SOME_VALUES_FROM, + ct.DATA_MAX_CARDINALITY, + ct.OBJECT_MAX_CARDINALITY, + ct.DATA_ALL_VALUES_FROM + ] + +class FastTensorDataLoader: + """ + A DataLoader-like object for a set of tensors that can be much faster than + TensorDataset + DataLoader because dataloader grabs individual indices of + the dataset and calls cat (slow). + Source: https://discuss.pytorch.org/t/dataloader-much-slower-than-manual-batching/27014/6 + """ + + def __init__(self, *tensors, batch_size=32, shuffle=False): + """ + Initialize a FastTensorDataLoader. + :param *tensors: tensors to store. All tensors must have the same size at dimension 0. + :param batch_size: batch size to load. Defaults to 32. + :type batch_size: int, optional + :param shuffle: if True, shuffle the data *in-place* whenever an + iterator is created out of this object. Defaults to False. + :type shuffle: bool, optional + """ + + # Type checking + if not all(isinstance(t, th.Tensor) for t in tensors): + raise TypeError("All non-optional parameters must be Tensors") + + if not isinstance(batch_size, int): + raise TypeError("Optional parameter batch_size must be of type int") + + if not isinstance(shuffle, bool): + raise TypeError("Optional parameter shuffle must be of type bool") + + assert all(t.shape[0] == tensors[0].shape[0] for t in tensors) + self.tensors = tensors + + self.dataset_len = self.tensors[0].shape[0] + self.batch_size = batch_size + self.shuffle = shuffle + + # Calculate # batches + n_batches, remainder = divmod(self.dataset_len, self.batch_size) + if remainder > 0: + n_batches += 1 + self.n_batches = n_batches + + def __iter__(self): + if self.shuffle: + r = th.randperm(self.dataset_len) + self.tensors = [t[r] for t in self.tensors] + self.i = 0 + return self + + def __next__(self): + if self.i >= self.dataset_len: + raise StopIteration + batch = tuple(t[self.i:self.i + self.batch_size] for t in self.tensors) + self.i += self.batch_size + return batch + + def __len__(self): + return self.n_batches + + diff --git a/mowl/projection/dl2vec/model.py b/mowl/projection/dl2vec/model.py index 8369fb92..e32f5700 100644 --- a/mowl/projection/dl2vec/model.py +++ b/mowl/projection/dl2vec/model.py @@ -7,12 +7,13 @@ class DL2VecProjector(ProjectionModel): ''' - :param ontology: The ontology to be processed. - :type ontology: :class:`org.semanticweb.owlapi.model.OWLOntology` + Implementation of projection rules defined in [chen2020]_. + .. include:: extra/dl2vec.rst + :param bidirectional_taxonomy: If true then per each SubClass edge one SuperClass edge will be generated. - :type bidirectional_taxonomy: bool + :type bidirectional_taxonomy: bool, optional ''' def __init__(self, bidirectional_taxonomy: bool = False): diff --git a/mowl/projection/owl2vec_star/model.py b/mowl/projection/owl2vec_star/model.py index 0b9deee4..7dec17ed 100644 --- a/mowl/projection/owl2vec_star/model.py +++ b/mowl/projection/owl2vec_star/model.py @@ -5,18 +5,18 @@ class OWL2VecStarProjector(ProjectionModel): - ''' - :param ontology: The ontology to be processed. - :type ontology: :class:`org.semanticweb.owlapi.model.OWLOntology` - :param bidirectional_taxonomy: If true then per each SubClass edge one SuperClass edge will \ - be generated. Default is False. - :type bidirectional_taxonomy: bool - :param include_literals: If true the graph will also include triples involving data property \ - assertions and annotations. Default is False. - :type include_literals: bool - :param only_taxonomy: If true, the projection will only include subClass edges - :type only_taxonomy: bool + Implementation of projection rules defined in [chen2020b]_. + + .. include:: extra/owl2vec.rst + + + :param bidirectional_taxonomy: If ``True`` then per each SubClass edge one SuperClass edge will be generated. Default is False. + :type bidirectional_taxonomy: bool, optional + :param include_literals: If ``True`` the graph will also include triples involving data property assertions and annotations. Default is False. + :type include_literals: bool, optional + :param only_taxonomy: If ``True``, the projection will only include subClass edges + :type only_taxonomy: bool, optional ''' def __init__(self, bidirectional_taxonomy=False, only_taxonomy=False, include_literals=False): @@ -36,6 +36,12 @@ def __init__(self, bidirectional_taxonomy=False, only_taxonomy=False, include_li self.include_literals) def project(self, ontology): + r"""Generates the projection of the ontology. + + :param ontology: The ontology to be processed. + :type ontology: :class:`org.semanticweb.owlapi.model.OWLOntology` + """ + if not isinstance(ontology, OWLOntology): raise TypeError( "Parameter ontology must be of type org.semanticweb.owlapi.model.OWLOntology") diff --git a/mowl/projection/taxonomy/model.py b/mowl/projection/taxonomy/model.py index 267b430c..c34b0f59 100644 --- a/mowl/projection/taxonomy/model.py +++ b/mowl/projection/taxonomy/model.py @@ -8,12 +8,10 @@ class TaxonomyProjector(ProjectionModel): ''' - This class will project the ontology considering only the axioms of the form - :math:`A \sqsubseteq B` where A and B are ontology classes. + Projection of axioms :math:`A \sqsubseteq B`. :param ontology: The ontology to be processed. - :param bidirectional_taxonomy: If true then per each SubClass edge one SuperClass edge will - be generated. + :param bidirectional_taxonomy: If true then per each SubClass edge one SuperClass edge wil be generated. ''' def __init__(self, bidirectional_taxonomy: bool = False): @@ -24,6 +22,12 @@ def __init__(self, bidirectional_taxonomy: bool = False): self.projector = Projector(bidirectional_taxonomy) def project(self, ontology): + r"""Generates the projection of the ontology. + + :param ontology: The ontology to be processed. + :type ontology: :class:`org.semanticweb.owlapi.model.OWLOntology` + """ + if not isinstance(ontology, OWLOntology): raise TypeError("Parameter ontology must be of \ type org.semanticweb.owlapi.model.OWLOntology") @@ -31,7 +35,4 @@ def project(self, ontology): edges = [Edge(str(e.src()), str(e.rel()), str(e.dst())) for e in edges] return edges - def projectWithTransClosure(self, ontology): - edges = self.projector.projectWithTransClosure(ontology) - edges = [Edge(str(e.src()), str(e.rel()), str(e.dst())) for e in edges] - return edges + diff --git a/mowl/projection/taxonomy_rels/model.py b/mowl/projection/taxonomy_rels/model.py index d88d72d5..fce6e8ca 100644 --- a/mowl/projection/taxonomy_rels/model.py +++ b/mowl/projection/taxonomy_rels/model.py @@ -10,7 +10,7 @@ class TaxonomyWithRelationsProjector(ProjectionModel): r''' - This class will project the ontology considering the following axioms: + Projection of axioms :math:`A \sqsubseteq B` and :math:`A \sqsubseteq \exists R.B`. * :math:`A \sqsubseteq B` will generate the triple :math:`\langle A, subClassOf, B \rangle` @@ -18,7 +18,8 @@ class TaxonomyWithRelationsProjector(ProjectionModel): :math:`\left\langle A, R, B \right\rangle` - :param ontology: The ontology to be processed. + :param taxonomy: If ``True`` taxonomy axioms will be included. + :type taxonomy: :param bidirectional_taxonomy: If true then per each SubClass edge one SuperClass edge will \ be generated. ''' @@ -48,6 +49,12 @@ def __init__(self, taxonomy=False, bidirectional_taxonomy: bool = False, relatio self.projector = Projector(taxonomy, bidirectional_taxonomy, relationsJ) def project(self, ontology): + r"""Generates the projection of the ontology. + + :param ontology: The ontology to be processed. + :type ontology: :class:`org.semanticweb.owlapi.model.OWLOntology` + """ + if not isinstance(ontology, OWLOntology): raise TypeError('Parameter ontology must be of type \ org.semanticweb.owlapi.model.OWLOntology') diff --git a/mowl/walking/deepwalk/model.py b/mowl/walking/deepwalk/model.py index 30c90c55..02cc5d55 100644 --- a/mowl/walking/deepwalk/model.py +++ b/mowl/walking/deepwalk/model.py @@ -16,9 +16,8 @@ class DeepWalk(WalkingModel): ''' - Implementation of DeepWalk based on \ - - + Implementation of DeepWalk based on [perozzi2014]_. + :param alpha: Probability of restart, defaults to 0 :type alpha: float, optional ''' diff --git a/mowl/walking/node2vec/model.py b/mowl/walking/node2vec/model.py index b4b8b368..b9a6f8c1 100644 --- a/mowl/walking/node2vec/model.py +++ b/mowl/walking/node2vec/model.py @@ -14,6 +14,8 @@ class Node2Vec(WalkingModel): ''' + Implementation of DeepWalk based on [grover2016]_. + :param p: Return hyperparameter. Default is 1. :type p: float :param q: In-out hyperparameter. Default is 1. diff --git a/mowl/walking/walking.py b/mowl/walking/walking.py index 6c84f001..7c341a63 100644 --- a/mowl/walking/walking.py +++ b/mowl/walking/walking.py @@ -5,6 +5,8 @@ class WalkingModel(): ''' + Base class for walking methods. + :param num_walks: Number of walks per node :type num_walks: int :param walk_length: Length of each walk diff --git a/readthedocs.yml b/readthedocs.yml index 27988f75..eb2406d6 100644 --- a/readthedocs.yml +++ b/readthedocs.yml @@ -11,12 +11,13 @@ build: apt_packages: - openjdk-11-jre - openjdk-11-jdk + - graphviz jobs: pre_install: - ./gradle_install.sh # - ./build_jars.sh tools: - python: "miniconda3-4.7" + python: "mambaforge-22.9" # You can also specify other tool versions: # nodejs: "16" # rust: "1.55" @@ -33,4 +34,4 @@ sphinx: # Optionally declare the Python requirements required to build your docs conda: - environment: docs/environment.yml \ No newline at end of file + environment: docs/environment.yml diff --git a/setup.py b/setup.py index fbdcaebf..6ed22fd6 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setuptools.setup( name="mowl-borg", - version="0.2.1", + version="0.3.0", author="Bio-Ontology Research Group", author_email="fernando.zhapacamacho@kaust.edu.sa", description="mOWL: A machine learning library with ontologies", diff --git a/tests/base_models/test_elmodel.py b/tests/base_models/test_elmodel.py index 52fc202b..be5e9e48 100644 --- a/tests/base_models/test_elmodel.py +++ b/tests/base_models/test_elmodel.py @@ -1,6 +1,6 @@ from unittest import TestCase from mowl.base_models.elmodel import EmbeddingELModel -from mowl.base_models.model import EmbeddingModel +from mowl.base_models.model import Model from mowl.datasets import Dataset from tests.datasetFactory import FamilyDataset, PPIYeastSlimDataset from mowl.datasets.el import ELDataset @@ -17,9 +17,9 @@ def setUpClass(self): self.ppi_dataset = PPIYeastSlimDataset() def test_instance_of_embedding_model(self): - """This checks if the class EmbeddingELModel is a subclass of EmbeddingModel""" + """This checks if the class EmbeddingELModel is a subclass of Model""" model = EmbeddingELModel(self.family_dataset, 1, False) - self.assertTrue(isinstance(model, EmbeddingModel)) + self.assertTrue(isinstance(model, Model)) def test_constructor_param_types(self): """This checks if the constructor parameters are of the correct type""" diff --git a/tests/base_models/test_model.py b/tests/base_models/test_model.py index dd707481..cf250707 100644 --- a/tests/base_models/test_model.py +++ b/tests/base_models/test_model.py @@ -73,21 +73,3 @@ def test_temporary_model_filepath(self): import tempfile tmppath = tempfile.gettempdir() self.assertTrue(model_filepath.startswith(tmppath)) - - -class TestEmbeddingModel(TestCase): - - @classmethod - def setUpClass(self): - self.dataset = FamilyDataset() - - def test_is_instance_of_model(self): - """This checks if EmbeddingModel is instance of Model""" - - self.assertIsInstance(mowl.base_models.EmbeddingModel(self.dataset), Model) - - def test_get_embedding_data_not_implemented_error(self): - """This checks if EmbeddingModel.get_embedding_data method raises NotImplementedError""" - - model = mowl.base_models.EmbeddingModel(self.dataset) - self.assertRaises(NotImplementedError, model.get_embeddings_data) diff --git a/tests/projection/fixtures/cate_family.csv b/tests/projection/fixtures/cate_family.csv new file mode 100644 index 00000000..c2e3791f --- /dev/null +++ b/tests/projection/fixtures/cate_family.csv @@ -0,0 +1,431 @@ +#Axioms +http://Father,http://Male +http://Father,http://Parent +http://Female,http://Person +http://Male,http://Person +http://Mother,http://Female +http://Mother,http://Parent +http://Parent,http://Person +http://Parent,http://hasChild some owl:Thing +http://Female and http://Male,owl:Nothing +http://Female and http://Parent,http://Mother +http://Male and http://Parent,http://Father +http://hasChild some http://Person,http://Parent +#Identity +http://Father,http://Father +http://Mother,http://Mother +http://Male,http://Male +http://Female,http://Female +http://Person,http://Person +http://Parent,http://Parent +http://Female and http://Male,http://Female and http://Male +http://Female and http://Parent,http://Female and http://Parent +http://Male and http://Parent,http://Male and http://Parent +http://hasChild some http://Person,http://hasChild some http://Person +http://hasChild some owl:Thing,http://hasChild some owl:Thing +#Initial object +owl:Nothing,http://Father +owl:Nothing,http://Mother +owl:Nothing,http://Male +owl:Nothing,http://Female +owl:Nothing,http://Person +owl:Nothing,http://Parent +owl:Nothing,http://Female and http://Male +owl:Nothing,http://Female and http://Parent +owl:Nothing,http://Male and http://Parent +owl:Nothing,http://hasChild some http://Person +owl:Nothing,http://hasChild some owl:Thing +#Terminal object +http://Father,owl:Thing +http://Mother,owl:Thing +http://Male,owl:Thing +http://Female,owl:Thing +http://Person,owl:Thing +http://Parent,owl:Thing +http://Female and http://Male,owl:Thing +http://Female and http://Parent,owl:Thing +http://Male and http://Parent,owl:Thing +http://hasChild some http://Person,owl:Thing +http://hasChild some owl:Thing,owl:Thing +#Axiom 1: Father -> Male +not http://Father,not http://Father +http://Father and not http://Father,http://Father and not http://Father +http://Father or not http://Father,http://Father or not http://Father +http://Male or not http://Father,http://Male or not http://Father +owl:Thing,http://Male or not http://Father +owl:Nothing,not http://Father +owl:Nothing,http://Father and not http://Father +owl:Nothing,http://Father or not http://Father +owl:Nothing,http://Male or not http://Father +not http://Father,owl:Thing +http://Father and not http://Father,owl:Thing +http://Father or not http://Father,owl:Thing +http://Male or not http://Father,owl:Thing +http://Father and not http://Father,owl:Nothing +http://Father and not http://Father,http://Father +http://Father and not http://Father,not http://Father +owl:Thing,http://Father or not http://Father +http://Father,http://Father or not http://Father +not http://Father,http://Father or not http://Father +not http://Father,http://Male or not http://Father +http://Male,http://Male or not http://Father +#Axiom 2: Father -> Parent +http://Parent or not http://Father,http://Parent or not http://Father +owl:Thing,http://Parent or not http://Father +owl:Nothing,http://Parent or not http://Father +http://Parent or not http://Father,owl:Thing +not http://Father,http://Parent or not http://Father +http://Parent,http://Parent or not http://Father +#Axiom 3: Female -> Person +not http://Female,not http://Female +http://Female and not http://Female,http://Female and not http://Female +http://Female or not http://Female,http://Female or not http://Female +http://Person or not http://Female,http://Person or not http://Female +owl:Thing,http://Person or not http://Female +owl:Nothing,not http://Female +owl:Nothing,http://Female and not http://Female +owl:Nothing,http://Female or not http://Female +owl:Nothing,http://Person or not http://Female +not http://Female,owl:Thing +http://Female and not http://Female,owl:Thing +http://Female or not http://Female,owl:Thing +http://Person or not http://Female,owl:Thing +http://Female and not http://Female,owl:Nothing +http://Female and not http://Female,http://Female +http://Female and not http://Female,not http://Female +owl:Thing,http://Female or not http://Female +http://Female,http://Female or not http://Female +not http://Female,http://Female or not http://Female +not http://Female,http://Person or not http://Female +http://Person,http://Person or not http://Female +#Axiom 4: Male -> Person +not http://Male,not http://Male +http://Male and not http://Male,http://Male and not http://Male +http://Male or not http://Male,http://Male or not http://Male +http://Person or not http://Male,http://Person or not http://Male +owl:Thing,http://Person or not http://Male +owl:Nothing,not http://Male +owl:Nothing,http://Male and not http://Male +owl:Nothing,http://Male or not http://Male +owl:Nothing,http://Person or not http://Male +not http://Male,owl:Thing +http://Male and not http://Male,owl:Thing +http://Male or not http://Male,owl:Thing +http://Person or not http://Male,owl:Thing +http://Male and not http://Male,owl:Nothing +http://Male and not http://Male,http://Male +http://Male and not http://Male,not http://Male +owl:Thing,http://Male or not http://Male +http://Male,http://Male or not http://Male +not http://Male,http://Male or not http://Male +not http://Male,http://Person or not http://Male +http://Person,http://Person or not http://Male +#Axiom 5: Mother -> Female +not http://Mother,not http://Mother +http://Mother and not http://Mother,http://Mother and not http://Mother +http://Mother or not http://Mother,http://Mother or not http://Mother +http://Female or not http://Mother,http://Female or not http://Mother +owl:Thing,http://Female or not http://Mother +owl:Nothing,not http://Mother +owl:Nothing,http://Mother and not http://Mother +owl:Nothing,http://Mother or not http://Mother +owl:Nothing,http://Female or not http://Mother +not http://Mother,owl:Thing +http://Mother and not http://Mother,owl:Thing +http://Mother or not http://Mother,owl:Thing +http://Female or not http://Mother,owl:Thing +http://Mother and not http://Mother,owl:Nothing +http://Mother and not http://Mother,http://Mother +http://Mother and not http://Mother,not http://Mother +owl:Thing,http://Mother or not http://Mother +http://Mother,http://Mother or not http://Mother +not http://Mother,http://Mother or not http://Mother +not http://Mother,http://Female or not http://Mother +http://Female,http://Female or not http://Mother +#Axiom 6: Mother -> Parent +http://Parent or not http://Mother,http://Parent or not http://Mother +owl:Thing,http://Parent or not http://Mother +owl:Nothing,http://Parent or not http://Mother +http://Parent or not http://Mother,owl:Thing +not http://Mother,http://Parent or not http://Mother +http://Parent,http://Parent or not http://Mother +#Axiom 7: Parent -> Person +not http://Parent,not http://Parent +http://Parent and not http://Parent,http://Parent and not http://Parent +http://Parent or not http://Parent,http://Parent or not http://Parent +http://Person or not http://Parent,http://Person or not http://Parent +owl:Thing,http://Person or not http://Parent +owl:Nothing,not http://Parent +owl:Nothing,http://Parent and not http://Parent +owl:Nothing,http://Parent or not http://Parent +owl:Nothing,http://Person or not http://Parent +not http://Parent,owl:Thing +http://Parent and not http://Parent,owl:Thing +http://Parent or not http://Parent,owl:Thing +http://Person or not http://Parent,owl:Thing +http://Parent and not http://Parent,owl:Nothing +http://Parent and not http://Parent,http://Parent +http://Parent and not http://Parent,not http://Parent +owl:Thing,http://Parent or not http://Parent +http://Parent,http://Parent or not http://Parent +not http://Parent,http://Parent or not http://Parent +not http://Parent,http://Person or not http://Parent +http://Person,http://Person or not http://Parent +#Axiom 8: Parent -> hasChild some owl:Thing +not http://Parent,not http://Parent +http://Parent and not http://Parent,http://Parent and not http://Parent +http://Parent or not http://Parent,http://Parent or not http://Parent +not http://Parent or http://hasChild some owl:Thing,not http://Parent or http://hasChild some owl:Thing +owl:Thing,not http://Parent or http://hasChild some owl:Thing +owl:Nothing,not http://Parent +owl:Nothing,http://Parent and not http://Parent +owl:Nothing,http://Parent or not http://Parent +owl:Nothing,not http://Parent or http://hasChild some owl:Thing +not http://Parent,owl:Thing +http://Parent and not http://Parent,owl:Thing +http://Parent or not http://Parent,owl:Thing +not http://Parent or http://hasChild some owl:Thing,owl:Thing +http://Parent and not http://Parent,owl:Nothing +http://Parent and not http://Parent,http://Parent +http://Parent and not http://Parent,not http://Parent +owl:Thing,http://Parent or not http://Parent +http://Parent,http://Parent or not http://Parent +not http://Parent,http://Parent or not http://Parent +not http://Parent,not http://Parent or http://hasChild some owl:Thing +http://hasChild some owl:Thing,not http://Parent or http://hasChild some owl:Thing +#Axiom 9: Female and Male -> owl:Nothing +not http://Female and http://Male,not http://Female and http://Male +http://Female and http://Male and not http://Female and http://Male,http://Female and http://Male and not http://Female and http://Male +http://Female and http://Male or not http://Female and http://Male,http://Female and http://Male or not http://Female and http://Male +owl:Thing,not http://Female and http://Male +owl:Nothing,not http://Female and http://Male +owl:Nothing,http://Female and http://Male and not http://Female and http://Male +owl:Nothing,http://Female and http://Male or not http://Female and http://Male +not http://Female and http://Male,owl:Thing +http://Female and http://Male and not http://Female and http://Male,owl:Thing +http://Female and http://Male or not http://Female and http://Male,owl:Thing +http://Female and http://Male and not http://Female and http://Male,owl:Nothing +http://Female and http://Male and not http://Female and http://Male,http://Female and http://Male +http://Female and http://Male and not http://Female and http://Male,not http://Female and http://Male +owl:Thing,http://Female and http://Male or not http://Female and http://Male +http://Female and http://Male,http://Female and http://Male or not http://Female and http://Male +not http://Female and http://Male,http://Female and http://Male or not http://Female and http://Male +#Axiom 9 NNF: Female and Male -> owl:Nothing +not http://Female or not http://Male,not http://Female or not http://Male +http://Female and http://Male and not http://Female or not http://Male,http://Female and http://Male and not http://Female or not http://Male +http://Female and http://Male or not http://Female or not http://Male,http://Female and http://Male or not http://Female or not http://Male +owl:Nothing,not http://Female or not http://Male +owl:Nothing,http://Female and http://Male and not http://Female or not http://Male +owl:Nothing,http://Female and http://Male or not http://Female or not http://Male +not http://Female or not http://Male,owl:Thing +http://Female and http://Male and not http://Female or not http://Male,owl:Thing +http://Female and http://Male or not http://Female or not http://Male,owl:Thing +#Axiom 10: Female and Parent -> Mother +not http://Female and http://Parent,not http://Female and http://Parent +http://Female and http://Parent and not http://Female and http://Parent,http://Female and http://Parent and not http://Female and http://Parent +http://Female and http://Parent or not http://Female and http://Parent,http://Female and http://Parent or not http://Female and http://Parent +http://Mother or not http://Female and http://Parent,http://Mother or not http://Female and http://Parent +owl:Thing,http://Mother or not http://Female and http://Parent +owl:Nothing,not http://Female and http://Parent +owl:Nothing,http://Female and http://Parent and not http://Female and http://Parent +owl:Nothing,http://Female and http://Parent or not http://Female and http://Parent +owl:Nothing,http://Mother or not http://Female and http://Parent +not http://Female and http://Parent,owl:Thing +http://Female and http://Parent and not http://Female and http://Parent,owl:Thing +http://Female and http://Parent or not http://Female and http://Parent,owl:Thing +http://Mother or not http://Female and http://Parent,owl:Thing +http://Female and http://Parent and not http://Female and http://Parent,owl:Nothing +http://Female and http://Parent and not http://Female and http://Parent,http://Female and http://Parent +http://Female and http://Parent and not http://Female and http://Parent,not http://Female and http://Parent +owl:Thing,http://Female and http://Parent or not http://Female and http://Parent +http://Female and http://Parent,http://Female and http://Parent or not http://Female and http://Parent +not http://Female and http://Parent,http://Female and http://Parent or not http://Female and http://Parent +not http://Female and http://Parent,http://Mother or not http://Female and http://Parent +http://Mother,http://Mother or not http://Female and http://Parent +#Axiom 10 NNF: Female and Parent -> Mother +not http://Female or not http://Parent,not http://Female or not http://Parent +http://Female and http://Parent and not http://Female or not http://Parent,http://Female and http://Parent and not http://Female or not http://Parent +http://Female and http://Parent or not http://Female or not http://Parent,http://Female and http://Parent or not http://Female or not http://Parent +http://Mother or not http://Female or not http://Parent,http://Mother or not http://Female or not http://Parent +owl:Nothing,not http://Female or not http://Parent +owl:Nothing,http://Female and http://Parent and not http://Female or not http://Parent +owl:Nothing,http://Female and http://Parent or not http://Female or not http://Parent +owl:Nothing,http://Mother or not http://Female or not http://Parent +not http://Female or not http://Parent,owl:Thing +http://Female and http://Parent and not http://Female or not http://Parent,owl:Thing +http://Female and http://Parent or not http://Female or not http://Parent,owl:Thing +http://Mother or not http://Female or not http://Parent,owl:Thing +#Axiom 11: Male and Parent -> Father +not http://Male and http://Parent,not http://Male and http://Parent +http://Male and http://Parent and not http://Male and http://Parent,http://Male and http://Parent and not http://Male and http://Parent +http://Male and http://Parent or not http://Male and http://Parent,http://Male and http://Parent or not http://Male and http://Parent +http://Father or not http://Male and http://Parent,http://Father or not http://Male and http://Parent +owl:Thing,http://Father or not http://Male and http://Parent +owl:Nothing,not http://Male and http://Parent +owl:Nothing,http://Male and http://Parent and not http://Male and http://Parent +owl:Nothing,http://Male and http://Parent or not http://Male and http://Parent +owl:Nothing,http://Father or not http://Male and http://Parent +not http://Male and http://Parent,owl:Thing +http://Male and http://Parent and not http://Male and http://Parent,owl:Thing +http://Male and http://Parent or not http://Male and http://Parent,owl:Thing +http://Father or not http://Male and http://Parent,owl:Thing +http://Male and http://Parent and not http://Male and http://Parent,owl:Nothing +http://Male and http://Parent and not http://Male and http://Parent,http://Male and http://Parent +http://Male and http://Parent and not http://Male and http://Parent,not http://Male and http://Parent +owl:Thing,http://Male and http://Parent or not http://Male and http://Parent +http://Male and http://Parent,http://Male and http://Parent or not http://Male and http://Parent +not http://Male and http://Parent,http://Male and http://Parent or not http://Male and http://Parent +not http://Male and http://Parent,http://Father or not http://Male and http://Parent +http://Father,http://Father or not http://Male and http://Parent +#Axiom 11 NNF: Male and Parent -> Father +not http://Male or not http://Parent,not http://Male or not http://Parent +http://Male and http://Parent and not http://Male or not http://Parent,http://Male and http://Parent and not http://Male or not http://Parent +http://Male and http://Parent or not http://Male or not http://Parent,http://Male and http://Parent or not http://Male or not http://Parent +http://Father or not http://Male or not http://Parent,http://Father or not http://Male or not http://Parent +owl:Nothing,not http://Male or not http://Parent +owl:Nothing,http://Male and http://Parent and not http://Male or not http://Parent +owl:Nothing,http://Male and http://Parent or not http://Male or not http://Parent +owl:Nothing,http://Father or not http://Male or not http://Parent +not http://Male or not http://Parent,owl:Thing +http://Male and http://Parent and not http://Male or not http://Parent,owl:Thing +http://Male and http://Parent or not http://Male or not http://Parent,owl:Thing +http://Father or not http://Male or not http://Parent,owl:Thing +#Axiom 12: hasChild some Person -> Parent +not http://hasChild some http://Person,not http://hasChild some http://Person +not http://hasChild some http://Person and http://hasChild some http://Person,not http://hasChild some http://Person and http://hasChild some http://Person +not http://hasChild some http://Person or http://hasChild some http://Person,not http://hasChild some http://Person or http://hasChild some http://Person +http://Parent or not http://hasChild some http://Person,http://Parent or not http://hasChild some http://Person +owl:Thing,http://Parent or not http://hasChild some http://Person +owl:Nothing,not http://hasChild some http://Person +owl:Nothing,not http://hasChild some http://Person and http://hasChild some http://Person +owl:Nothing,not http://hasChild some http://Person or http://hasChild some http://Person +owl:Nothing,http://Parent or not http://hasChild some http://Person +not http://hasChild some http://Person,owl:Thing +not http://hasChild some http://Person and http://hasChild some http://Person,owl:Thing +not http://hasChild some http://Person or http://hasChild some http://Person,owl:Thing +http://Parent or not http://hasChild some http://Person,owl:Thing +not http://hasChild some http://Person and http://hasChild some http://Person,owl:Nothing +not http://hasChild some http://Person and http://hasChild some http://Person,http://hasChild some http://Person +not http://hasChild some http://Person and http://hasChild some http://Person,not http://hasChild some http://Person +owl:Thing,not http://hasChild some http://Person or http://hasChild some http://Person +http://hasChild some http://Person,not http://hasChild some http://Person or http://hasChild some http://Person +not http://hasChild some http://Person,not http://hasChild some http://Person or http://hasChild some http://Person +not http://hasChild some http://Person,http://Parent or not http://hasChild some http://Person +http://Parent,http://Parent or not http://hasChild some http://Person +#Axiom 12 NNF: hasChild some Person -> Parent +http://hasChild only not http://Person,http://hasChild only not http://Person +http://hasChild some http://Person and http://hasChild only not http://Person,http://hasChild some http://Person and http://hasChild only not http://Person +http://hasChild some http://Person or http://hasChild only not http://Person,http://hasChild some http://Person or http://hasChild only not http://Person +http://Parent or http://hasChild only not http://Person,http://Parent or http://hasChild only not http://Person +owl:Nothing,http://hasChild only not http://Person +owl:Nothing,http://hasChild some http://Person and http://hasChild only not http://Person +owl:Nothing,http://hasChild some http://Person or http://hasChild only not http://Person +owl:Nothing,http://Parent or http://hasChild only not http://Person +http://hasChild only not http://Person,owl:Thing +http://hasChild some http://Person and http://hasChild only not http://Person,owl:Thing +http://hasChild some http://Person or http://hasChild only not http://Person,owl:Thing +http://Parent or http://hasChild only not http://Person,owl:Thing +#Projections +http://Female and http://Male,http://Female +http://Female and http://Male,http://Male +http://Female and http://Parent,http://Female +http://Female and http://Parent,http://Parent +http://Male and http://Parent,http://Male +http://Male and http://Parent,http://Parent +#Existential +http://hasChild_under_http://hasChild some http://Person,http://hasChild +CODOMAIN_http://hasChild_under_http://hasChild some http://Person,http://Person +DOMAIN_http://hasChild_under_http://hasChild some http://Person,http://hasChild some http://Person +http://hasChild some http://Person,DOMAIN_http://hasChild_under_http://hasChild some http://Person +http://hasChild_under_http://hasChild some owl:Thing,http://hasChild +CODOMAIN_http://hasChild_under_http://hasChild some owl:Thing,owl:Thing +DOMAIN_http://hasChild_under_http://hasChild some owl:Thing,http://hasChild some owl:Thing +http://hasChild_under_http://hasChild some http://Person,http://hasChild_under_http://hasChild some http://Person +owl:Nothing,http://hasChild_under_http://hasChild some http://Person +http://hasChild_under_http://hasChild some http://Person,owl:Thing +CODOMAIN_http://hasChild_under_http://hasChild some http://Person,CODOMAIN_http://hasChild_under_http://hasChild some http://Person +owl:Nothing,CODOMAIN_http://hasChild_under_http://hasChild some http://Person +CODOMAIN_http://hasChild_under_http://hasChild some http://Person,owl:Thing +DOMAIN_http://hasChild_under_http://hasChild some http://Person,DOMAIN_http://hasChild_under_http://hasChild some http://Person +owl:Nothing,DOMAIN_http://hasChild_under_http://hasChild some http://Person +DOMAIN_http://hasChild_under_http://hasChild some http://Person,owl:Thing +http://hasChild some owl:Thing,DOMAIN_http://hasChild_under_http://hasChild some owl:Thing +http://hasChild_under_http://hasChild some owl:Thing,http://hasChild_under_http://hasChild some owl:Thing +owl:Nothing,http://hasChild_under_http://hasChild some owl:Thing +http://hasChild_under_http://hasChild some owl:Thing,owl:Thing +CODOMAIN_http://hasChild_under_http://hasChild some owl:Thing,CODOMAIN_http://hasChild_under_http://hasChild some owl:Thing +owl:Nothing,CODOMAIN_http://hasChild_under_http://hasChild some owl:Thing +CODOMAIN_http://hasChild_under_http://hasChild some owl:Thing,owl:Thing +DOMAIN_http://hasChild_under_http://hasChild some owl:Thing,DOMAIN_http://hasChild_under_http://hasChild some owl:Thing +owl:Nothing,DOMAIN_http://hasChild_under_http://hasChild some owl:Thing +DOMAIN_http://hasChild_under_http://hasChild some owl:Thing,owl:Thing +#Not Person (from axiom 12) +not http://Person,not http://Person +owl:Nothing,not http://Person +not http://Person,owl:Thing +http://Person and not http://Person,http://Person and not http://Person +owl:Nothing,http://Person and not http://Person +http://Person and not http://Person,http://Person +http://Person and not http://Person,not http://Person +http://Person and not http://Person,owl:Thing +http://Person and not http://Person,owl:Nothing +http://Person or not http://Person,http://Person or not http://Person +owl:Nothing,http://Person or not http://Person +http://Person,http://Person or not http://Person +not http://Person,http://Person or not http://Person +http://Person or not http://Person,owl:Thing +owl:Thing,http://Person or not http://Person +#Role +http://hasChild,http://hasChild +owl:Nothing,http://hasChild +http://hasChild,owl:Thing +#General +owl:Nothing,owl:Thing +#From ABox +# identity +http://John,http://John +http://Mary,http://Mary +http://Peter,http://Peter +DOMAIN_http://hasChild_under_http://hasChild some http://Male,DOMAIN_http://hasChild_under_http://hasChild some http://Male +CODOMAIN_http://hasChild_under_http://hasChild some http://Male,CODOMAIN_http://hasChild_under_http://hasChild some http://Male +http://hasChild some http://Male,http://hasChild some http://Male +# initial +owl:Nothing,http://John +owl:Nothing,http://Mary +owl:Nothing,http://Peter +owl:Nothing,DOMAIN_http://hasChild_under_http://hasChild some http://Male +owl:Nothing,CODOMAIN_http://hasChild_under_http://hasChild some http://Male +owl:Nothing,http://hasChild some http://Male +# terminal +http://John,owl:Thing +http://Mary,owl:Thing +http://Peter,owl:Thing +DOMAIN_http://hasChild_under_http://hasChild some http://Male,owl:Thing +CODOMAIN_http://hasChild_under_http://hasChild some http://Male,owl:Thing +http://hasChild some http://Male,owl:Thing +## exists hasChild. Male (John) +http://hasChild_under_http://hasChild some http://Male,http://hasChild +CODOMAIN_http://hasChild_under_http://hasChild some http://Male,http://Male +DOMAIN_http://hasChild_under_http://hasChild some http://Male,http://hasChild some http://Male +http://hasChild some http://Male,DOMAIN_http://hasChild_under_http://hasChild some http://Male +http://hasChild_under_http://hasChild some http://Male,http://hasChild_under_http://hasChild some http://Male +owl:Nothing,http://hasChild_under_http://hasChild some http://Male +http://hasChild_under_http://hasChild some http://Male,owl:Thing +CODOMAIN_http://hasChild_under_http://hasChild some http://Male,CODOMAIN_http://hasChild_under_http://hasChild some http://Male +owl:Nothing,CODOMAIN_http://hasChild_under_http://hasChild some http://Male +CODOMAIN_http://hasChild_under_http://hasChild some http://Male,owl:Thing +DOMAIN_http://hasChild_under_http://hasChild some http://Male,DOMAIN_http://hasChild_under_http://hasChild some http://Male +http://John,http://hasChild some http://Male +## hasChild(John,Mary) +owl:Nothing,DOMAIN_http://hasChild +DOMAIN_http://hasChild,owl:Thing +DOMAIN_http://hasChild,DOMAIN_http://hasChild +owl:Nothing,CODOMAIN_http://hasChild +CODOMAIN_http://hasChild,owl:Thing +CODOMAIN_http://hasChild,CODOMAIN_http://hasChild +http://John,DOMAIN_http://hasChild +http://Mary,CODOMAIN_http://hasChild +http://hasChild,DOMAIN_http://hasChild +http://hasChild,CODOMAIN_http://hasChild +## Male(Peter) +http://Peter,http://Male diff --git a/tests/projection/test_cate.py b/tests/projection/test_cate.py new file mode 100644 index 00000000..dbac4c67 --- /dev/null +++ b/tests/projection/test_cate.py @@ -0,0 +1,86 @@ +from tests.datasetFactory import FamilyDataset +from mowl.projection import CategoricalProjector +from mowl.owlapi.defaults import TOP, BOT +from mowl.owlapi import OWLAPIAdapter + +from org.semanticweb.owlapi.model import IRI +from unittest import TestCase + + +class TestCat(TestCase): + + @classmethod + def setUpClass(self): + dataset = FamilyDataset() + self.ontology = dataset.ontology + + + adapter = OWLAPIAdapter() + data_factory = adapter.data_factory + ont_manager = adapter.owl_manager + + ind1 = data_factory.getOWLNamedIndividual(IRI.create("http://John")) + ind2 = data_factory.getOWLNamedIndividual(IRI.create("http://Mary")) + ind3 = data_factory.getOWLNamedIndividual(IRI.create("http://Peter")) + role = data_factory.getOWLObjectProperty(IRI.create("http://hasChild")) + class1 = data_factory.getOWLClass(IRI.create("http://Male")) + + expression = data_factory.getOWLObjectSomeValuesFrom(role, class1) + axiom = data_factory.getOWLClassAssertionAxiom(expression, ind1) + + # Individual, relation, individual + + axiom2 = data_factory.getOWLObjectPropertyAssertionAxiom(role, + ind1, + ind2) + axiom3 = data_factory.getOWLClassAssertionAxiom(class1, ind3) + + + ont_manager.addAxiom(self.ontology, axiom) + ont_manager.addAxiom(self.ontology, axiom2) + ont_manager.addAxiom(self.ontology, axiom3) + + + + + def test_constructor_parameter_types(self): + """This should check if the constructor parameters are of the correct type""" + self.assertRaisesRegex( + TypeError, "Optional parameter saturation_steps must be of type int", + CategoricalProjector, "0") + self.assertRaisesRegex( + ValueError, "Optional parameter saturation_steps must be non-negative", + CategoricalProjector, -1) + self.assertRaisesRegex( + TypeError, "Optional parameter transitive_closure must be of type bool", + CategoricalProjector, 1, {"a"}) + + + def test_project_method_parameter_types(self): + """This should check if the project method parameters are of the correct type""" + projector = CategoricalProjector() + self.assertRaisesRegex( + TypeError, + "Parameter ontology must be of type org.semanticweb.owlapi.model.OWLOntology", + projector.project, "True") + + def test_project_family_ontology(self): + """This should check if the projection result is correct""" + projector = CategoricalProjector() + edges = projector.project(self.ontology) + edges = set([e.astuple() for e in edges]) + + ground_truth_edges = set() + with open("tests/projection/fixtures/cate_family.csv") as f: + lines = f.readlines() + for line in lines: + if line.startswith("#"): + continue + line = line.strip() + sub, super_ = line.split(",") + ground_truth_edges.add(edge(sub, super_)) + + self.assertEqual(set(edges), ground_truth_edges) + +def edge(a, b): + return (a, "http://arrow", b)