Repository: ontodev/robot
Pull request #778 (2020-11-26 11:25:30)

New command: measure
Resolves [#627]
The original pull request was here: https://github.com/ontodev/robot/pull/774

I replaced it to trigger jenkins etc.

- [x] `docs/` have been added/updated
- [x] tests have been added/updated
- [x] `mvn verify` says all tests pass
- [x] `mvn site` says all JavaDocs correct
- [x] `CHANGELOG.md` has been updated

[robot.jar](https://build.obolibrary.io/job/ontodev/job/robot/job/cmd-metrics/lastSuccessfulBuild/artifact/bin/robot.jar) for testing.

For the community, the most relevant question will be which of the metrics should go in which bin (essential, extended, advanced):

### Essential metrics

#### Entity counts
- SIGNATURE_SIZE_INCL
- SIGNATURE_SIZE
- CLASS_COUNT_INCL
- CLASS_COUNT
- OBJPROPERTY_COUNT_INCL
- OBJPROPERTY_COUNT
- INDIVIDUAL_COUNT_INCL
- INDIVIDUAL_COUNT
- DATAPROPERTY_COUNT_INCL
- DATAPROPERTY_COUNT
- ANNOTATION_PROP_COUNT
- ANNOTATION_PROP_COUNT_INCL
- DATATYPE_COUNT_INCL
- DATATYPE_COUNT

#### Axiom counts
- LOGICAL_AXIOM_COUNT
- LOGICAL_AXIOM_COUNT_INCL
- AXIOM_COUNT
- AXIOM_COUNT_INCL
- TBOX_SIZE
- TBOX_SIZE_INCL
- TBOXRBOX_SIZE
- TBOXRBOX_SIZE_INCL
- RULE_CT
- RULE_CT_INCL
- RBOX_SIZE
- RBOX_SIZE_INCL
- ABOX_SIZE
- ABOX_SIZE_INCL

#### OWL Profile metrics
- BOOL_PROFILE_OWL
- BOOL_PROFILE_OWL
- BOOL_PROFILE_OWL
- BOOL_PROFILE_OWL
- BOOL_PROFILE_OWL
- BOOL_PROFILE_RDFS
- VIOLATION_PROFILE_OWL

#### Other metrics
- OWLAPI_VERSION
- ONTOLOGY_ID
- ONTOLOGY_ANNOTATIONS_COUNT
- VALID_IMPORTS
- VALID_IMPORTS_INCL

### Extended metrics
#### Extended entity metrics
- DATATYPE_BUILTIN_COUNT_INCL
- DATATYPE_BUILTIN_COUNT
- DATATYPE_NOTBUILTIN_COUNT_INCL
- DATATYPE_NOTBUILTIN_COUNT

#### Logical expressivity
- EXPRESSIVITY
- EXPRESSIVITY_INCL
- CONSTRUCTS_INCL
- CONSTRUCTS
- AXIOM_TYPES
- AXIOM_TYPES_INCL
- AXIOMTYPE_COUNT
- AXIOMTYPE_COUNT_INCL
- SYNTAX

#### Entity usage
- NS_USE_AXIOMS
- NS_USE_AXIOMS_INCL
- NS_USE_SIGNATURE
- NS_USE_SIGNATURE_INCL

### Advanced metrics (all)
- MAX_AXIOMLENGTH
- MAX_AXIOMLENGTH_INCL
- AVG_ASSERT_N_SUBCLASS_INCL
- AVG_ASSERT_N_SUBCLASS
- AVG_ASSERT_N_SUPERCLASS_INCL
- AVG_ASSERT_N_SUPERCLASS
- AVG_INSTANCE_PER_CLASS_INCL
- AVG_INSTANCE_PER_CLASS
- CLASS_SGL_SUBCLASS_COUNT_INCL
- CLASS_SGL_SUBCLASS_COUNT
- AXIOM_COMPLEXRHS_COUNT_INCL
- AXIOM_COMPLEXRHS_COUNT
- TBOX_CONTAINS_NOMINALS_INCL
- TBOX_CONTAINS_NOMINALS
- ABOX_CONTAINS_NOMINALS_INCL
- ABOX_CONTAINS_NOMINALS
- GCI_COUNT_INCL
- GCI_COUNT
- GCI_HIDDEN_COUNT_INCL
- GCI_HIDDEN_COUNT
- MAX_NUM_NAMED_SUPERCLASS_INCL
- MAX_NUM_NAMED_SUPERCLASS
- MULTI_INHERITANCE_COUNT_INCL
- MULTI_INHERITANCE_COUNT
- REF_CLASS_COUNT
- REF_DATAPROP_COUNT_INCL
- REF_DATAPROP_COUNT
- REF_INDIV_COUNT_INCL
- REF_INDIV_COUNT
- REF_OBJPROP_COUNT_INCL
- REF_OBJPROP_COUNT
- UNDECLARED_ENTITY_COUNT_INCL
- UNDECLARED_ENTITY_COUNT
- TAUTOLOGYCOUNT
- TAUTOLOGYCOUNT_INCL
- CYCLE
- CYCLE_INCL
- CYCLE
- CYCLE
- DATATYPE_AXIOMCOUNT
- DATATYPE_AXIOMCOUNT_INCL
- DATATYPES
- DATATYPES_INCL
- DATATYPES_NOT_BUILT_IN
- DATATYPES_NOT_BUILT_IN_INCL
- MOST_FRQUENTLY_USED_CONCEPT_INCL
- MOST_FRQUENTLY_USED_CONCEPT



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8126ec20..5be8855f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,8 +8,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 ## [Unreleased]
 
 ### Added
+- Add [`measure`] command [#774]
 - Added 'strict' mode for loading ontologies in [#788]
 - Add --allow-missing-entities option in [`rename`] command [#793]
+- Add `html-list` format option to [`export`] in [#703]
 
 ### Changed
 - Explanations for inconsistent and unsatisfiable classes in [`explain`] [#779]
@@ -221,6 +223,7 @@ First official release of ROBOT!
 [`export`]: http://robot.obolibrary.org/export
 [`extract`]: http://robot.obolibrary.org/extract
 [`filter`]: http://robot.obolibrary.org/filter
+[`measure`]: http://robot.obolibrary.org/measure
 [`merge`]: http://robot.obolibrary.org/merge
 [`python`]: http://robot.obolibrary.org/python
 [`query`]: http://robot.obolibrary.org/query
diff --git a/docs/examples/metrics.tsv b/docs/examples/metrics.tsv
new file mode 100644
index 00000000..39b02c9f
--- /dev/null
+++ b/docs/examples/metrics.tsv
@@ -0,0 +1,38 @@
+metric	metric_value	metric_type
+abox_axiom_count	0	single_value
+abox_axiom_count_incl	0	single_value
+annotation_property_count	38	single_value
+annotation_property_count_incl	38	single_value
+axiom_count	5504	single_value
+axiom_count_incl	5504	single_value
+class_count	236	single_value
+class_count_incl	236	single_value
+dataproperty_count	0	single_value
+dataproperty_count_incl	0	single_value
+datatypes_count	2	single_value
+datatypes_count_incl	2	single_value
+individual_count	0	single_value
+individual_count_incl	0	single_value
+logical_axiom_count	828	single_value
+logical_axiom_count_incl	828	single_value
+obj_property_count	49	single_value
+obj_property_count_incl	49	single_value
+ontology_anno_count	0	single_value
+ontology_iri	http://purl.obolibrary.org/obo/uberon.owl	single_value
+ontology_version_iri	no.iri	single_value
+owl2	true	single_value
+owl2_dl	true	single_value
+owl2_el	false	single_value
+owl2_ql	false	single_value
+owl2_rl	false	single_value
+rbox_axiom_count	56	single_value
+rbox_axiom_count_incl	56	single_value
+rdfs	false	single_value
+rule_count	0	single_value
+rule_count_incl	0	single_value
+signature_entity_count	325	single_value
+signature_entity_count_incl	325	single_value
+tbox_axiom_count	772	single_value
+tbox_axiom_count_incl	772	single_value
+tboxrbox_axiom_count	828	single_value
+tboxrbox_axiom_count_incl	828	single_value
diff --git a/docs/examples/metrics_all.json b/docs/examples/metrics_all.json
new file mode 100644
index 00000000..02f8eb57
--- /dev/null
+++ b/docs/examples/metrics_all.json
@@ -0,0 +1,225 @@
+{
+  "metrics": {
+    "abox_axiom_count": 0,
+    "abox_axiom_count_incl": 0,
+    "abox_nominals": false,
+    "abox_nominals_incl": false,
+    "annotation_property_count": 38,
+    "annotation_property_count_incl": 38,
+    "assert_n_subclass_avg": 1.305084745762712,
+    "assert_n_subclass_avg_incl": 1.305084745762712,
+    "assert_n_superclass_avg": 1.326271186440678,
+    "assert_n_superclass_avg_incl": 1.326271186440678,
+    "ax_complexrhs_count": 464,
+    "ax_complexrhs_count_incl": 464,
+    "axiom_count": 5504,
+    "axiom_count_incl": 5504,
+    "axiom_length_max": 8,
+    "axiom_length_max_incl": 8,
+    "certain_cycle": true,
+    "certain_cycle_incl": true,
+    "class_count": 236,
+    "class_count_incl": 236,
+    "class_sgl_subcl_count": 41,
+    "class_sgl_subcl_count_incl": 41,
+    "dataproperty_count": 0,
+    "dataproperty_count_incl": 0,
+    "datatypes_count": 2,
+    "datatypes_count_incl": 2,
+    "dt_builtin_count": 2,
+    "dt_builtin_count_incl": 2,
+    "dt_notbuiltin_count": 0,
+    "dt_notbuiltin_count_incl": 0,
+    "expressivity": "SRIQ",
+    "expressivity_incl": "SRIQ",
+    "gci_count": 0,
+    "gci_count_incl": 0,
+    "gci_hidden_count": 105,
+    "gci_hidden_count_incl": 105,
+    "individual_count": 0,
+    "individual_count_incl": 0,
+    "instance_per_class_avg": 0.0,
+    "instance_per_class_avg_incl": 0.0,
+    "logical_axiom_count": 828,
+    "logical_axiom_count_incl": 828,
+    "most_freq_concept": "http://purl.obolibrary.org/obo/UBERON_0007530",
+    "most_freq_concept_incl": "http://purl.obolibrary.org/obo/UBERON_0007530",
+    "multiple_inheritance_count": 103,
+    "multiple_inheritance_count_incl": 103,
+    "named_supercl_count_max": 1,
+    "named_supercl_count_max_incl": 1,
+    "obj_property_count": 49,
+    "obj_property_count_incl": 49,
+    "ontology_anno_count": 0,
+    "ontology_iri": "http://purl.obolibrary.org/obo/uberon.owl",
+    "ontology_version_iri": "no.iri",
+    "owl2": true,
+    "owl2_dl": true,
+    "owl2_el": false,
+    "owl2_ql": false,
+    "owl2_rl": false,
+    "rbox_axiom_count": 56,
+    "rbox_axiom_count_incl": 56,
+    "rdfs": false,
+    "rule_count": 0,
+    "rule_count_incl": 0,
+    "signature_entity_count": 325,
+    "signature_entity_count_incl": 325,
+    "syntax": "RDF/XML Syntax",
+    "tautology_count": 0,
+    "tautology_count_incl": 0,
+    "tbox_axiom_count": 772,
+    "tbox_axiom_count_incl": 772,
+    "tbox_nominals": false,
+    "tbox_nominals_incl": false,
+    "tboxrbox_axiom_count": 828,
+    "tboxrbox_axiom_count_incl": 828,
+    "undecl_entity_count": 5,
+    "undecl_entity_count_incl": 5,
+    "axiom_types": [
+      "AnnotationAssertion",
+      "EquivalentClasses",
+      "TransitiveObjectProperty",
+      "SubObjectPropertyOf",
+      "SymmetricObjectProperty",
+      "SubPropertyChainOf",
+      "Declaration",
+      "SubClassOf",
+      "InverseObjectProperties"
+    ],
+    "axiom_types_incl": [
+      "AnnotationAssertion",
+      "EquivalentClasses",
+      "TransitiveObjectProperty",
+      "SubObjectPropertyOf",
+      "SymmetricObjectProperty",
+      "SubPropertyChainOf",
+      "Declaration",
+      "SubClassOf",
+      "InverseObjectProperties"
+    ],
+    "constructs": [
+      "Q",
+      "R",
+      "S",
+      "I"
+    ],
+    "constructs_incl": [
+      "Q",
+      "R",
+      "S",
+      "I"
+    ],
+    "datatypes_builtin": [
+      "XSD_STRING",
+      "XSD_ANY_URI"
+    ],
+    "datatypes_builtin_incl": [
+      "XSD_STRING",
+      "XSD_ANY_URI"
+    ],
+    "datatypes_not_built_in": [],
+    "datatypes_not_built_in_incl": [],
+    "valid_imports": [],
+    "valid_imports_incl": [],
+    "axiom_type_count": {
+      "AnnotationAssertion": 4356,
+      "EquivalentClasses": 106,
+      "TransitiveObjectProperty": 12,
+      "SubObjectPropertyOf": 25,
+      "SymmetricObjectProperty": 1,
+      "SubPropertyChainOf": 11,
+      "Declaration": 320,
+      "SubClassOf": 666,
+      "InverseObjectProperties": 7
+    },
+    "axiom_type_count_incl": {
+      "AnnotationAssertion": 4356,
+      "EquivalentClasses": 106,
+      "TransitiveObjectProperty": 12,
+      "SubObjectPropertyOf": 25,
+      "SymmetricObjectProperty": 1,
+      "SubPropertyChainOf": 11,
+      "Declaration": 320,
+      "SubClassOf": 666,
+      "InverseObjectProperties": 7
+    },
+    "class_expression_count": {
+      "ObjectMinCardinality": 2,
+      "Class": 1903,
+      "ObjectUnionOf": 3,
+      "ObjectSomeValuesFrom": 486,
+      "ObjectIntersectionOf": 105,
+      "ObjectMaxCardinality": 2
+    },
+    "class_expression_count_incl": {
+      "ObjectMinCardinality": 2,
+      "Class": 1903,
+      "ObjectUnionOf": 3,
+      "ObjectSomeValuesFrom": 486,
+      "ObjectIntersectionOf": 105,
+      "ObjectMaxCardinality": 2
+    },
+    "curie_map": {
+      "oboInOwl": "http://www.geneontology.org/formats/oboInOwl#",
+      "IAO": "http://purl.obolibrary.org/obo/IAO_",
+      "UBERON": "http://purl.obolibrary.org/obo/UBERON_",
+      "xsd": "http://www.w3.org/2001/XMLSchema#",
+      "rdfs": "http://www.w3.org/2000/01/rdf-schema#",
+      "BFO": "http://purl.obolibrary.org/obo/BFO_",
+      "obo": "http://purl.obolibrary.org/obo/",
+      "RO": "http://purl.obolibrary.org/obo/RO_",
+      "foaf": "http://xmlns.com/foaf/0.1/",
+      "BSPO": "http://purl.obolibrary.org/obo/BSPO_"
+    },
+    "datatypes_axiom_count": {},
+    "datatypes_axiom_count_incl": {},
+    "namespace_axiom_count": {
+      "oboInOwl": 4819,
+      "IAO": 306,
+      "UBERON": 1903,
+      "rdfs": 308,
+      "BFO": 283,
+      "obo": 235,
+      "RO": 307,
+      "foaf": 56,
+      "BSPO": 29
+    },
+    "namespace_axiom_count_incl": {
+      "oboInOwl": 4819,
+      "IAO": 306,
+      "UBERON": 1903,
+      "rdfs": 308,
+      "BFO": 283,
+      "obo": 235,
+      "RO": 307,
+      "foaf": 56,
+      "BSPO": 29
+    },
+    "namespace_entity_count": {
+      "oboInOwl": 20,
+      "IAO": 3,
+      "UBERON": 236,
+      "xsd": 2,
+      "BFO": 4,
+      "rdfs": 3,
+      "RO": 36,
+      "obo": 13,
+      "foaf": 1,
+      "BSPO": 7
+    },
+    "namespace_entity_count_incl": {
+      "oboInOwl": 20,
+      "IAO": 3,
+      "UBERON": 236,
+      "xsd": 2,
+      "BFO": 4,
+      "rdfs": 3,
+      "RO": 36,
+      "obo": 13,
+      "foaf": 1,
+      "BSPO": 7
+    },
+    "owl2dl_profile_violation": {}
+  }
+}
\ No newline at end of file
diff --git a/docs/examples/metrics_essential.json b/docs/examples/metrics_essential.json
new file mode 100644
index 00000000..54784417
--- /dev/null
+++ b/docs/examples/metrics_essential.json
@@ -0,0 +1,44 @@
+{
+  "metrics": {
+    "abox_axiom_count": 0,
+    "abox_axiom_count_incl": 0,
+    "annotation_property_count": 38,
+    "annotation_property_count_incl": 38,
+    "axiom_count": 5504,
+    "axiom_count_incl": 5504,
+    "class_count": 236,
+    "class_count_incl": 236,
+    "dataproperty_count": 0,
+    "dataproperty_count_incl": 0,
+    "datatypes_count": 2,
+    "datatypes_count_incl": 2,
+    "individual_count": 0,
+    "individual_count_incl": 0,
+    "logical_axiom_count": 828,
+    "logical_axiom_count_incl": 828,
+    "obj_property_count": 49,
+    "obj_property_count_incl": 49,
+    "ontology_anno_count": 0,
+    "ontology_iri": "http://purl.obolibrary.org/obo/uberon.owl",
+    "ontology_version_iri": "no.iri",
+    "owl2": true,
+    "owl2_dl": true,
+    "owl2_el": false,
+    "owl2_ql": false,
+    "owl2_rl": false,
+    "rbox_axiom_count": 56,
+    "rbox_axiom_count_incl": 56,
+    "rdfs": false,
+    "rule_count": 0,
+    "rule_count_incl": 0,
+    "signature_entity_count": 325,
+    "signature_entity_count_incl": 325,
+    "tbox_axiom_count": 772,
+    "tbox_axiom_count_incl": 772,
+    "tboxrbox_axiom_count": 828,
+    "tboxrbox_axiom_count_incl": 828,
+    "valid_imports": [],
+    "valid_imports_incl": [],
+    "owl2dl_profile_violation": {}
+  }
+}
\ No newline at end of file
diff --git a/docs/examples/metrics_essential_reasoner.json b/docs/examples/metrics_essential_reasoner.json
new file mode 100644
index 00000000..34820bc3
--- /dev/null
+++ b/docs/examples/metrics_essential_reasoner.json
@@ -0,0 +1,46 @@
+{
+  "metrics": {
+    "abox_axiom_count": 0,
+    "abox_axiom_count_incl": 0,
+    "annotation_property_count": 38,
+    "annotation_property_count_incl": 38,
+    "axiom_count": 5504,
+    "axiom_count_incl": 5504,
+    "class_count": 236,
+    "class_count_incl": 236,
+    "consistent": true,
+    "dataproperty_count": 0,
+    "dataproperty_count_incl": 0,
+    "datatypes_count": 2,
+    "datatypes_count_incl": 2,
+    "individual_count": 0,
+    "individual_count_incl": 0,
+    "logical_axiom_count": 828,
+    "logical_axiom_count_incl": 828,
+    "obj_property_count": 49,
+    "obj_property_count_incl": 49,
+    "ontology_anno_count": 0,
+    "ontology_iri": "http://purl.obolibrary.org/obo/uberon.owl",
+    "ontology_version_iri": "no.iri",
+    "owl2": true,
+    "owl2_dl": true,
+    "owl2_el": false,
+    "owl2_ql": false,
+    "owl2_rl": false,
+    "rbox_axiom_count": 56,
+    "rbox_axiom_count_incl": 56,
+    "rdfs": false,
+    "rule_count": 0,
+    "rule_count_incl": 0,
+    "signature_entity_count": 325,
+    "signature_entity_count_incl": 325,
+    "tbox_axiom_count": 772,
+    "tbox_axiom_count_incl": 772,
+    "tboxrbox_axiom_count": 828,
+    "tboxrbox_axiom_count_incl": 828,
+    "unsatisfiable_class_count": 0,
+    "valid_imports": [],
+    "valid_imports_incl": [],
+    "owl2dl_profile_violation": {}
+  }
+}
\ No newline at end of file
diff --git a/docs/measure.md b/docs/measure.md
new file mode 100644
index 00000000..6944f488
--- /dev/null
+++ b/docs/measure.md
@@ -0,0 +1,145 @@
+# Measure
+
+## Contents
+
+1. [Overview](#overview)
+2. [Types of metrics](#types-of-metrics)
+3. [Output formats](#output-formats)
+
+## Overview
+ROBOT can compute a number of metrics about your ontology, such as entity and
+axiom counts, qualitative information such as OWL 2 profiles
+and more complex metrics aimed at informing ontology developers such as logical expressivity and axiom shape.
+A number of essential metrics can be generated very simply as follows:
+
+    robot measure --input uberon_fragment.owl --output results/metrics.tsv
+
+This will generate a table with metrics such as:
+- Entity metrics (number of classes, object properties etc.)
+- Datatypes used
+- Number of axioms (logical and otherwise)
+- TBox, RBox, ABox size (size as number of axioms)
+- OWL2 profile information
+
+## Types of metrics
+There are three general modes for computing metrics:
+
+1. Essential metrics (`--metrics essential`, default)
+2. Extended metrics (`--metrics extended`)
+3. All metrics (`--metrics all`)
+4. Reasoner metrics (`--metrics all-reasoner`, `--metrics essential-reasoner`, `--metrics extended-reasoner`)
+
+_Essential metrics_ include basic ontology entity counts (number of classes, individuals and properties), axiom counts
+and a few qualitiative metrics such as OWL profiles, see the initial example above.
+
+    robot measure --input uberon_fragment.owl --format json --metrics essential --output results/metrics_essential.json
+
+_Extended metrics_ contain all the essential metrics, and additional details on axiom types, logical expressivity and datatypes.
+Use: `--metrics extended`.
+
+_All metrics_ include all the essential and extended metrics, as well as a range of more complex metrics targeted at
+OWL and reasoning specialists, such as information about GCIs, shape of the class hierarchy and potential cyclicity.
+Use: `--metrics all`.
+
+_Reasoner metrics_ (currently only satisfiability status and number of unsatisfiable classes) can be collected by appending `-reasoner`
+to the `--metrics` parameter, for example: `--metrics essential-reasoner`.
+
+    robot measure --input uberon_fragment.owl --format json --metrics essential-reasoner --output results/metrics_essential_reasoner.json
+
+A full breakdown of all metrics can be found in the table below. In the actual output, you will also see the same metrics ending with `_incl`.
+This indicates that the metric is the ontology _including_ its imports. Otherwise, the metric is just representative of the ontology _without_ imports.
+
+Some abbreviations useful to understand the following:
+- `abox`: The part of the ontology that is about individuals, see [Wikipedia](https://en.wikipedia.org/wiki/Abox)
+- `rbox`: The part of the ontology that is about object properties
+- `tbox`: The part of the ontology that is about classes, see [Wikipedia](https://en.wikipedia.org/wiki/Tbox)
+- `avg`: average
+- `max`: maximum
+- `rhs`: ride-hand-side. If used in conjunction with axioms, usually the "superclass" part of a SubClassOf axiom.
+- `dt`: datatype
+- `gci`: General concept inclusion. Formally, [all SubClassOf axioms are GCIs](https://en.wikipedia.org/wiki/Description_logic#Terminological_axioms),
+         but here we mean those that have a complex left-hand-side (subclass part).
+- OWL 2 Profiles:
+  - OWL 2 DL: Roughly the subset of OWL that conforms to description logics, plus annotations.
+  - OWL 2 EL: Subset of OWL 2 DL, see [W3C](https://www.w3.org/TR/owl2-profiles/#OWL_2_EL_2).
+  - OWL 2 QL: Subset of OWL 2 DL, see [W3C](https://www.w3.org/TR/owl2-profiles/#OWL_2_QL_2).
+  - OWL 2 RL: Subset of OWL 2 DL, see [W3C](https://www.w3.org/TR/owl2-profiles/#OWL_2_RL_2).
+- Logical `expressivity` and `constructs`. A breakdown of the meaning of the letters can be found [here](https://en.wikipedia.org/wiki/Description_logic#Naming_convention).
+
+
+|metric|metric_value|metric_type| explanation |
+|:---|:---|:---|:---|
+|abox_axiom_count|0|single_value| Number of axioms in the ABox |
+|annotation_property_count|24|single_value| Number of annotation properties |
+|assert_n_subclass_avg|1.0|single_value| Average number of (asserted) subclasses per class |
+|assert_n_superclass_avg|1.0416666666666667|single_value| Average number of (asserted) superclasses per class |
+|ax_complexrhs_count|15|single_value| Number of axioms with a complex right hand side |
+|axiom_count|703|single_value| Number of axioms |
+|axiom_length_max|4|single_value| Longest axiom in terms of number of entities used (including duplicate uses). |
+|axiom_type_count|SubClassOf 37|map_value| Number of axioms of a specific type, such as SubClassOf |
+|axiom_types|AnnotationAssertion|list_value| Indicates the presence of an axiom type in the ontology. |
+|certain_cycle|false|single_value| If true, there is a definite cycle in the ontology. If false, cyclicity is unknown (experimental). |
+|class_count|24|single_value| Number of classes in the ontology |
+|class_sgl_subcl_count|10|single_value| Number of super-classes which have more than one subclass. |
+|constructs|AL|list_value| Logical constructs used |
+|dataproperty_count|0|single_value| Number of distinct data properties. |
+|datatypes_builtin|XSD_BOOLEAN|list_value| Datatypes used from the built-in datatype map (like XSD schema datatypes). |
+|datatypes_count|2|single_value| Total number of distinct datatypes. |
+|dt_builtin_count|2|single_value| Total number of distinct built-in datatypes. |
+|dt_notbuiltin_count|0|single_value| Total number of distinct custom (not built-in) datatypes. |
+|expressivity|ALEH+|single_value| Logical expressivity. |
+|gci_count|0|single_value| Total count of GCI axioms.|
+|gci_hidden_count|2|single_value| Total count of hidden GCIs. |
+|individual_count|0|single_value| Number of individuals. |
+|instance_per_class_avg|0.0|single_value| Average number of individuals per class. |
+|namespace_axiom_count|http://purl.obolibrary.org/obo/BFO_ 17|map_value| Number of axioms using at least one term in a namespace. |
+|namespace_entity_count|http://purl.obolibrary.org/obo/BFO_ 17|map_value| Number of distinct entities used from a namespace. |
+|logical_axiom_count|41|single_value| Number of logical axioms in ontology |
+|most_freq_concept|UBERON:0000475|single_value| Most frequently used class. |
+|multiple_inheritance_count|2|single_value| Number of classes with multiple inheritance. |
+|named_supercl_count_max|1|single_value| Maximum number of named superclasses. |
+|obj_property_count|2|single_value| Number of object properties. |
+|ontology_anno_count|0|single_value| Number of ontology annotations. |
+|ontology_id|https://github.com/ontodev/robot/examples/edit.owl|single_value| The ontology id. |
+|owl2_dl|false|single_value| Does the ontology correspond to the OWL2 DL profile? |
+|owl2_el|false|single_value| Does the ontology correspond to the OWL2 EL profile? |
+|owl2_ql|false|single_value| Does the ontology correspond to the OWL2 QL profile? |
+|owl2_rl|false|single_value| Does the ontology correspond to the OWL2 RL profile? |
+|owl2|true|single_value| Does the ontology correspond to the OWL2 Full profile?  |
+|owl2dl_profile_violation|UseOfUndeclaredAnnotationProperty 531 |map_value| Counts of OWL2 DL profile violations. |
+|rbox_axiom_count|2|single_value| How many axioms in the RBOX? |
+|rdfs|false|single_value| Does the ontology correspond to the RDFS profile? |
+|rule_count|0|single_value| Number of SWRL rules used. |
+|signature_entity_count|52|single_value| Total number of entities in signature, including classes and individuals. |
+|syntax|RDF/XML Syntax|single_value| What serialisation is used for the ontology? |
+|tautology_count|0|single_value| Number of tautological axioms. |
+|tbox_axiom_count|39|single_value| Number of TBOX axioms (without RBOX). |
+|tbox_nominals|false|single_value| Number of TBOX axioms with nominals. |
+|tboxrbox_axiom_count|41|single_value| Number of TBOX axioms (with RBOX).|
+|undecl_entity_count|17|single_value| Number of undeclared entities. |
+
+## Output formats
+
+Currently, the following serialisation formats are supported:
+- tsv
+- csv
+- html
+- json
+- yaml
+
+To serialise the metrics as json, for example, we run:
+
+    robot measure --input uberon_fragment.owl --format json --metrics all --output results/metrics_all.json
+
+Note that if no `--format`/`-f` is explicitly provided, ROBOT will try to infer the inteded format from the file extension
+instead. Failing that, it will generate a TSV file.
+
+## Error Messages
+
+### Metrics format error
+
+Only the following `--format/-f` options are currently supported: `tsv`, `csv`, `json`, `yaml`, `html`.
+
+### Metrics type error
+
+Only the following `--metrics/-m` options are currently supported: `essential`, `essential-reasoner`, `extended`, `extended-reasoner`, `all`, `all-reasoner`.
diff --git a/robot-command/src/main/java/org/obolibrary/robot/CommandLineInterface.java b/robot-command/src/main/java/org/obolibrary/robot/CommandLineInterface.java
index a26de993..221c5710 100644
--- a/robot-command/src/main/java/org/obolibrary/robot/CommandLineInterface.java
+++ b/robot-command/src/main/java/org/obolibrary/robot/CommandLineInterface.java
@@ -30,6 +30,7 @@ public class CommandLineInterface {
     m.addCommand("filter", new FilterCommand());
     m.addCommand("materialize", new MaterializeCommand());
     m.addCommand("merge", new MergeCommand());
+    m.addCommand("measure", new MeasureCommand());
     m.addCommand("mirror", new MirrorCommand());
     m.addCommand("python", new PythonCommand());
     m.addCommand("query", new QueryCommand());
diff --git a/robot-command/src/main/java/org/obolibrary/robot/MeasureCommand.java b/robot-command/src/main/java/org/obolibrary/robot/MeasureCommand.java
new file mode 100644
index 00000000..825444d1
--- /dev/null
+++ b/robot-command/src/main/java/org/obolibrary/robot/MeasureCommand.java
@@ -0,0 +1,147 @@
+package org.obolibrary.robot;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Options;
+import org.apache.commons.io.FilenameUtils;
+import org.semanticweb.owlapi.reasoner.OWLReasonerFactory;
+
+/**
+ * Handles inputs and outputs for the {@link MeasureOperation}.
+ *
+ * @author <a href="mailto:nicolas.matentzoglu@gmail.com">Nicolas Matentzoglu</a>
+ */
+public class MeasureCommand implements Command {
+
+  /** Store the command-line options for the command. */
+  private Options options;
+
+  private final List<String> LEGAL_FORMATS = Arrays.asList("tsv", "csv", "html", "yaml", "json");
+
+  /** Initialze the command. */
+  public MeasureCommand() {
+    Options o = CommandLineHelper.getCommonOptions();
+    o.addOption("i", "input", true, "load ontology from a file");
+    o.addOption("r", "reasoner", true, "reasoner to use: (ELK, HermiT)");
+    o.addOption("I", "input-iri", true, "load ontology from an IRI");
+    o.addOption(
+        "f",
+        "format",
+        true,
+        "the measure result format: " + String.join(" ", LEGAL_FORMATS).trim() + ".");
+    o.addOption("o", "output", true, "save updated metrics to a file");
+    o.addOption(
+        "m",
+        "metrics",
+        true,
+        "select which set of metrics you would like to compute: essential (default), extended, all.");
+    options = o;
+  }
+
+  /**
+   * Name of the command.
+   *
+   * @return name
+   */
+  public String getName() {
+    return "measure";
+  }
+
+  /**
+   * Brief description of the command.
+   *
+   * @return description
+   */
+  public String getDescription() {
+    return "compute the metrics of an ontology";
+  }
+
+  /**
+   * Command-line usage for the command.
+   *
+   * @return usage
+   */
+  // todo: --output?
+  public String getUsage() {
+    return "robot measure --input <file> --output <output>";
+  }
+
+  /**
+   * Command-line options for the command.
+   *
+   * @return options
+   */
+  public Options getOptions() {
+    return options;
+  }
+
+  /**
+   * Handle the command-line and file operations for the MeasureOperation.
+   *
+   * @param args strings to use as arguments
+   */
+  public void main(String[] args) {
+    try {
+      execute(null, args);
+    } catch (Exception e) {
+      CommandLineHelper.handleException(e);
+    }
+  }
+
+  /**
+   * Given an input state and command line arguments, compute metrics in ontology. The input
+   * ontology is not changed.
+   *
+   * @param state the state from the previous command, or null
+   * @param args the command-line arguments
+   * @return the unchanged state
+   * @throws Exception on any problem
+   */
+  public CommandState execute(CommandState state, String[] args) throws Exception {
+    CommandLine line = CommandLineHelper.getCommandLine(getUsage(), getOptions(), args);
+    if (line == null) {
+      return null;
+    }
+
+    IOHelper ioHelper = CommandLineHelper.getIOHelper(line);
+
+    String metricsType = CommandLineHelper.getDefaultValue(line, "metrics", "essential");
+    String format = CommandLineHelper.getOptionalValue(line, "format");
+    String output =
+        CommandLineHelper.getRequiredValue(line, "output", "an output file must be specified");
+
+    File output_file = new File(output);
+    if (format == null) {
+      String extension = FilenameUtils.getExtension(output_file.getName()).replace("yml", "yaml");
+      if (LEGAL_FORMATS.contains(extension)) {
+        format = extension;
+      } else {
+        format = "tsv";
+      }
+    }
+
+    state = CommandLineHelper.updateInputOntology(ioHelper, state, line);
+    OWLReasonerFactory reasonerFactory = CommandLineHelper.getReasonerFactory(line);
+
+    // Override default reasoner options with command-line options
+    Map<String, String> reasonerOptions = ReduceOperation.getDefaultOptions();
+    for (String option : reasonerOptions.keySet()) {
+      if (line.hasOption(option)) {
+        reasonerOptions.put(option, line.getOptionValue(option));
+      }
+    }
+
+    MeasureOperation.measure(
+        state.getOntology(),
+        reasonerFactory,
+        metricsType,
+        format,
+        output_file,
+        ioHelper.getPrefixes());
+
+    return state;
+  }
+}
diff --git a/robot-core/src/main/java/org/obolibrary/robot/MeasureOperation.java b/robot-core/src/main/java/org/obolibrary/robot/MeasureOperation.java
new file mode 100644
index 00000000..405332d4
--- /dev/null
+++ b/robot-core/src/main/java/org/obolibrary/robot/MeasureOperation.java
@@ -0,0 +1,344 @@
+package org.obolibrary.robot;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
+import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator;
+import com.google.gson.*;
+import java.io.*;
+import java.util.*;
+import org.obolibrary.robot.export.Cell;
+import org.obolibrary.robot.export.Column;
+import org.obolibrary.robot.export.Row;
+import org.obolibrary.robot.export.Table;
+import org.obolibrary.robot.metrics.MeasureResult;
+import org.obolibrary.robot.metrics.OntologyMetrics;
+import org.obolibrary.robot.providers.CURIEShortFormProvider;
+import org.semanticweb.owlapi.model.OWLOntology;
+import org.semanticweb.owlapi.reasoner.OWLReasoner;
+import org.semanticweb.owlapi.reasoner.OWLReasonerFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Compute metrics for the ontology.
+ *
+ * @author <a href="mailto:nicolas.matentzoglu@gmail.com">Nicolas Matentzoglu</a>
+ */
+public class MeasureOperation {
+  /** Logger. */
+  private static final Logger LOGGER = LoggerFactory.getLogger(MeasureOperation.class);
+
+  /** Namespace for error messages. */
+  private static final String NS = "measure#";
+
+  /** Error message when metric type is illegal. Expects: metric type. */
+  private static final String METRICS_TYPE_ERROR =
+      NS + "METRICS TYPE ERROR unknown metrics type: %s";
+
+  /** Error message when format type is illegal. Expects: format. */
+  private static final String METRICS_FORMAT_ERROR =
+      NS + "METRICS FORMAT ERROR unknown metrics format: %s";
+
+  /**
+   * If a result set has results, write to the output stream and return true. Otherwise return
+   * false.
+   *
+   * @param result the results to write
+   * @param format the name of the file format to write the results to
+   * @param output the file to write to
+   * @return true if there were results, false otherwise
+   * @throws IOException if writing file failed
+   */
+  public static boolean maybeWriteResult(MeasureResult result, String format, File output)
+      throws IOException {
+    if (!result.isEmpty()) {
+      writeResult(result, format, output);
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  /**
+   * Running the measure command
+   *
+   * @param ontology input ontology
+   * @param rf reasoner factory to be used for reasoning metrics
+   * @param metricsType The type of metrics that should be generated, like 'essential', 'extended'
+   *     or all
+   * @param format the name of the file format to write the results to
+   * @param output the file to write to
+   * @param prefixes prefix map to be used for computing metrics
+   * @throws IOException if writing file failed
+   */
+  public static void measure(
+      OWLOntology ontology,
+      OWLReasonerFactory rf,
+      String metricsType,
+      String format,
+      File output,
+      Map<String, String> prefixes)
+      throws IOException {
+    MeasureResult metrics = new MeasureResult();
+    CURIEShortFormProvider curieShortFormProvider = new CURIEShortFormProvider(prefixes);
+    if (metricsType.contains("reasoner")) {
+      metrics.importMetrics(getMetrics(ontology, rf, metricsType, curieShortFormProvider));
+    } else {
+      metrics.importMetrics(getMetrics(ontology, metricsType, curieShortFormProvider));
+    }
+    boolean wroteData = MeasureOperation.maybeWriteResult(metrics, format, output);
+    if (!wroteData) {
+      LOGGER.info("No metrics written.");
+    }
+  }
+
+  /**
+   * Compute metrics for a given ontology.
+   *
+   * @param ontology Ontology to run metrics
+   * @param metricsType what kind of metrics to harvest
+   * @param curieShortFormProvider Shortformprovider to be used for computation of CURIEs
+   * @return Metrics, if successful
+   */
+  public static MeasureResult getMetrics(
+      OWLOntology ontology, String metricsType, CURIEShortFormProvider curieShortFormProvider) {
+    OntologyMetrics ontologyMetrics = new OntologyMetrics(ontology, curieShortFormProvider);
+    MeasureResult metrics;
+    switch (metricsType) {
+      case "essential":
+        metrics = ontologyMetrics.getEssentialMetrics();
+        break;
+      case "extended":
+        metrics = ontologyMetrics.getExtendedMetrics();
+        break;
+      case "all":
+        metrics = ontologyMetrics.getAllMetrics();
+        break;
+      default:
+        throw new IllegalArgumentException(String.format(METRICS_TYPE_ERROR, metricsType));
+    }
+    return metrics;
+  }
+
+  /**
+   * Run the metrics command using the reasoner factory. Note: when the reasoner factory is passed,
+   * it is assumed that reasoner metrics should be harvested. For example: both reasoner-all, and
+   * all will collect the same metrics: all metrics, plus the (simple) reasoner metrics.
+   *
+   * @param ontology Ontology to run metrics
+   * @param metricsType what kind of metrics to harvest
+   * @param rf reasoner factory, in case reasoner metrics should be collected
+   * @return Metrics, if successful
+   */
+  public static MeasureResult getMetrics(
+      OWLOntology ontology,
+      OWLReasonerFactory rf,
+      String metricsType,
+      CURIEShortFormProvider curieShortFormProvider) {
+    OntologyMetrics ontologyMetrics = new OntologyMetrics(ontology);
+    MeasureResult metrics = new MeasureResult();
+    OWLReasoner r = rf.createReasoner(ontology);
+    metrics.importMetrics(ontologyMetrics.getSimpleReasonerMetrics(r));
+    if (metricsType.contains("reasoner")) {
+      switch (metricsType) {
+        case "essential-reasoner":
+          metrics.importMetrics(getMetrics(ontology, "essential", curieShortFormProvider));
+          break;
+        case "extended-reasoner":
+          metrics.importMetrics(getMetrics(ontology, "extended", curieShortFormProvider));
+          break;
+        case "all-reasoner":
+          metrics.importMetrics(getMetrics(ontology, "all", curieShortFormProvider));
+          break;
+        default:
+          throw new IllegalArgumentException(String.format(METRICS_TYPE_ERROR, metricsType));
+      }
+      return metrics;
+    } else {
+      metrics.importMetrics(getMetrics(ontology, metricsType, curieShortFormProvider));
+    }
+    return metrics;
+  }
+
+  /**
+   * Write a model to an output stream.
+   *
+   * @param result results of the metrics operation
+   * @param format the language to write in (if null, TTL)
+   * @param output the output stream to write to
+   * @throws IOException on problem writing to output
+   */
+  public static void writeResult(MeasureResult result, String format, File output)
+      throws IOException {
+    switch (format) {
+      case "tsv":
+        writeTable(result, output, "tsv");
+        break;
+      case "csv":
+        writeTable(result, output, "csv");
+        break;
+      case "yaml":
+        writeYAML(result, output);
+        break;
+      case "json":
+        writeJSON(result, output);
+        break;
+      case "html":
+        writeHTML(result, output);
+        break;
+      default:
+        throw new IllegalArgumentException(String.format(METRICS_FORMAT_ERROR, format));
+    }
+  }
+
+  private static JsonElement resultsToJson(MeasureResult result) {
+    JsonObject root = new JsonObject();
+    Gson gson = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
+
+    JsonObject metrics = new JsonObject();
+    root.add("metrics", metrics);
+
+    Map<String, Object> data = result.getData();
+    List<String> keys = new ArrayList<>(data.keySet());
+    Collections.sort(keys);
+
+    for (String key : keys) {
+      Object value = data.get(key);
+      if (value instanceof Double) {
+        metrics.addProperty(key, (Double) value);
+      } else if (value instanceof Float) {
+        metrics.addProperty(key, (Float) value);
+      } else if (value instanceof Long) {
+        metrics.addProperty(key, (Long) value);
+      } else if (value instanceof Integer) {
+        metrics.addProperty(key, (Integer) value);
+      } else if (value instanceof Boolean) {
+        metrics.addProperty(key, (Boolean) value);
+      } else {
+        metrics.addProperty(key, value.toString());
+      }
+    }
+    Map<String, List<Object>> dataList = result.getListData();
+    List<String> keysList = new ArrayList<>(dataList.keySet());
+    Collections.sort(keysList);
+
+    for (String key : keysList) {
+      List<String> stringList = new ArrayList<>();
+      dataList.get(key).forEach(s -> stringList.add(s.toString()));
+      JsonElement element = gson.toJsonTree(stringList).getAsJsonArray();
+      metrics.add(key, element);
+    }
+
+    Map<String, Map<String, Object>> dataMap = result.getMapData();
+    List<String> keysMap = new ArrayList<>(dataMap.keySet());
+    Collections.sort(keysMap);
+    for (String key : keysMap) {
+      JsonElement element = gson.toJsonTree(dataMap.get(key)).getAsJsonObject();
+      metrics.add(key, element);
+    }
+    return root;
+  }
+
+  private static void writeJSON(MeasureResult result, File output) throws IOException {
+    JsonElement root = resultsToJson(result);
+    Gson gson = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
+    writeStringToFile(gson.toJson(root), output);
+  }
+
+  private static void writeStringToFile(String output, File outputPath) throws IOException {
+    try (FileWriter fw = new FileWriter(outputPath);
+        BufferedWriter bw = new BufferedWriter(fw)) {
+      LOGGER.debug("Writing metrics to: " + outputPath);
+      bw.write(output);
+    }
+  }
+
+  private static void writeYAML(MeasureResult result, File outputPath) throws IOException {
+    JsonElement root = resultsToJson(result);
+    Gson gson = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
+    String output = asYaml(gson.toJson(root));
+    writeStringToFile(output, outputPath);
+  }
+
+  private static String asYaml(String jsonString) throws IOException {
+    JsonNode jsonNodeTree = new ObjectMapper().readTree(jsonString);
+    ObjectMapper mapper =
+        new ObjectMapper(new YAMLFactory().disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER));
+    return mapper.writeValueAsString(jsonNodeTree);
+  }
+
+  private static String escapeTSV(String s) {
+    return s.replaceAll("\t", " ");
+  }
+
+  private static final Column COLUMN_METRIC = new Column("metric");
+  private static final Column COLUMN_METRIC_VALUE = new Column("metric_value");
+  private static final Column COLUMN_METRIC_TYPE = new Column("metric_type");
+
+  private static void addRowToTable(
+      Table table, String metric, String metricValue, String metricType) {
+    Row row = new Row();
+    row.add(new Cell(COLUMN_METRIC, metric));
+    row.add(new Cell(COLUMN_METRIC_VALUE, metricValue));
+    row.add(new Cell(COLUMN_METRIC_TYPE, metricType));
+    table.addRow(row);
+  }
+
+  private static Table resultsToTable(MeasureResult result, String format) {
+    Table table = new Table(format);
+
+    COLUMN_METRIC.setSort(2);
+    COLUMN_METRIC_TYPE.setSort(1);
+    COLUMN_METRIC_VALUE.setSort(0);
+
+    table.addColumn(COLUMN_METRIC);
+    table.addColumn(COLUMN_METRIC_VALUE);
+    table.addColumn(COLUMN_METRIC_TYPE);
+    table.setSortColumns();
+
+    // StringBuilder sb = new StringBuilder();
+
+    for (Map.Entry<String, Object> entry : result.getData().entrySet()) {
+      String key = escapeTSV(entry.getKey());
+      String value = escapeTSV(entry.getValue().toString());
+      addRowToTable(table, key, value, "single_value");
+      // sb.append(key).append("\t").append(value).append("\t").append("single_value").append("\n");
+    }
+
+    for (Map.Entry<String, List<Object>> entry : result.getListData().entrySet()) {
+
+      String key = escapeTSV(entry.getKey());
+
+      for (Object v : entry.getValue()) {
+        String value = escapeTSV(v.toString());
+        addRowToTable(table, key, value, "list_value");
+        // sb.append(key).append("\t").append(value).append("\t").append("list_value").append("\n");
+      }
+    }
+
+    for (Map.Entry<String, Map<String, Object>> entry : result.getMapData().entrySet()) {
+      String key = escapeTSV(entry.getKey());
+      Map<String, Object> v = entry.getValue();
+      for (Map.Entry<String, Object> entryMap : v.entrySet()) {
+        String key_inner = entryMap.getKey();
+        String value_inner = escapeTSV(entryMap.getValue() + "");
+        addRowToTable(table, key, key_inner + " " + value_inner, "map_value");
+      }
+    }
+
+    table.sortRows();
+    return table;
+  }
+
+  private static void writeTable(MeasureResult result, File output, String format)
+      throws IOException {
+    Table table = resultsToTable(result, format);
+    table.write(output.getPath(), "");
+  }
+
+  private static void writeHTML(MeasureResult result, File output) throws IOException {
+    Table table = resultsToTable(result, "tsv");
+    writeStringToFile(table.toHTML(""), output);
+  }
+}
diff --git a/robot-core/src/main/java/org/obolibrary/robot/metrics/MeasureResult.java b/robot-core/src/main/java/org/obolibrary/robot/metrics/MeasureResult.java
new file mode 100644
index 00000000..3cb090ae
--- /dev/null
+++ b/robot-core/src/main/java/org/obolibrary/robot/metrics/MeasureResult.java
@@ -0,0 +1,75 @@
+package org.obolibrary.robot.metrics;
+
+import java.util.*;
+
+public class MeasureResult {
+
+  Map<String, Object> data = new HashMap<>();
+  Map<String, List<Object>> dataListvalues = new HashMap<>();
+  Map<String, Map<String, Object>> dataMapvalues = new HashMap<>();
+
+  /**
+   * add an individual data item
+   *
+   * @param key the metric
+   * @param value the value
+   */
+  public void put(String key, Object value) {
+    data.put(key, value);
+  }
+
+  /**
+   * @param key the metric
+   * @param set the value (a set of Objects)
+   */
+  public void putSet(String key, Set<?> set) {
+    dataListvalues.put(key, new ArrayList<>(set));
+  }
+
+  /**
+   * @param key the metric
+   * @param data the value (a map of Key-Value pairs)
+   */
+  public void putMap(String key, Map<String, ?> data) {
+    Map<String, Object> map = new HashMap<>();
+    for (String k : data.keySet()) {
+      map.put(k, data.get(k));
+    }
+    dataMapvalues.put(key, map);
+  }
+
+  /** @return get the current simple data */
+  public Map<String, Object> getData() {
+    return data;
+  }
+
+  /** @return all the data that is in list form */
+  public Map<String, List<Object>> getListData() {
+    return dataListvalues;
+  }
+
+  /** @return all the data that is in map form */
+  public Map<String, Map<String, Object>> getMapData() {
+    return dataMapvalues;
+  }
+
+  /** @return true if there are no results in this object, otherwise false */
+  public boolean isEmpty() {
+    return data.isEmpty() && dataListvalues.isEmpty() && dataMapvalues.isEmpty();
+  }
+
+  /**
+   * @param key the metric
+   * @return the value recorded for that metric (only simple metrics)
+   */
+  public Object getSimpleMetricValue(String key) {
+    return data.get(key);
+  }
+
+  /** @param data to be imported */
+  public void importMetrics(MeasureResult data) {
+    this.data.putAll(data.getData());
+    this.dataListvalues.putAll(data.getListData());
+    this.dataMapvalues.putAll(data.getMapData());
+  }
+}
diff --git a/robot-core/src/main/java/org/obolibrary/robot/metrics/MetricsLabels.java b/robot-core/src/main/java/org/obolibrary/robot/metrics/MetricsLabels.java
new file mode 100644
index 00000000..797d442b
--- /dev/null
+++ b/robot-core/src/main/java/org/obolibrary/robot/metrics/MetricsLabels.java
@@ -0,0 +1,201 @@
+package org.obolibrary.robot.metrics;
+
+import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.Map;
+
+@SuppressWarnings("unused")
+public class MetricsLabels {
+
+  public static final String OWLAPI_VERSION = "owlapi_version";
+  public static final String ABOX_SIZE_INCL = "abox_axiom_count_incl";
+  public static final String ABOX_SIZE = "abox_axiom_count";
+  public static final String TBOX_SIZE_INCL = "tbox_axiom_count_incl";
+  public static final String TBOX_SIZE = "tbox_axiom_count";
+  public static final String TBOXRBOX_SIZE_INCL = "tboxrbox_axiom_count_incl";
+  public static final String TBOXRBOX_SIZE = "tboxrbox_axiom_count";
+  public static final String SIGNATURE_SIZE = "signature_entity_count";
+  public static final String SIGNATURE_SIZE_INCL = "signature_entity_count_incl";
+  public static final String ANNOTATION_PROP_COUNT = "annotation_property_count";
+  public static final String ANNOTATION_PROP_COUNT_INCL = "annotation_property_count_incl";
+  public static final String AVG_ASSERT_N_SUBCLASS = "assert_n_subclass_avg";
+  public static final String AVG_ASSERT_N_SUBCLASS_INCL = "assert_n_subclass_avg_incl";
+  public static final String AVG_ASSERT_N_SUPERCLASS = "assert_n_superclass_avg";
+  public static final String AVG_ASSERT_N_SUPERCLASS_INCL = "assert_n_superclass_avg_incl";
+  public static final String AVG_INSTANCE_PER_CLASS = "instance_per_class_avg";
+  public static final String AVG_INSTANCE_PER_CLASS_INCL = "instance_per_class_avg_incl";
+  public static final String AXIOM_COUNT = "axiom_count";
+  public static final String AXIOM_COUNT_INCL = "axiom_count_incl";
+  public static final String AXIOMTYPE_COUNT = "axiom_type_count";
+  public static final String AXIOMTYPE_COUNT_INCL = "axiom_type_count_incl";
+  public static final String CLASSEXPRESSION_COUNT = "class_expression_count";
+  public static final String CLASSEXPRESSION_COUNT_INCL = "class_expression_count_incl";
+  public static final String CLASS_COUNT = "class_count";
+  public static final String CLASS_COUNT_INCL = "class_count_incl";
+  public static final String CLASS_SGL_SUBCLASS_COUNT = "class_sgl_subcl_count";
+  public static final String CLASS_SGL_SUBCLASS_COUNT_INCL = "class_sgl_subcl_count_incl";
+  public static final String CONSTRUCTS = "constructs";
+  public static final String CONSTRUCTS_INCL = "constructs_incl";
+  public static final String DATAPROPERTY_COUNT = "dataproperty_count";
+  public static final String DATAPROPERTY_COUNT_INCL = "dataproperty_count_incl";
+  public static final String DATATYPE_COUNT = "datatypes_count";
+  public static final String DATATYPE_COUNT_INCL = "datatypes_count_incl";
+  public static final String EXPRESSIVITY = "expressivity";
+  public static final String EXPRESSIVITY_INCL = "expressivity_incl";
+  public static final String GCI_COUNT = "gci_count";
+  public static final String GCI_COUNT_INCL = "gci_count_incl";
+  public static final String GCI_HIDDEN_COUNT = "gci_hidden_count";
+  public static final String GCI_HIDDEN_COUNT_INCL = "gci_hidden_count_incl";
+  public static final String INDIVIDUAL_COUNT = "individual_count";
+  public static final String INDIVIDUAL_COUNT_INCL = "individual_count_incl";
+  public static final String LOGICAL_AXIOM_COUNT = "logical_axiom_count";
+  public static final String LOGICAL_AXIOM_COUNT_INCL = "logical_axiom_count_incl";
+  public static final String AXIOM_COMPLEXRHS_COUNT = "ax_complexrhs_count";
+  public static final String AXIOM_COMPLEXRHS_COUNT_INCL = "ax_complexrhs_count_incl";
+  public static final String AVG_SIZE_COMPLEXRHS = "complexrhs_expression_count_avg";
+  public static final String AVG_SIZE_COMPLEXRHS_INCL = "complexrhs_expression_count_avg_incl";
+  public static final String MAX_NUM_NAMED_SUPERCLASS = "named_supercl_count_max";
+  public static final String MAX_NUM_NAMED_SUPERCLASS_INCL = "named_supercl_count_max_incl";
+  public static final String MULTI_INHERITANCE_COUNT = "multiple_inheritance_count";
+  public static final String MULTI_INHERITANCE_COUNT_INCL = "multiple_inheritance_count_incl";
+  public static final String ONTOLOGY_ID = "ontology_iri";
+  public static final String ONTOLOGY_VERSION_ID = "ontology_version_iri";
+  public static final String OBJPROPERTY_COUNT = "obj_property_count";
+  public static final String OBJPROPERTY_COUNT_INCL = "obj_property_count_incl";
+  public static final String RBOX_SIZE = "rbox_axiom_count";
+  public static final String RBOX_SIZE_INCL = "rbox_axiom_count_incl";
+  public static final String REF_CLASS_COUNT = "ref_class_count";
+  public static final String REF_CLASS_COUNT_INCL = "ref_class_count_incl";
+  public static final String REF_DATAPROP_COUNT = "ref_dataprop_count";
+  public static final String REF_DATAPROP_COUNT_INCL = "ref_dataprop_count_incl";
+  public static final String REF_INDIV_COUNT = "ref_indiv_count";
+  public static final String REF_INDIV_COUNT_INCL = "ref_indiv_count_incl";
+  public static final String REF_OBJPROP_COUNT = "ref_objprop_count";
+  public static final String REF_OBJPROP_COUNT_INCL = "ref_objprop_count_incl";
+  public static final String SYNTAX = "syntax";
+  public static final String UNDECLARED_ENTITY_COUNT = "undecl_entity_count";
+  public static final String UNDECLARED_ENTITY_COUNT_INCL = "undecl_entity_count_incl";
+  public static final String VALID_IMPORTS = "valid_imports";
+  public static final String BOOL_PROFILE_OWL2 = "owl2";
+  public static final String BOOL_PROFILE_OWL2_DL = "owl2_dl";
+  public static final String BOOL_PROFILE_OWL2_EL = "owl2_el";
+  public static final String BOOL_PROFILE_OWL2_QL = "owl2_ql";
+  public static final String BOOL_PROFILE_OWL2_RL = "owl2_rl";
+  public static final String VIOLATION_PROFILE_OWL2_DL = "owl2dl_profile_violation";
+  public static final String BOOL_PROFILE_RDFS = "rdfs";
+
+  public static final String CONSISTENT = "consistent";
+  public static final String UNSATISFIABLECLASSES_COUNT = "unsatisfiable_class_count";
+  public static final String UNSATISFIABLECLASSES = "unsatisfiable_classes";
+  public static final String INFERRED_SUBSUMPTIONS_COUNT = "inferred_subsumption_count";
+  public static final String ERROR_MESSAGE_REASONING = "reasoner_error_message";
+  public static final String INFERRED_ONLY_ENTAILMENT_COUNT = "inferred_only_entailment_count";
+  public static final String DATATYPES = "datatypes_builtin";
+  public static final String DATATYPES_INCL = "datatypes_builtin_incl";
+  public static final String MAX_AXIOMLENGTH = "axiom_length_max";
+  public static final String MAX_AXIOMLENGTH_INCL = "axiom_length_max_incl";
+  public static final String DATATYPES_NOT_BUILT_IN = "datatypes_not_built_in";
+  public static final String DATATYPES_NOT_BUILT_IN_INCL = "datatypes_not_built_in_incl";
+  public static final String DATATYPE_AXIOMCOUNT = "datatypes_axiom_count";
+  public static final String DATATYPE_AXIOMCOUNT_INCL = "datatypes_axiom_count_incl";
+  public static final String COHERENT = "coherent";
+  public static final String REASONERNAME = "reasoner";
+  public static final String REASONERNAME_CLASS = "reasoner_class";
+  public static final String REASONER_JAR = "reasoner_jar";
+  public static final String REASONERVERSION = "reasoner_version";
+  public static final String REASONER_TIMEOUT = "reasoner_timeout";
+  public static final String DELEGATE_REASONER_CT = "delreasoner_ct";
+  public static final String EL_DELEGATE_REASONER_CT = "el_delreasoner_ct";
+  public static final String OWL_DELEGATE_REASONER_CT = "owl_delreasoner_ct";
+  public static final String FILENAME = "filename";
+  public static final String FILEPATH = "filepath";
+  public static final String FILESIZE = "filesize";
+  public static final String JAVA_EXCEPTION = "exception";
+  public static final String JAVA_EXCEPTION_CAUSE = "exception_cause";
+  public static final String EXPERIMENT_ID = "experiment_id";
+  public static final String EXPERIMENT_VERSION = "experiment_version";
+  public static final String EXPERIMENT_DATE = "experiment_date";
+  public static final String JAVA_EXCEPTION_MESSAGE = "exception_message";
+  public static final String GRAPH_TRAVERSE_MODE = "graph_traverse";
+  public static final String LOGICAL_AXIOM_COUNT_REASONER = "logical_axiom_ct_reasoner";
+  public static final String AD_CLTIME_PER_ATOM = "cltime_atoms";
+  public static final String COMP_OVERHEAD = "comp_overhead";
+  public static final String INFERRED_TAXONOMY_AXIOM_COUNT = "inf_ax_ct";
+  public static final String INCREMENTAL_REASONER_NR_CLASSIFY_CALLS = "incr_reas_clfy_ct";
+  public static final String SUBSUMPTIONS_ATOMIC_POSSIBLE = "subs_atomic_possible";
+  public static final String SUBSUMPTIONS_ATOMIC_AVOIDED = "subs_atomic_avoid_theory";
+  public static final String SUBSUMPTIONS_ATOMIC_AVOIDED_REASONER = "subs_atomic_avoid_reasoner";
+  public static final String SUBSUMPTIONS_ATOMIC_REDUNDANT = "subs_atomic_redundant";
+  public static final String SUBSUMPTIONS_ATOMIC_REASONER = "subs_atomic_reasoner";
+  public static final String SIGNATURE_NO_URIS = "signature_w_o_iris";
+  public static final String RULE_CT = "rule_count";
+  public static final String RULE_CT_INCL = "rule_count_incl";
+  public static final String INF_CLASSHIERARCHY_DEPTH = "inf_hier_depth";
+  public static final String AXIOM_TYPES = "axiom_types";
+  public static final String AXIOM_TYPES_INCL = "axiom_types_incl";
+  public static final String NS_USE_AXIOMS = "namespace_axiom_count";
+  public static final String NS_USE_AXIOMS_INCL = "namespace_axiom_count_incl";
+  public static final String NS_USE_SIGNATURE = "namespace_entity_count";
+  public static final String NS_USE_SIGNATURE_INCL = "namespace_entity_count_incl";
+  public static final String TAUTOLOGYCOUNT = "tautology_count";
+  public static final String TAUTOLOGYCOUNT_INCL = "tautology_count_incl";
+  public static final String CYCLE = "certain_cycle";
+  public static final String CYCLE_INCL = "certain_cycle_incl";
+  public static final String EXPORT_EXCEPTION = "export_exception";
+  public static final String FIXED_VIOLATIONS = "fixed_profile_violations";
+  public static final String STRIPPED_AXIOMTYPES = "stripped_axiomtypes";
+
+  public static final String STARTPROCESS_TS = "startprocess_ts";
+  public static final String OWLAPILOAD_TS = "owlapiload_ts";
+  public static final String CREATEREASONER_TS = "createreasoner_ts";
+  public static final String CLASSIFICATION_TS = "classification_ts";
+  public static final String DISPOSE_TS = "dispose_ts";
+  public static final String FINISH_TS = "finishprocess_ts";
+
+  public static final String PREPROCESSING_TS = "preprocessing_ts";
+  public static final String CONSISTENCYCHECK_TS = "consistencycheck_ts";
+  public static final String PREREASONINGOPTIMISATION_TS = "prereasoningoptimisation_ts";
+  public static final String TRAVERSAL_TS = "traversal_ts";
+  public static final String POSTPROCESSING_TS = "postprocessing_ts";
+  public static final String CLASSIFICATION_CORRECT = "classification_correct";
+  public static final String REASONING_PREPROCESSING_TIME = "reason_pp_dur";
+  public static final String REASONING_MODULARREASONING_TIME = "reason_mmcl_dur";
+  public static final String REASONING_CONSISTENCY_TIME = "reason_cc_dur";
+  public static final String REASONING_PREREASONINGOPTIMISATION_TIME = "reason_pro_dur";
+  public static final String REASONING_TRAVERSALPHASE_TIME = "reason_trav_dur";
+  public static final String REASONING_POSTROCESSING_TIME = "reason_pop_dur";
+  public static final String REASONING_DECOMPOSITION_TIME = "reason_dec_dur";
+  public static final String REASONER_ID = "reasonerid";
+  public static final String REASONER_DISPOSE_TIME = "reasoner_disposetime";
+  public static final String RUNID = "experiment_runid";
+  public static final String ONTOLOGY_ANNOTATIONS_COUNT = "ontology_anno_count";
+  public static final String DATATYPE_BUILTIN_COUNT_INCL = "dt_builtin_count_incl";
+  public static final String DATATYPE_BUILTIN_COUNT = "dt_builtin_count";
+  public static final String DATATYPE_NOTBUILTIN_COUNT_INCL = "dt_notbuiltin_count_incl";
+  public static final String DATATYPE_NOTBUILTIN_COUNT = "dt_notbuiltin_count";
+  public static final String TBOX_CONTAINS_NOMINALS = "tbox_nominals";
+  public static final String TBOX_CONTAINS_NOMINALS_INCL = "tbox_nominals_incl";
+  public static final String ABOX_CONTAINS_NOMINALS = "abox_nominals";
+  public static final String ABOX_CONTAINS_NOMINALS_INCL = "abox_nominals_incl";
+  public static final String MOST_FRQUENTLY_USED_CONCEPT = "most_freq_concept";
+  public static final String MOST_FRQUENTLY_USED_CONCEPT_INCL = "most_freq_concept_incl";
+  public static final String VALID_IMPORTS_INCL = "valid_imports_incl";
+  public static final String CURIE_MAP = "curie_map";
+
+  /** @return Get all metrics as a map of variable names to label */
+  public static Map<String, String> getLabels() {
+    Map<String, String> data = new HashMap<>();
+    for (Field field : MetricsLabels.class.getDeclaredFields()) {
+
+      try {
+        String name = field.getName();
+        String value = field.get(MetricsLabels.class).toString();
+        data.put(name, value);
+
+      } catch (IllegalArgumentException | IllegalAccessException e) {
+        e.printStackTrace();
+      }
+    }
+    return data;
+  }
+}
diff --git a/robot-core/src/main/java/org/obolibrary/robot/metrics/MetricsUtils.java b/robot-core/src/main/java/org/obolibrary/robot/metrics/MetricsUtils.java
new file mode 100644
index 00000000..ba00c43a
--- /dev/null
+++ b/robot-core/src/main/java/org/obolibrary/robot/metrics/MetricsUtils.java
@@ -0,0 +1,127 @@
+package org.obolibrary.robot.metrics;
+
+import org.semanticweb.owlapi.model.AxiomType;
+import org.semanticweb.owlapi.model.OWLAnnotationAssertionAxiom;
+import org.semanticweb.owlapi.model.OWLAxiom;
+import org.semanticweb.owlapi.model.OWLOntology;
+import org.semanticweb.owlapi.model.parameters.Imports;
+
+import java.util.HashSet;
+import java.util.Set;
+
+//@SuppressWarnings("unused")
+public class MetricsUtils {
+
+  /**
+   * @param axioms set of axioms
+   * @return filtered set of axioms (only ABox axioms)
+   */
+  public static Set<OWLAxiom> getABoxAxioms(Set<OWLAxiom> axioms) {
+    Set<OWLAxiom> abox = new HashSet<>();
+    Set<AxiomType<?>> types = getABoxAxiomTypes();
+    for (OWLAxiom ax : axioms) {
+      if (types.contains(ax.getAxiomType())) {
+        abox.add(ax);
+      }
+    }
+    return abox;
+  }
+
+  /**
+   * @param ontology              ontology to be checked
+   * @param includeImportsClosure take into account imports
+   * @param skiprules             ignore rules (SWRL)
+   * @param types                 AxiomTypes to consider
+   * @return all logical axioms in the ontology
+   */
+  public static Set<OWLAxiom> getLogicalAxioms(
+    OWLOntology ontology,
+    Imports includeImportsClosure,
+    boolean skiprules,
+    Set<AxiomType<?>> types) {
+    return getLogicalAxioms(ontology, includeImportsClosure, skiprules, false, types);
+  }
+
+  /**
+   * @param including_rbox whether or not RBox
+   * @return all TBox axiom types
+   */
+  public static Set<AxiomType<?>> getTBoxAxiomTypes(boolean including_rbox) {
+    Set<AxiomType<?>> axty = new HashSet<>();
+    if (including_rbox) {
+      axty.addAll(AxiomType.RBoxAxiomTypes);
+    }
+    axty.addAll(AxiomType.TBoxAxiomTypes);
+    return axty;
+  }
+
+  /**
+   * @param axioms set of axioms
+   * @return filtered set of axioms (only tbox axioms)
+   */
+  public static Set<OWLAxiom> getTBoxAxioms(Set<OWLAxiom> axioms) {
+    Set<OWLAxiom> tbox = new HashSet<>();
+    Set<AxiomType<?>> types = getTBoxAxiomTypes(true);
+    for (OWLAxiom ax : axioms) {
+      if (types.contains(ax.getAxiomType())) {
+        tbox.add(ax);
+      }
+    }
+    return tbox;
+  }
+
+  private static Set<OWLAxiom> stripAnnotations(Set<OWLAxiom> axioms) {
+    Set<OWLAxiom> newAxioms = new HashSet<>();
+    for (OWLAxiom ax : axioms) {
+      if (ax instanceof OWLAnnotationAssertionAxiom) {
+        continue;
+      }
+      newAxioms.add(ax.getAxiomWithoutAnnotations());
+    }
+    return newAxioms;
+  }
+
+  private static Set<OWLAxiom> getLogicalAxioms(
+    OWLOntology ontology,
+    Imports includeImportsClosure,
+    boolean skiprules,
+    @SuppressWarnings("SameParameterValue") boolean stripaxiomanno,
+    Set<AxiomType<?>> types) {
+    Set<OWLAxiom> axioms = new HashSet<>();
+    for (OWLAxiom ax : ontology.getLogicalAxioms()) {
+      if (types.contains(ax.getAxiomType())) {
+        axioms.add(ax);
+      }
+    }
+    if (includeImportsClosure == Imports.INCLUDED) {
+      for (OWLOntology imp : ontology.getImports()) {
+        for (OWLAxiom ax : imp.getLogicalAxioms()) {
+          if (types.contains(ax.getAxiomType())) {
+            axioms.add(ax);
+          }
+        }
+      }
+    }
+    if (skiprules) {
+      stripRules(axioms);
+    }
+    if (stripaxiomanno) {
+      axioms = stripAnnotations(axioms);
+    }
+    return axioms;
+  }
+
+  private static void stripRules(Set<OWLAxiom> axioms) {
+    Set<OWLAxiom> tmp = new HashSet<>(axioms);
+    axioms.clear();
+    for (OWLAxiom ax : tmp) {
+      if (!ax.getAxiomType().toString().equals("Rule")) {
+        axioms.add(ax);
+      }
+    }
+  }
+
+  private static Set<AxiomType<?>> getABoxAxiomTypes() {
+    return new HashSet<>(AxiomType.ABoxAxiomTypes);
+  }
+}
diff --git a/robot-core/src/main/java/org/obolibrary/robot/metrics/OntologyCycleDetector.java b/robot-core/src/main/java/org/obolibrary/robot/metrics/OntologyCycleDetector.java
new file mode 100644
index 00000000..c5a32856
--- /dev/null
+++ b/robot-core/src/main/java/org/obolibrary/robot/metrics/OntologyCycleDetector.java
@@ -0,0 +1,110 @@
+package org.obolibrary.robot.metrics;
+
+import java.util.*;
+import org.semanticweb.owlapi.model.*;
+import org.semanticweb.owlapi.model.parameters.Imports;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class OntologyCycleDetector {
+
+  /*
+   * Adapted from Dmitry Tsarkov
+   */
+
+  /*
+   * Sound, but maybe incomplete (may miss some cycles).
+   */
+
+  /** Logger. */
+  private static final Logger LOGGER = LoggerFactory.getLogger(OntologyCycleDetector.class);
+
+  /**
+   * @param ontology the ontology to be checked for cycles
+   * @param includeImports whether imports closure should be considered
+   * @return True if there is certainly a cycle, otherwise False.
+   */
+  public static boolean containsCycle(OWLOntology ontology, Imports includeImports) {
+    final Set<OWLClass> Classes = ontology.getClassesInSignature(includeImports);
+    for (OWLClass Class : Classes) {
+      Queue<OWLClass> Queue = new LinkedList<>();
+      Set<OWLClass> Visited = new HashSet<>();
+      Queue.add(Class);
+      // System.out.println("DeCycler.processOntology() Class " + Class);
+      while (Queue.peek() != null) {
+        OWLClass current = Queue.poll();
+        if (current == Class && !Queue.isEmpty()) {
+          LOGGER.warn(
+              "Cycle detector encountered a potential problem - results may be unreliable.");
+        }
+        // no need to check the same entry more than once
+        if (Visited.contains(current)) continue;
+        Visited.add(current);
+
+        Set<OWLAxiom> Defs = getReferencingAxioms(ontology, current, includeImports);
+        Set<OWLClassExpression> ces = getDefinition(current, Defs);
+
+        // no definition for a class -- fine!
+        OWLClassExpression C =
+            ontology.getOWLOntologyManager().getOWLDataFactory().getOWLObjectIntersectionOf(ces);
+
+        // check the signature of that axiom
+        Set<OWLClass> AxSig = C.getClassesInSignature();
+
+        if (AxSig.contains(Class)) {
+          return true;
+        } else {
+          AxSig.remove(current);
+          Queue.addAll(AxSig);
+        }
+      }
+    }
+    return false;
+  }
+
+  private static Set<OWLAxiom> getReferencingAxioms(
+      OWLOntology ontology, OWLClass current, Imports includeImports) {
+    Set<OWLAxiom> refs = new HashSet<>();
+    if (includeImports == Imports.INCLUDED) {
+      for (OWLOntology o : ontology.getImportsClosure()) {
+        refs.addAll(o.getReferencingAxioms(current));
+      }
+    } else {
+      refs.addAll(ontology.getReferencingAxioms(current));
+    }
+    return refs;
+  }
+
+  private static Set<OWLClassExpression> getDescription(OWLClass current, OWLAxiom ax) {
+    Set<OWLClassExpression> ces = new HashSet<>();
+    if (ax.isOfType(AxiomType.SUBCLASS_OF)) {
+      OWLSubClassOfAxiom axiom = (OWLSubClassOfAxiom) ax;
+      return Collections.singleton(axiom.getSuperClass());
+    } else if (ax.isOfType(AxiomType.EQUIVALENT_CLASSES)) {
+      OWLEquivalentClassesAxiom axiom = (OWLEquivalentClassesAxiom) ax;
+      ces.addAll(axiom.getClassExpressions());
+      ces.remove(current);
+    }
+    return ces;
+  }
+
+  private static Set<OWLClassExpression> getDefinition(OWLClass current, Set<OWLAxiom> defs) {
+    Set<OWLClassExpression> sce = new HashSet<>();
+    for (OWLAxiom ax : defs) {
+      if (ax.isOfType(AxiomType.SUBCLASS_OF)) {
+        OWLSubClassOfAxiom axiom = (OWLSubClassOfAxiom) ax;
+        if (current.equals(axiom.getSubClass())) {
+          sce.addAll(getDescription(current, ax));
+        }
+      } else if (ax.isOfType(AxiomType.EQUIVALENT_CLASSES)) {
+        OWLEquivalentClassesAxiom axiom = (OWLEquivalentClassesAxiom) ax;
+        for (OWLClassExpression cl : axiom.getClassExpressions()) {
+          if (!cl.isAnonymous() && current.equals(cl.asOWLClass())) {
+            sce.addAll(getDescription(current, ax));
+          }
+        }
+      }
+    }
+    return sce;
+  }
+}
diff --git a/robot-core/src/main/java/org/obolibrary/robot/metrics/OntologyMetrics.java b/robot-core/src/main/java/org/obolibrary/robot/metrics/OntologyMetrics.java
new file mode 100644
index 00000000..dc643e42
--- /dev/null
+++ b/robot-core/src/main/java/org/obolibrary/robot/metrics/OntologyMetrics.java
@@ -0,0 +1,1117 @@
+package org.obolibrary.robot.metrics;
+
+import java.io.IOException;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.obolibrary.robot.IOHelper;
+import org.obolibrary.robot.providers.CURIEShortFormProvider;
+import org.semanticweb.owlapi.metrics.AbstractOWLMetric;
+import org.semanticweb.owlapi.metrics.AverageAssertedNamedSuperclassCount;
+import org.semanticweb.owlapi.metrics.DLExpressivity;
+import org.semanticweb.owlapi.metrics.GCICount;
+import org.semanticweb.owlapi.metrics.HiddenGCICount;
+import org.semanticweb.owlapi.metrics.MaximumNumberOfNamedSuperclasses;
+import org.semanticweb.owlapi.metrics.NumberOfClassesWithMultipleInheritance;
+import org.semanticweb.owlapi.metrics.ReferencedClassCount;
+import org.semanticweb.owlapi.metrics.ReferencedDataPropertyCount;
+import org.semanticweb.owlapi.metrics.ReferencedIndividualCount;
+import org.semanticweb.owlapi.metrics.ReferencedObjectPropertyCount;
+import org.semanticweb.owlapi.model.AxiomType;
+import org.semanticweb.owlapi.model.IRI;
+import org.semanticweb.owlapi.model.OWLAxiom;
+import org.semanticweb.owlapi.model.OWLClass;
+import org.semanticweb.owlapi.model.OWLClassAssertionAxiom;
+import org.semanticweb.owlapi.model.OWLClassExpression;
+import org.semanticweb.owlapi.model.OWLDataProperty;
+import org.semanticweb.owlapi.model.OWLDatatype;
+import org.semanticweb.owlapi.model.OWLEntity;
+import org.semanticweb.owlapi.model.OWLEquivalentClassesAxiom;
+import org.semanticweb.owlapi.model.OWLImportsDeclaration;
+import org.semanticweb.owlapi.model.OWLObjectHasValue;
+import org.semanticweb.owlapi.model.OWLObjectIntersectionOf;
+import org.semanticweb.owlapi.model.OWLObjectProperty;
+import org.semanticweb.owlapi.model.OWLOntology;
+import org.semanticweb.owlapi.model.OWLOntologyID;
+import org.semanticweb.owlapi.model.OWLOntologyManager;
+import org.semanticweb.owlapi.model.OWLSubClassOfAxiom;
+import org.semanticweb.owlapi.model.OWLSubObjectPropertyOfAxiom;
+import org.semanticweb.owlapi.model.parameters.Imports;
+import org.semanticweb.owlapi.profiles.OWL2DLProfile;
+import org.semanticweb.owlapi.profiles.OWL2ELProfile;
+import org.semanticweb.owlapi.profiles.OWL2Profile;
+import org.semanticweb.owlapi.profiles.OWL2QLProfile;
+import org.semanticweb.owlapi.profiles.OWL2RLProfile;
+import org.semanticweb.owlapi.profiles.OWLProfile;
+import org.semanticweb.owlapi.profiles.OWLProfileReport;
+import org.semanticweb.owlapi.profiles.OWLProfileViolation;
+import org.semanticweb.owlapi.reasoner.OWLReasoner;
+import org.semanticweb.owlapi.util.DLExpressivityChecker;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import uk.ac.manchester.cs.owl.owlapi.OWLObjectOneOfImpl;
+
+@SuppressWarnings("unused")
+public class OntologyMetrics {
+
+  private final OWLOntology ontology;
+  private final OWLOntologyManager manager;
+  private final List<String> owlProfileViolationsList = new ArrayList<>();
+  private final Map<String, String> prefixmap = new HashMap<>();
+  private final Map<String, String> prefixmapUsed = new HashMap<>();
+  private final List<OWLProfileViolation> owlProfileViolations = new ArrayList<>();
+  private CURIEShortFormProvider curieProvider;
+  private static final Logger LOGGER = LoggerFactory.getLogger(OntologyMetrics.class);
+
+  public OntologyMetrics(OWLOntology ontology) {
+    this.ontology = ontology;
+    this.manager = ontology.getOWLOntologyManager();
+    try {
+      this.curieProvider = new CURIEShortFormProvider(new IOHelper().getPrefixes());
+      this.curieProvider.getSortedPrefixMap().forEach(e -> prefixmap.put(e.getKey(), e.getValue()));
+    } catch (IOException e) {
+      LOGGER.warn("Curie Provider could not be instantiated, trying without.. ", e);
+    }
+  }
+
+  public OntologyMetrics(OWLOntology ontology, CURIEShortFormProvider curieShortFormProvider) {
+    this.ontology = ontology;
+    this.manager = ontology.getOWLOntologyManager();
+    this.curieProvider = curieShortFormProvider;
+    this.curieProvider.getSortedPrefixMap().forEach(e -> prefixmap.put(e.getKey(), e.getValue()));
+  }
+
+  /** @return the essential metrics */
+  public MeasureResult getEssentialMetrics() {
+    return getEssentialMetrics("");
+  }
+
+  /** @return the extended metrics */
+  public MeasureResult getExtendedMetrics() {
+    return getExtendedMetrics("");
+  }
+
+  /** @return all metrics */
+  public MeasureResult getAllMetrics() {
+    return getAllMetrics("");
+  }
+
+  /**
+   * @param prefix a prefix to prepend to all the metrics, for example "bfo_"
+   * @return essential metrics
+   */
+  public MeasureResult getEssentialMetrics(String prefix) {
+    MeasureResult csvData = new MeasureResult();
+    csvData.put(prefix + MetricsLabels.ONTOLOGY_ID, getOntologyId());
+    csvData.put(prefix + MetricsLabels.ONTOLOGY_VERSION_ID, getOntologyVersionId());
+    /*
+    Essential entity metrics
+    */
+    csvData.put(prefix + MetricsLabels.SIGNATURE_SIZE_INCL, getSignatureSize(Imports.INCLUDED));
+    csvData.put(prefix + MetricsLabels.SIGNATURE_SIZE, getSignatureSize(Imports.EXCLUDED));
+    csvData.put(prefix + MetricsLabels.CLASS_COUNT_INCL, getClassCount(Imports.INCLUDED));
+    csvData.put(prefix + MetricsLabels.CLASS_COUNT, getClassCount(Imports.EXCLUDED));
+    csvData.put(
+        prefix + MetricsLabels.OBJPROPERTY_COUNT_INCL, getObjectPropertyCount(Imports.INCLUDED));
+    csvData.put(prefix + MetricsLabels.OBJPROPERTY_COUNT, getObjectPropertyCount(Imports.EXCLUDED));
+    csvData.put(
+        prefix + MetricsLabels.INDIVIDUAL_COUNT_INCL, getIndividualsCount(Imports.INCLUDED));
+    csvData.put(prefix + MetricsLabels.INDIVIDUAL_COUNT, getIndividualsCount(Imports.EXCLUDED));
+    csvData.put(
+        prefix + MetricsLabels.DATAPROPERTY_COUNT_INCL, getDataPropertyCount(Imports.INCLUDED));
+    csvData.put(prefix + MetricsLabels.DATAPROPERTY_COUNT, getDataPropertyCount(Imports.EXCLUDED));
+    csvData.put(
+        prefix + MetricsLabels.ANNOTATION_PROP_COUNT, getAnnotationPropertyCount(Imports.EXCLUDED));
+    csvData.put(
+        prefix + MetricsLabels.ANNOTATION_PROP_COUNT_INCL,
+        getAnnotationPropertyCount(Imports.INCLUDED));
+    csvData.put(prefix + MetricsLabels.DATATYPE_COUNT_INCL, getDatatypesCount(Imports.INCLUDED));
+    csvData.put(prefix + MetricsLabels.DATATYPE_COUNT, getDatatypesCount(Imports.EXCLUDED));
+
+    csvData.put(prefix + MetricsLabels.ONTOLOGY_ANNOTATIONS_COUNT, getAnnotationsCount());
+
+    /*
+    Essential axiom counts
+     */
+
+    csvData.put(prefix + MetricsLabels.LOGICAL_AXIOM_COUNT, getLogicalAxiomCount(Imports.EXCLUDED));
+    csvData.put(
+        prefix + MetricsLabels.LOGICAL_AXIOM_COUNT_INCL, getLogicalAxiomCount(Imports.INCLUDED));
+    csvData.put(prefix + MetricsLabels.AXIOM_COUNT, getAxiomCount(Imports.EXCLUDED));
+    csvData.put(prefix + MetricsLabels.AXIOM_COUNT_INCL, getAxiomCount(Imports.INCLUDED));
+
+    csvData.put(prefix + MetricsLabels.TBOX_SIZE, getTBoxSize(Imports.EXCLUDED));
+    csvData.put(prefix + MetricsLabels.TBOX_SIZE_INCL, getTBoxSize(Imports.INCLUDED));
+    csvData.put(prefix + MetricsLabels.TBOXRBOX_SIZE, getTBoxRboxSize(Imports.EXCLUDED));
+    csvData.put(prefix + MetricsLabels.TBOXRBOX_SIZE_INCL, getTBoxRboxSize(Imports.INCLUDED));
+    csvData.put(prefix + MetricsLabels.RULE_CT, getNumberOfRules(Imports.EXCLUDED));
+    csvData.put(prefix + MetricsLabels.RULE_CT_INCL, getNumberOfRules(Imports.INCLUDED));
+    csvData.put(prefix + MetricsLabels.RBOX_SIZE, getRBoxSize(Imports.EXCLUDED));
+    csvData.put(prefix + MetricsLabels.RBOX_SIZE_INCL, getRBoxSize(Imports.INCLUDED));
+    csvData.put(prefix + MetricsLabels.ABOX_SIZE, getABoxSize(Imports.EXCLUDED));
+    csvData.put(prefix + MetricsLabels.ABOX_SIZE_INCL, getABoxSize(Imports.INCLUDED));
+
+    /*
+    Essential expressivity metrics
+     */
+    csvData.put(prefix + MetricsLabels.BOOL_PROFILE_OWL2, isOWL2Profile());
+    csvData.put(prefix + MetricsLabels.BOOL_PROFILE_OWL2_DL, isOWL2DLProfile());
+    csvData.put(prefix + MetricsLabels.BOOL_PROFILE_OWL2_EL, isOWL2ELProfile());
+    csvData.put(prefix + MetricsLabels.BOOL_PROFILE_OWL2_QL, isOWL2QLProfile());
+    csvData.put(prefix + MetricsLabels.BOOL_PROFILE_OWL2_RL, isOWL2RLProfile());
+    csvData.put(prefix + MetricsLabels.BOOL_PROFILE_RDFS, isRDFS());
+    csvData.putMap(prefix + MetricsLabels.VIOLATION_PROFILE_OWL2_DL, getOwlprofileviolations());
+
+    csvData.putSet(prefix + MetricsLabels.VALID_IMPORTS, getValidImports(false));
+    csvData.putSet(prefix + MetricsLabels.VALID_IMPORTS_INCL, getValidImports(true));
+
+    return csvData;
+  }
+
+  /**
+   * @param prefix a prefix to prepend to all the metrics, for example "bfo_"
+   * @return extended metrics
+   */
+  public MeasureResult getExtendedMetrics(String prefix) {
+    MeasureResult csvData = new MeasureResult();
+    MeasureResult essentialData = getEssentialMetrics(prefix);
+    csvData.importMetrics(essentialData);
+
+    /*
+    Extended entity metrics
+     */
+    csvData.put(
+        prefix + MetricsLabels.DATATYPE_BUILTIN_COUNT_INCL,
+        getDatatypesBuiltinCount(Imports.INCLUDED));
+    csvData.put(
+        prefix + MetricsLabels.DATATYPE_BUILTIN_COUNT, getDatatypesBuiltinCount(Imports.EXCLUDED));
+    csvData.put(
+        prefix + MetricsLabels.DATATYPE_NOTBUILTIN_COUNT_INCL,
+        getDatatypesNotBuiltinCount(Imports.INCLUDED));
+    csvData.put(
+        prefix + MetricsLabels.DATATYPE_NOTBUILTIN_COUNT,
+        getDatatypesNotBuiltinCount(Imports.EXCLUDED));
+
+    /*
+    Extended expressivity metrics
+     */
+    csvData.put(prefix + MetricsLabels.EXPRESSIVITY, getExpressivity(false));
+    csvData.put(prefix + MetricsLabels.EXPRESSIVITY_INCL, getExpressivity(true));
+    csvData.putSet(prefix + MetricsLabels.CONSTRUCTS_INCL, getConstructs(true));
+    csvData.putSet(prefix + MetricsLabels.CONSTRUCTS, getConstructs(false));
+    csvData.putSet(prefix + MetricsLabels.AXIOM_TYPES, getAxiomTypes(Imports.EXCLUDED));
+    csvData.putSet(prefix + MetricsLabels.AXIOM_TYPES_INCL, getAxiomTypes(Imports.INCLUDED));
+    csvData.putMap(prefix + MetricsLabels.AXIOMTYPE_COUNT, getAxiomTypeCounts(Imports.EXCLUDED));
+    csvData.putMap(
+        prefix + MetricsLabels.AXIOMTYPE_COUNT_INCL, getAxiomTypeCounts(Imports.INCLUDED));
+    csvData.put(prefix + MetricsLabels.SYNTAX, getSyntax());
+    csvData.putMap(
+        prefix + MetricsLabels.CLASSEXPRESSION_COUNT,
+        getOWLClassExpressionCounts(Imports.EXCLUDED));
+    csvData.putMap(
+        prefix + MetricsLabels.CLASSEXPRESSION_COUNT_INCL,
+        getOWLClassExpressionCounts(Imports.INCLUDED));
+    csvData.put(prefix + MetricsLabels.SYNTAX, getSyntax());
+
+    /*
+    Entity usage
+     */
+
+    csvData.putMap(prefix + MetricsLabels.NS_USE_AXIOMS, getAxiomUsageMap(Imports.EXCLUDED));
+    csvData.putMap(prefix + MetricsLabels.NS_USE_AXIOMS_INCL, getAxiomUsageMap(Imports.INCLUDED));
+    csvData.putMap(prefix + MetricsLabels.NS_USE_SIGNATURE, getEntityUsageMap(Imports.EXCLUDED));
+    csvData.putMap(
+        prefix + MetricsLabels.NS_USE_SIGNATURE_INCL, getEntityUsageMap(Imports.INCLUDED));
+    csvData.putMap(prefix + MetricsLabels.CURIE_MAP, prefixmapUsed);
+    return csvData;
+  }
+
+  /**
+   * @param reasoner the reasoner to use to compute the metric; it is expected that this reasoner
+   *     has the same ontology loaded as the one used by this class.
+   * @return simple reasoner metrics
+   */
+  public MeasureResult getSimpleReasonerMetrics(OWLReasoner reasoner) {
+    return getSimpleReasonerMetrics("", reasoner);
+  }
+
+  /**
+   * @param reasoner the reasoner to use to compute the metric; it is expected that this reasoner
+   *     has the same ontology loaded as the one used by this class.
+   * @return extended reasoner metrics
+   */
+  public MeasureResult getExtendedReasonerMetrics(OWLReasoner reasoner) {
+    return getExtendedReasonerMetrics("", reasoner);
+  }
+
+  /**
+   * @param prefix a prefix to prepend to all the metrics, for example "bfo_"
+   * @param reasoner the reasoner to use to compute the metric; it is expected that this reasoner
+   *     has the same ontology loaded as the one used by this class.
+   * @return simple reasoner metrics
+   */
+  public MeasureResult getSimpleReasonerMetrics(String prefix, OWLReasoner reasoner) {
+    MeasureResult csvData = new MeasureResult();
+    csvData.put(prefix + MetricsLabels.CONSISTENT, reasoner.isConsistent());
+    if (reasoner.isConsistent()) {
+      csvData.put(
+          prefix + MetricsLabels.UNSATISFIABLECLASSES_COUNT,
+          reasoner.getUnsatisfiableClasses().getEntitiesMinusBottom().size());
+    } else {
+      csvData.put(prefix + MetricsLabels.UNSATISFIABLECLASSES_COUNT, -1);
+    }
+    return csvData;
+  }
+
+  /**
+   * @param prefix a prefix to prepend to all the metrics, for example "bfo_"
+   * @param reasoner the reasoner to use to compute the metric; it is expected that this reasoner
+   *     has the same ontology loaded as the one used by this class.
+   * @return extended reasoner metrics
+   */
+  public MeasureResult getExtendedReasonerMetrics(String prefix, OWLReasoner reasoner) {
+    MeasureResult csvData = new MeasureResult();
+    MeasureResult extendedData = getSimpleReasonerMetrics(prefix, reasoner);
+    csvData.importMetrics(extendedData);
+    return csvData;
+  }
+
+  /**
+   * @param prefix a prefix to prepend to all the metrics, for example "bfo_"
+   * @return all metrics
+   */
+  public MeasureResult getAllMetrics(String prefix) {
+    MeasureResult csvData = new MeasureResult();
+    MeasureResult extendedData = getExtendedMetrics(prefix);
+    csvData.importMetrics(extendedData);
+
+    csvData.put(prefix + MetricsLabels.MAX_AXIOMLENGTH, getLongestAxiomLength(Imports.EXCLUDED));
+    csvData.put(
+        prefix + MetricsLabels.MAX_AXIOMLENGTH_INCL, getLongestAxiomLength(Imports.INCLUDED));
+
+    csvData.put(
+        prefix + MetricsLabels.AVG_ASSERT_N_SUBCLASS_INCL,
+        getAverageAssertedNamedSubclasses(Imports.INCLUDED));
+    csvData.put(
+        prefix + MetricsLabels.AVG_ASSERT_N_SUBCLASS,
+        getAverageAssertedNamedSubclasses(Imports.EXCLUDED));
+    csvData.put(
+        prefix + MetricsLabels.AVG_ASSERT_N_SUPERCLASS_INCL,
+        getAverageAssertedNamedSuperclasses(true));
+    csvData.put(
+        prefix + MetricsLabels.AVG_ASSERT_N_SUPERCLASS, getAverageAssertedNamedSuperclasses(false));
+    csvData.put(
+        prefix + MetricsLabels.AVG_INSTANCE_PER_CLASS_INCL,
+        getAverageInstancesPerClass(Imports.INCLUDED));
+    csvData.put(
+        prefix + MetricsLabels.AVG_INSTANCE_PER_CLASS,
+        getAverageInstancesPerClass(Imports.EXCLUDED));
+
+    csvData.put(
+        prefix + MetricsLabels.CLASS_SGL_SUBCLASS_COUNT_INCL,
+        getClassesWithSingleSubclassCount(Imports.INCLUDED));
+    csvData.put(
+        prefix + MetricsLabels.CLASS_SGL_SUBCLASS_COUNT,
+        getClassesWithSingleSubclassCount(Imports.EXCLUDED));
+
+    csvData.put(
+        prefix + MetricsLabels.AXIOM_COMPLEXRHS_COUNT_INCL,
+        getAxiomsWithComplexRHS(Imports.INCLUDED));
+    csvData.put(
+        prefix + MetricsLabels.AXIOM_COMPLEXRHS_COUNT, getAxiomsWithComplexRHS(Imports.EXCLUDED));
+
+    csvData.put(
+        prefix + MetricsLabels.TBOX_CONTAINS_NOMINALS_INCL,
+        isTBoxContainsNominals(Imports.INCLUDED));
+    csvData.put(
+        prefix + MetricsLabels.TBOX_CONTAINS_NOMINALS, isTBoxContainsNominals(Imports.EXCLUDED));
+    csvData.put(
+        prefix + MetricsLabels.ABOX_CONTAINS_NOMINALS_INCL,
+        isABoxContainsNominals(Imports.INCLUDED));
+    csvData.put(
+        prefix + MetricsLabels.ABOX_CONTAINS_NOMINALS, isABoxContainsNominals(Imports.EXCLUDED));
+
+    csvData.put(prefix + MetricsLabels.GCI_COUNT_INCL, getGCICount(true));
+    csvData.put(prefix + MetricsLabels.GCI_COUNT, getGCICount(false));
+    csvData.put(prefix + MetricsLabels.GCI_HIDDEN_COUNT_INCL, getHiddenGCICount(true));
+    csvData.put(prefix + MetricsLabels.GCI_HIDDEN_COUNT, getHiddenGCICount(false));
+
+    csvData.put(
+        prefix + MetricsLabels.MAX_NUM_NAMED_SUPERCLASS_INCL,
+        getMaximumNumberOfNamedSuperclasses(true));
+    csvData.put(
+        prefix + MetricsLabels.MAX_NUM_NAMED_SUPERCLASS,
+        getMaximumNumberOfNamedSuperclasses(false));
+    csvData.put(
+        prefix + MetricsLabels.MULTI_INHERITANCE_COUNT_INCL, getMultipleInheritanceCount(true));
+    csvData.put(prefix + MetricsLabels.MULTI_INHERITANCE_COUNT, getMultipleInheritanceCount(false));
+
+    /*    csvData.put(prefix + MetricsLabels.REF_CLASS_COUNT_INCL, getReferencedClassCount(true));
+        csvData.put(prefix + MetricsLabels.REF_CLASS_COUNT, getReferencedClassCount(false));
+        csvData.put(
+            prefix + MetricsLabels.REF_DATAPROP_COUNT_INCL, getReferencedDataPropertyCount(true));
+        csvData.put(prefix + MetricsLabels.REF_DATAPROP_COUNT, getReferencedDataPropertyCount(false));
+        csvData.put(prefix + MetricsLabels.REF_INDIV_COUNT_INCL, getReferencedIndividualCount(true));
+        csvData.put(prefix + MetricsLabels.REF_INDIV_COUNT, getReferencedIndividualCount(false));
+        csvData.put(
+            prefix + MetricsLabels.REF_OBJPROP_COUNT_INCL, getReferencedObjectPropertyCount(true));
+        csvData.put(prefix + MetricsLabels.REF_OBJPROP_COUNT, getReferencedObjectPropertyCount(false));
+    */
+    csvData.put(
+        prefix + MetricsLabels.UNDECLARED_ENTITY_COUNT_INCL,
+        getUndeclaredEntitiesCount(Imports.INCLUDED));
+    csvData.put(
+        prefix + MetricsLabels.UNDECLARED_ENTITY_COUNT,
+        getUndeclaredEntitiesCount(Imports.EXCLUDED));
+
+    csvData.put(prefix + MetricsLabels.TAUTOLOGYCOUNT, getTautologyCount(Imports.EXCLUDED));
+    csvData.put(prefix + MetricsLabels.TAUTOLOGYCOUNT_INCL, getTautologyCount(Imports.INCLUDED));
+    csvData.put(prefix + MetricsLabels.CYCLE, surelyContainsCycle(Imports.EXCLUDED));
+    csvData.put(prefix + MetricsLabels.CYCLE_INCL, surelyContainsCycle(Imports.INCLUDED));
+
+    if (surelyContainsCycle(Imports.EXCLUDED)) {
+      csvData.put(prefix + MetricsLabels.CYCLE, true);
+    } else {
+      csvData.put(prefix + MetricsLabels.CYCLE, false);
+    }
+
+    csvData.putMap(
+        prefix + MetricsLabels.DATATYPE_AXIOMCOUNT,
+        getDatatypesWithAxiomOccurrenceCount(Imports.EXCLUDED));
+    csvData.putMap(
+        prefix + MetricsLabels.DATATYPE_AXIOMCOUNT_INCL,
+        getDatatypesWithAxiomOccurrenceCount(Imports.INCLUDED));
+    csvData.putSet(prefix + MetricsLabels.DATATYPES, getBuiltInDatatypes(Imports.EXCLUDED));
+    csvData.putSet(prefix + MetricsLabels.DATATYPES_INCL, getBuiltInDatatypes(Imports.INCLUDED));
+    csvData.putSet(
+        prefix + MetricsLabels.DATATYPES_NOT_BUILT_IN, getNotBuiltInDatatypes(Imports.EXCLUDED));
+    csvData.putSet(
+        prefix + MetricsLabels.DATATYPES_NOT_BUILT_IN_INCL,
+        getNotBuiltInDatatypes(Imports.INCLUDED));
+
+    csvData.put(
+        prefix + MetricsLabels.MOST_FRQUENTLY_USED_CONCEPT_INCL,
+        getMostFrequentlyUsedClassInLogicalAxioms(Imports.INCLUDED));
+    csvData.put(
+        prefix + MetricsLabels.MOST_FRQUENTLY_USED_CONCEPT,
+        getMostFrequentlyUsedClassInLogicalAxioms(Imports.EXCLUDED));
+
+    return csvData;
+  }
+
+  // ENTITIES
+
+  private static int getLengthOfAxiom(OWLAxiom axiom) {
+    int length = 0;
+
+    String axiomstring = axiom.getAxiomWithoutAnnotations().toString();
+    for (OWLEntity e : axiom.getSignature()) {
+      length += getNumberOfOccurences(axiomstring, e.toString());
+    }
+    return length;
+  }
+
+  private static int getNumberOfOccurences(String haystack, String needle) {
+    int length = 0;
+    Pattern p = Pattern.compile(needle);
+    Matcher m = p.matcher(haystack);
+    while (m.find()) {
+      length++;
+    }
+    return length;
+  }
+
+  private int getSignatureSize(Imports includeImportsClosure) {
+    return getOntology().getSignature(includeImportsClosure).size();
+  }
+
+  private int getUndeclaredEntitiesCount(Imports includeImportsClosure) {
+    int undeclared = 0;
+    for (OWLEntity entity : getOntology().getSignature(includeImportsClosure)) {
+      if (!getOntology().isDeclared(entity)) {
+        undeclared++;
+      }
+    }
+    return undeclared;
+  }
+
+  private boolean isTBoxContainsNominals(Imports b) {
+    for (OWLAxiom ax : MetricsUtils.getTBoxAxioms(getLogicalAxioms(b, true))) {
+      for (OWLClassExpression cl : ax.getNestedClassExpressions()) {
+        if (cl instanceof OWLObjectOneOfImpl) {
+          // System.out.println(ax);
+          return true;
+        } else if (cl instanceof OWLObjectHasValue) {
+          // System.out.println(ax);
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  private boolean isABoxContainsNominals(Imports b) {
+    for (OWLAxiom ax : MetricsUtils.getABoxAxioms(getLogicalAxioms(b, true))) {
+      for (OWLClassExpression cl : ax.getNestedClassExpressions()) {
+        if (cl instanceof OWLObjectOneOfImpl) {
+          return true;
+        } else if (cl instanceof OWLObjectHasValue) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  private int getClassCount(Imports includeImportsClosure) {
+    return getOntology().getClassesInSignature(includeImportsClosure).size();
+  }
+
+  private int getObjectPropertyCount(Imports includeImportsClosure) {
+    return getOntology().getObjectPropertiesInSignature(includeImportsClosure).size();
+  }
+
+  private int getDataPropertyCount(Imports included) {
+    return getOntology().getDataPropertiesInSignature(included).size();
+  }
+
+  private int getDatatypesCount(Imports included) {
+    return getOntology().getDatatypesInSignature(included).size();
+  }
+
+  private Map<String, Integer> getDatatypesWithAxiomOccurrenceCount(Imports includeImportsClosure) {
+    Map<String, Integer> map = new HashMap<>();
+    Set<OWLAxiom> axioms = getAxioms(includeImportsClosure);
+    for (OWLAxiom axiom : axioms) {
+      Set<OWLDatatype> dtypes = axiom.getDatatypesInSignature();
+      for (OWLDatatype datatype : dtypes) {
+        String dtname = datatype.toString();
+        if (datatype.isBuiltIn()) {
+          dtname = datatype.getBuiltInDatatype().toString();
+        }
+        if (map.containsKey(dtname)) {
+          Integer itemp = map.get(dtname);
+          itemp++;
+          map.put(dtname, itemp);
+        } else {
+          map.put(dtname, 1);
+        }
+      }
+    }
+    return map;
+  }
+
+  private void countKeyUpInMap(String key, Map<String, Integer> map) {
+    if (!map.containsKey(key)) {
+      map.put(key, 0);
+    }
+    map.put(key, map.get(key) + 1);
+  }
+
+  private Map<String, Integer> getEntityUsageMap(Imports includeImportsClosure) {
+    Map<String, Integer> map = new HashMap<>();
+    for (OWLEntity e : getOntology().getSignature(includeImportsClosure)) {
+      String iri_pre = extractPrefixForEntityOrOtherIfUnknown(e);
+      countKeyUpInMap(iri_pre, map);
+    }
+    return map;
+  }
+
+  private String extractPrefixForEntityOrOtherIfUnknown(OWLEntity e) {
+    Optional<CURIEShortFormProvider> sfpo = getCurieProvider();
+    if (sfpo.isPresent()) {
+      CURIEShortFormProvider sfp = sfpo.get();
+      String shortform = sfp.getShortForm(e);
+      if (shortform.contains(":") && !shortform.equals(e.getIRI().toString())) {
+        String prefix = shortform.split(":")[0];
+        prefixmapUsed.put(prefix, prefixmap.get(prefix));
+        return prefix;
+      }
+    }
+
+    LOGGER.info("Entity " + e.getIRI() + " does not have a known prefix.");
+    return "prefix_unknown";
+  }
+
+  private Map<String, Integer> getAxiomUsageMap(Imports includeImportsClosure) {
+    Map<String, Integer> map = new HashMap<>();
+    Set<OWLAxiom> axioms = getAxioms(includeImportsClosure);
+    for (OWLAxiom axiom : axioms) {
+      Set<OWLEntity> entities = axiom.getSignature();
+      for (OWLEntity e : entities) {
+        String iri_pre = extractPrefixForEntityOrOtherIfUnknown(e);
+        countKeyUpInMap(iri_pre, map);
+      }
+    }
+    return map;
+  }
+
+  private Set<String> getBuiltInDatatypes(Imports included) {
+    Set<String> set = new HashSet<>();
+    Set<OWLDatatype> datatypes = getOntology().getDatatypesInSignature(included);
+    for (OWLDatatype datatype : datatypes) {
+      if (datatype.isBuiltIn()) {
+        set.add(datatype.getBuiltInDatatype().toString());
+      }
+    }
+    return set;
+  }
+
+  private Set<String> getNotBuiltInDatatypes(Imports included) {
+    Set<String> set = new HashSet<>();
+    Set<OWLDatatype> properties = getOntology().getDatatypesInSignature(included);
+    for (OWLDatatype datatype : properties) {
+      if (!datatype.isBuiltIn()) {
+        set.add(datatype.toString());
+      }
+    }
+    return set;
+  }
+
+  private int getAnnotationPropertyCount(Imports imports) {
+    return getOntology().getAnnotationPropertiesInSignature(imports).size();
+  }
+
+  // AXIOMS
+
+  private int getAnnotationsCount() {
+    return getOntology().getAnnotations().size();
+  }
+
+  private int getIndividualsCount(Imports included) {
+    return getOntology().getIndividualsInSignature(included).size();
+  }
+
+  private int getNumberOfRules(Imports includeImportsClosure) {
+    int ct = 0;
+    Set<OWLAxiom> logicalaxiom = getLogicalAxioms(includeImportsClosure, false);
+    for (OWLAxiom ax : logicalaxiom) {
+      if (ax.isLogicalAxiom()) {
+        if (ax.getAxiomType().toString().equals("Rule")) {
+          ct++;
+        }
+      }
+    }
+    return ct;
+  }
+
+  private int getAxiomsWithComplexRHS(Imports included) {
+    // complex: RHS does not only contain nested conjuctions / atomic
+    // classnames
+    int ct = 0;
+    Set<OWLAxiom> logicalaxiom = getLogicalAxioms(included, false);
+    for (OWLAxiom ax : logicalaxiom) {
+      if (ax instanceof OWLSubClassOfAxiom) {
+        OWLSubClassOfAxiom scax = (OWLSubClassOfAxiom) ax;
+        OWLClassExpression RHS = scax.getSuperClass();
+        if (isComplex(RHS)) {
+          ct++;
+        }
+      } else if (ax instanceof OWLEquivalentClassesAxiom) {
+        OWLEquivalentClassesAxiom scax = (OWLEquivalentClassesAxiom) ax;
+        for (OWLClassExpression ex : scax.getClassExpressions()) {
+          if (isComplex(ex)) {
+            ct++;
+            break;
+          }
+        }
+      }
+    }
+    return ct;
+  }
+
+  private double getAVGSizeOfRHS(Imports included) {
+    // complex: RHS does not only contain nested conjuctions / atomic
+    // classnames
+    double ct = 0;
+    double ct_complex = 0;
+    Set<OWLAxiom> logicalaxiom = getLogicalAxioms(included, false);
+    for (OWLAxiom ax : logicalaxiom) {
+      if (ax instanceof OWLSubClassOfAxiom) {
+        OWLSubClassOfAxiom scax = (OWLSubClassOfAxiom) ax;
+        OWLClassExpression RHS = scax.getSuperClass();
+        if (isComplex(RHS)) {
+          // System.out.println(RHS);
+          ct_complex++;
+          for (OWLClassExpression ex : RHS.getNestedClassExpressions()) {
+            if (isComplex(ex)) {
+              ct++;
+            }
+          }
+        }
+      } else if (ax instanceof OWLEquivalentClassesAxiom) {
+        OWLEquivalentClassesAxiom scax = (OWLEquivalentClassesAxiom) ax;
+        for (OWLClassExpression oper : scax.getClassExpressions()) {
+          if (isComplex(oper)) {
+            ct_complex++;
+            for (OWLClassExpression ex : oper.getNestedClassExpressions()) {
+              if (isComplex(ex)) {
+                ct++;
+              }
+            }
+          }
+        }
+      }
+    }
+    return (ct / ct_complex);
+  }
+
+  private boolean isComplex(OWLClassExpression ex) {
+    for (OWLClassExpression exnested : ex.getNestedClassExpressions()) {
+      if (!exnested.isClassExpressionLiteral()) {
+        if (!(exnested instanceof OWLObjectIntersectionOf)) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  private Set<OWLAxiom> getLogicalAxioms(Imports includeImportsClosure, boolean skiprules) {
+    Set<AxiomType<?>> at = new HashSet<>();
+    at.addAll(AxiomType.TBoxAxiomTypes);
+    at.addAll(AxiomType.RBoxAxiomTypes);
+    at.addAll(AxiomType.ABoxAxiomTypes);
+    return MetricsUtils.getLogicalAxioms(getOntology(), includeImportsClosure, skiprules, at);
+  }
+
+  private Set<OWLAxiom> getAxioms(Imports included) {
+    return new HashSet<>(getOntology().getAxioms(included));
+  }
+
+  private int getAxiomCount(Imports includeImportsClosure) {
+    return getAxioms(includeImportsClosure).size();
+  }
+
+  private int getLogicalAxiomCount(Imports includeImportsClosure) {
+    return getLogicalAxioms(includeImportsClosure, true).size();
+  }
+
+  private int getTBoxSize(Imports useImportsClosure) {
+    return getOntology().getTBoxAxioms(useImportsClosure).size();
+  }
+
+  private int getTBoxRboxSize(Imports useImportsClosure) {
+    int i = 0;
+    Set<AxiomType<?>> axty = MetricsUtils.getTBoxAxiomTypes(true);
+    for (AxiomType<?> at : axty) {
+      i += getOntology().getAxioms(at, useImportsClosure).size();
+    }
+    return i;
+  }
+
+  private int getABoxSize(Imports useImportsClosure) {
+    return getOntology().getABoxAxioms(useImportsClosure).size();
+  }
+
+  private int getRBoxSize(Imports useImportsClosure) {
+    return getOntology().getRBoxAxioms(useImportsClosure).size();
+  }
+
+  private Map<String, Integer> getOWLClassExpressionCounts(Imports includeImportsClosure) {
+    Map<String, Integer> map = new HashMap<>();
+    Set<OWLAxiom> axioms = getAxioms(includeImportsClosure);
+
+    for (OWLAxiom ax : axioms) {
+      for (OWLClassExpression exp : ax.getNestedClassExpressions()) {
+        String type = exp.getClassExpressionType().getName();
+        if (map.containsKey(type)) {
+          Integer i = map.get(type);
+          map.put(type, (i + 1));
+        } else {
+          map.put(type, 1);
+        }
+      }
+    }
+
+    return map;
+  }
+
+  private Map<String, Integer> getAxiomTypeCounts(Imports includeImportsClosure) {
+    Map<String, Integer> map = new HashMap<>();
+    Set<OWLAxiom> axioms = getAxioms(includeImportsClosure);
+
+    for (OWLAxiom ax : axioms) {
+      String type = ax.getAxiomType().getName();
+      if (map.containsKey(type)) {
+        Integer i = map.get(type);
+        map.put(type, (i + 1));
+      } else {
+        map.put(type, 1);
+      }
+    }
+
+    return map;
+  }
+
+  private int getTautologyCount(Imports includeImports) {
+    int tautologies = 0;
+    for (OWLAxiom ax : getLogicalAxioms(includeImports, true)) {
+      if (TautologyChecker.isTautology(ax)) {
+        tautologies++;
+      }
+    }
+    return tautologies;
+  }
+
+  private Set<AxiomType<?>> getAxiomTypes(Imports includeImportsClosure) {
+    Set<AxiomType<?>> axtypes = new HashSet<>();
+    for (OWLAxiom ax : getAxioms(includeImportsClosure)) {
+      axtypes.add(ax.getAxiomType());
+    }
+    return axtypes;
+  }
+
+  private boolean isOWL2Profile() {
+    OWLProfile profile = new OWL2Profile();
+    return profile.checkOntology(getOntology()).isInProfile();
+  }
+
+  private boolean isOWL2ELProfile() {
+    OWLProfile profile = new OWL2ELProfile();
+    return profile.checkOntology(getOntology()).isInProfile();
+  }
+
+  private boolean isOWL2DLProfile() {
+    OWLProfile profile = new OWL2DLProfile();
+    OWLProfileReport report = profile.checkOntology(getOntology());
+    for (OWLProfileViolation vio : report.getViolations()) {
+      String s = vio.getClass().getSimpleName();
+      owlProfileViolationsList.add(s);
+      owlProfileViolations.add(vio);
+    }
+    return report.isInProfile();
+  }
+
+  private boolean isOWL2RLProfile() {
+    OWLProfile profile = new OWL2RLProfile();
+    return profile.checkOntology(getOntology()).isInProfile();
+  }
+
+  private boolean isOWL2QLProfile() {
+    OWLProfile profile = new OWL2QLProfile();
+    return profile.checkOntology(getOntology()).isInProfile();
+  }
+
+  // REFERENCED CLASSES AND PROPERTIES
+
+  private boolean isRDFS() {
+    // TODO: verify
+    boolean isRDFS = true;
+
+    for (OWLAxiom ax : getAxioms(Imports.INCLUDED)) {
+      if (ax.isLogicalAxiom()) {
+        if (ax.isOfType(AxiomType.SUBCLASS_OF)) {
+          OWLSubClassOfAxiom subAx = (OWLSubClassOfAxiom) ax;
+          if (subAx.getSubClass().isAnonymous() || subAx.getSuperClass().isAnonymous()) {
+            isRDFS = false;
+            // System.out.println("SubClassAx: " + ax);
+            break;
+          }
+        } else if (ax.isOfType(AxiomType.SUB_OBJECT_PROPERTY)) {
+          OWLSubObjectPropertyOfAxiom subProp = (OWLSubObjectPropertyOfAxiom) ax;
+          if (subProp.getSubProperty().isAnonymous() || subProp.getSuperProperty().isAnonymous()) {
+            isRDFS = false;
+            // System.out.println("SupPropertyAx: " + ax);
+            break;
+          }
+        } else //noinspection StatementWithEmptyBody
+        if (ax.isOfType(AxiomType.OBJECT_PROPERTY_DOMAIN)
+            || ax.isOfType(AxiomType.OBJECT_PROPERTY_RANGE)
+            || ax.isOfType(AxiomType.DATA_PROPERTY_ASSERTION)
+            || ax.isOfType(AxiomType.OBJECT_PROPERTY_ASSERTION)
+            || ax.isOfType(AxiomType.DATA_PROPERTY_DOMAIN)
+            || ax.isOfType(AxiomType.DATA_PROPERTY_RANGE)) {
+          // do nothing
+          // System.out.println(ax.getAxiomType().getName()+" axiom: "
+          // + ax);
+        } else if (ax.isOfType(AxiomType.CLASS_ASSERTION)) {
+          OWLClassAssertionAxiom assAx = (OWLClassAssertionAxiom) ax;
+          if (assAx.getClassExpression().isAnonymous()) {
+            isRDFS = false;
+            // System.out.println("Class Assertion: " + ax);
+            break;
+          }
+        } else {
+          // System.out.println("Some other axiom: " + ax);
+          isRDFS = false;
+          break;
+        }
+      }
+    }
+
+    return isRDFS;
+  }
+
+  private Set<String> getValidImports(boolean direct) {
+    Set<String> validImports = new HashSet<>();
+    Set<OWLOntology> closure = new HashSet<>();
+
+    if (!direct) {
+      closure.addAll(getOntology().getImportsClosure());
+    }
+    closure.add(getOntology());
+
+    for (OWLOntology o : closure) {
+      for (OWLImportsDeclaration im : o.getImportsDeclarations()) {
+        String iri = im.getIRI().toString();
+        validImports.add(iri);
+      }
+    }
+    return validImports;
+  }
+
+  private int getReferencedClassCount(boolean useImportsClosure) {
+    // TODO verify this
+    AbstractOWLMetric<Integer> metric = new ReferencedClassCount(getOntology());
+    metric.setImportsClosureUsed(useImportsClosure);
+    metric.setOntology(getOntology());
+    return metric.getValue();
+  }
+
+  private int getReferencedDataPropertyCount(boolean useImportsClosure) {
+    // TODO verify this
+    AbstractOWLMetric<Integer> metric = new ReferencedDataPropertyCount(getOntology());
+    metric.setImportsClosureUsed(useImportsClosure);
+    metric.setOntology(getOntology());
+    return metric.getValue();
+  }
+
+  // RANDOM METRICS
+
+  private int getReferencedIndividualCount(boolean useImportsClosure) {
+    // TODO verify this
+    AbstractOWLMetric<Integer> metric = new ReferencedIndividualCount(getOntology());
+    metric.setImportsClosureUsed(useImportsClosure);
+    metric.setOntology(getOntology());
+    return metric.getValue();
+  }
+
+  private int getReferencedObjectPropertyCount(boolean useImportsClosure) {
+    // TODO verify this
+    AbstractOWLMetric<Integer> metric = new ReferencedObjectPropertyCount(getOntology());
+    metric.setImportsClosureUsed(useImportsClosure);
+    metric.setOntology(getOntology());
+    return metric.getValue();
+  }
+
+  private int getMultipleInheritanceCount(boolean useImportsClosure) {
+    // TODO verify this
+    AbstractOWLMetric<Integer> metric = new NumberOfClassesWithMultipleInheritance(getOntology());
+    metric.setImportsClosureUsed(useImportsClosure);
+    metric.setOntology(getOntology());
+    return metric.getValue();
+  }
+
+  private int getMaximumNumberOfNamedSuperclasses(boolean useImportsClosure) {
+    // TODO verify this
+    AbstractOWLMetric<Integer> metric = new MaximumNumberOfNamedSuperclasses(getOntology());
+    metric.setImportsClosureUsed(useImportsClosure);
+    metric.setOntology(getOntology());
+    return metric.getValue();
+  }
+
+  private int getGCICount(boolean useImportsClosure) {
+    // TODO verify this
+    AbstractOWLMetric<Integer> metric = new GCICount(getOntology());
+    metric.setImportsClosureUsed(useImportsClosure);
+    metric.setOntology(getOntology());
+    return metric.getValue();
+  }
+
+  private int getHiddenGCICount(boolean useImportsClosure) {
+    // TODO verify this
+    AbstractOWLMetric<Integer> metric = new HiddenGCICount(getOntology());
+    metric.setImportsClosureUsed(useImportsClosure);
+    metric.setOntology(getOntology());
+    return metric.getValue();
+  }
+
+  private double getAverageAssertedNamedSuperclasses(boolean useImportsClosure) {
+    // TODO verify this
+    AbstractOWLMetric<Double> metric = new AverageAssertedNamedSuperclassCount(getOntology());
+    metric.setImportsClosureUsed(useImportsClosure);
+    metric.setOntology(getOntology());
+    return metric.getValue();
+  }
+
+  private double getAverageAssertedNamedSubclasses(Imports useImportsClosure) {
+    int count = 0;
+    Set<OWLClass> classes = getOntology().getClassesInSignature(useImportsClosure);
+    for (OWLClass c : classes) {
+      count += getOntology().getSubClassAxiomsForSuperClass(c).size();
+    }
+    return (((double) count) / ((double) classes.size()));
+  }
+
+  private int getClassesWithSingleSubclassCount(Imports useImportsClosure) {
+    int count = 0;
+    for (OWLClass c : getOntology().getClassesInSignature(useImportsClosure)) {
+      if (getOntology().getSubClassAxiomsForSuperClass(c).size() == 1) {
+        count++;
+      }
+    }
+    return count;
+  }
+
+  private double getAverageInstancesPerClass(Imports useImportsClosure) {
+    int count = 0;
+    for (OWLClass c : getOntology().getClassesInSignature(useImportsClosure)) {
+      count += getOntology().getClassAssertionAxioms(c).size();
+    }
+    return (((double) count)
+        / ((double) getOntology().getClassesInSignature(useImportsClosure).size()));
+  }
+
+  private String getSyntax() {
+    Object format = getManager().getOntologyFormat(getOntology());
+    if (format != null) {
+      return format.toString();
+    }
+    return "unknown";
+  }
+
+  private String getOntologyId() {
+    OWLOntologyID ontologyID = getOntology().getOntologyID();
+    if (ontologyID.isAnonymous()) {
+      return "anonymousId";
+    } else {
+      return ontologyID.getOntologyIRI().or(IRI.create("no.iri")).toString();
+    }
+  }
+
+  private String getOntologyVersionId() {
+    OWLOntologyID ontologyID = getOntology().getOntologyID();
+    if (ontologyID.isAnonymous()) {
+      return "anonymousId";
+    } else {
+      return ontologyID.getVersionIRI().or(IRI.create("no.iri")).toString();
+    }
+  }
+
+  private String getExpressivity(boolean included) {
+    DLExpressivity dl = new DLExpressivity(getOntology());
+    dl.setImportsClosureUsed(included);
+    dl.setOntology(getOntology());
+    return dl.getValue();
+  }
+
+  // this is highly unpleasant and I wish we had a NoSQL DB or even just a
+  // stupid XML file where we could throw it all in individually
+  private Set<String> getConstructs(boolean includeImportsClosure) {
+    Set<OWLOntology> onts = new HashSet<>();
+    if (includeImportsClosure) {
+      onts.addAll(getOntology().getImportsClosure());
+    } else {
+      onts.add(getOntology());
+    }
+    DLExpressivityChecker checker = new DLExpressivityChecker(onts);
+    Set<String> constructs = new HashSet<>();
+    for (DLExpressivityChecker.Construct c : checker.getConstructs()) {
+      constructs.add(c.name());
+    }
+    return constructs;
+  }
+
+  private boolean surelyContainsCycle(Imports includeImports) {
+    return OntologyCycleDetector.containsCycle(getOntology(), includeImports);
+  }
+
+  /** @return Ontology used for metrics computation */
+  public OWLOntology getOntology() {
+    return this.ontology;
+  }
+
+  private Optional<CURIEShortFormProvider> getCurieProvider() {
+    if (this.curieProvider != null) {
+      return Optional.of(this.curieProvider);
+    }
+    return Optional.empty();
+  }
+
+  private OWLOntologyManager getManager() {
+    return this.manager;
+  }
+
+  private Map<String, Integer> getOwlprofileviolations() {
+    Map<String, Integer> map = new HashMap<>();
+    Set<String> uniqueviolations = new HashSet<>(owlProfileViolationsList);
+    for (String vio : uniqueviolations) {
+      map.put(vio, Collections.frequency(owlProfileViolationsList, vio));
+    }
+    return map;
+  }
+
+  private String getMostFrequentlyUsedClassInLogicalAxioms(Imports includeImportsClosure) {
+    Map<String, Integer> classCountMap = new HashMap<>();
+    for (OWLAxiom eachAxiom : getLogicalAxioms(includeImportsClosure, true)) {
+      if (!(eachAxiom instanceof OWLSubClassOfAxiom)) {
+        continue;
+      }
+      OWLSubClassOfAxiom sax = (OWLSubClassOfAxiom) eachAxiom;
+      String saxrhs = sax.getSuperClass().toString();
+      for (OWLClass eachClass : eachAxiom.getClassesInSignature()) {
+        int frequency = getNumberOfOccurences(saxrhs, eachClass.toString());
+        String class_sf = eachClass.getIRI().toString();
+        if (classCountMap.containsKey(eachClass.toString())) {
+          Integer currentClassCount = classCountMap.get(eachClass.toString());
+          classCountMap.put(class_sf, currentClassCount + frequency);
+        } else {
+          classCountMap.put(class_sf, frequency);
+        }
+      }
+    }
+    int max = 0;
+    String maxClassString = "";
+    for (String eachKey : classCountMap.keySet()) {
+      int eachCount = classCountMap.get(eachKey);
+      if (eachCount > max) {
+        max = eachCount;
+        maxClassString = eachKey;
+      }
+    }
+    return maxClassString;
+  }
+
+  private int getLongestAxiomLength(Imports includeImportsClosure) {
+    int longestAxiomLength = 0;
+
+    for (OWLAxiom eachAxiom : getLogicalAxioms(includeImportsClosure, true)) {
+      int eachLength = getLengthOfAxiom(eachAxiom);
+      if (eachLength > longestAxiomLength) {
+        longestAxiomLength = eachLength;
+      }
+    }
+    return longestAxiomLength;
+  }
+
+  private int getDatatypesNotBuiltinCount(Imports included) {
+    return getNotBuiltInDatatypes(included).size();
+  }
+
+  private int getDatatypesBuiltinCount(Imports included) {
+    return getBuiltInDatatypes(included).size();
+  }
+
+  private String getSignatureWithoutIRIs(Imports incl) {
+    StringBuilder b = new StringBuilder();
+    for (OWLEntity e : getOntology().getSignature(incl)) {
+      if (e.isBottomEntity() || e.isTopEntity()) {
+        continue;
+      }
+      if (e instanceof OWLClass) {
+        b.append(e.getIRI().getRemainder().or("unknown")).append("; ");
+      } else if (e instanceof OWLObjectProperty) {
+        b.append(e.getIRI().getRemainder().or("unknown")).append("; ");
+      } else if (e instanceof OWLDataProperty) {
+        b.append(e.getIRI().getRemainder().or("unknown")).append("; ");
+      }
+    }
+    return b.toString().contains(";") ? b.substring(0, b.toString().lastIndexOf(";")) : "";
+  }
+
+  private List<OWLProfileViolation> getOWLDLProfileViolations() {
+    isOWL2DLProfile();
+    return owlProfileViolations;
+  }
+}
diff --git a/robot-core/src/main/java/org/obolibrary/robot/metrics/TautologyChecker.java b/robot-core/src/main/java/org/obolibrary/robot/metrics/TautologyChecker.java
new file mode 100644
index 00000000..d87e6a50
--- /dev/null
+++ b/robot-core/src/main/java/org/obolibrary/robot/metrics/TautologyChecker.java
@@ -0,0 +1,81 @@
+package org.obolibrary.robot.metrics;
+
+import java.util.HashSet;
+import java.util.Set;
+import org.semanticweb.owlapi.apibinding.OWLManager;
+import org.semanticweb.owlapi.model.*;
+import org.semanticweb.owlapi.reasoner.OWLReasoner;
+import org.semanticweb.owlapi.reasoner.OWLReasonerFactory;
+
+@SuppressWarnings("unused")
+public class TautologyChecker {
+
+  /**
+   * @param ax the axiom to be checked
+   * @return true if the axiom is a tautology
+   */
+  public static boolean isTautology(OWLAxiom ax) {
+    OWLOntologyManager emptyman = OWLManager.createOWLOntologyManager();
+    OWLOntology emptyo;
+    try {
+      emptyo = emptyman.createOntology();
+      OWLReasonerFactory fac = new org.semanticweb.HermiT.ReasonerFactory();
+      OWLReasoner reasoner = fac.createReasoner(emptyo);
+      return reasoner.isEntailed(ax);
+    } catch (OWLOntologyCreationException e) {
+      // TODO Auto-generated catch block
+      e.printStackTrace();
+    }
+    return false;
+  }
+
+  /**
+   * @param axioms the axioms to be checked
+   * @return all those axioms that are tautologies
+   * @throws OWLOntologyCreationException on problem creating reasoner
+   */
+  public static Set<OWLAxiom> getTautologies(Set<OWLAxiom> axioms)
+      throws OWLOntologyCreationException {
+    Set<OWLAxiom> delSet = new HashSet<>();
+    OWLReasoner r =
+        new org.semanticweb.HermiT.ReasonerFactory()
+            .createReasoner(OWLManager.createOWLOntologyManager().createOntology());
+    for (OWLAxiom ax : axioms) {
+      if (r.isEntailed(ax)) {
+        delSet.add(ax);
+      }
+    }
+    return delSet;
+  }
+
+  // Filtering of axioms - remove declarations and tautologies. At the moment
+  // tautologies are
+  // just defined w.r.t classification. Later versions will require something
+  // more complex.
+
+  /**
+   * @param axioms the axioms to be checked
+   * @return all those axioms that are tautologies (syntactic check, incomplete!)
+   */
+  public static Set<OWLAxiom> getTautologySyntactic(Set<OWLAxiom> axioms) {
+    Set<OWLAxiom> filter = new HashSet<>();
+    for (OWLAxiom ax : axioms) {
+      if (ax.isOfType(AxiomType.DECLARATION)) {
+        filter.add(ax);
+      } else {
+        // Sanity check
+        if (ax.isOfType(AxiomType.SUBCLASS_OF)) {
+          if (isTautology((OWLSubClassOfAxiom) ax)) {
+            filter.add(ax);
+          }
+        }
+      }
+    }
+    axioms.removeAll(filter);
+    return axioms;
+  }
+
+  private static Boolean isTautology(OWLSubClassOfAxiom ax) {
+    return ax.getSubClass().isOWLNothing() || ax.getSuperClass().isOWLThing();
+  }
+}
diff --git a/robot-core/src/main/java/org/obolibrary/robot/providers/CURIEShortFormProvider.java b/robot-core/src/main/java/org/obolibrary/robot/providers/CURIEShortFormProvider.java
index a886ae86..95e22ebb 100644
--- a/robot-core/src/main/java/org/obolibrary/robot/providers/CURIEShortFormProvider.java
+++ b/robot-core/src/main/java/org/obolibrary/robot/providers/CURIEShortFormProvider.java
@@ -66,6 +66,15 @@ public class CURIEShortFormProvider implements ShortFormProvider {
     return iriString;
   }
 
+  /**
+   * Get a copy of the full current curie map.
+   *
+   * @return sortedPrefixMap
+   */
+  public List<Map.Entry<String, String>> getSortedPrefixMap() {
+    return new ArrayList<>(sortedPrefixMap);
+  }
+
   /** Dispose of the ShortFormProvider */
   @Override
   public void dispose() {}


Test patch:
diff --git a/robot-core/src/test/java/org/obolibrary/robot/MeasureOperationTest.java b/robot-core/src/test/java/org/obolibrary/robot/MeasureOperationTest.java
new file mode 100644
index 00000000..dc9a3ef1
--- /dev/null
+++ b/robot-core/src/test/java/org/obolibrary/robot/MeasureOperationTest.java
@@ -0,0 +1,135 @@
+package org.obolibrary.robot;
+
+import static org.junit.Assert.*;
+
+import java.io.File;
+import java.io.IOException;
+import org.junit.Test;
+import org.obolibrary.robot.metrics.MeasureResult;
+import org.obolibrary.robot.metrics.MetricsLabels;
+import org.obolibrary.robot.providers.CURIEShortFormProvider;
+import org.semanticweb.elk.io.FileUtils;
+import org.semanticweb.elk.owlapi.ElkReasonerFactory;
+import org.semanticweb.owlapi.model.OWLOntology;
+import org.semanticweb.owlapi.reasoner.OWLReasonerFactory;
+
+/**
+ * Test metrics operation.trea
+ *
+ * @author <a href="mailto:nicolas.matentzoglu@gmail.com">Nicolas Matentzoglu</a>
+ */
+public class MeasureOperationTest extends CoreTest {
+
+  String TESTONTOLOGY = "uberon.owl";
+
+  /**
+   * Tests essential metrics generation.
+   *
+   * @throws IOException on IO error
+   */
+  @Test
+  public void testEssentialMetrics() throws IOException {
+    OWLOntology ontology = loadOntology("/" + TESTONTOLOGY);
+    MeasureResult result =
+        MeasureOperation.getMetrics(
+            ontology, "essential", new CURIEShortFormProvider(new IOHelper().getPrefixes()));
+    assertEquals(313, result.getSimpleMetricValue(MetricsLabels.AXIOM_COUNT));
+  }
+
+  /**
+   * Tests extended metrics generation.
+   *
+   * @throws IOException on IO error
+   */
+  @Test
+  public void testExtendedMetrics() throws IOException {
+    OWLOntology ontology = loadOntology("/" + TESTONTOLOGY);
+    MeasureResult result =
+        MeasureOperation.getMetrics(
+            ontology, "extended", new CURIEShortFormProvider(new IOHelper().getPrefixes()));
+    assertEquals("SI(D)", result.getSimpleMetricValue(MetricsLabels.EXPRESSIVITY));
+  }
+
+  /**
+   * Tests all metrics generation.
+   *
+   * @throws IOException on IO error
+   */
+  @Test
+  public void testAllMetrics() throws IOException {
+    OWLOntology ontology = loadOntology("/" + TESTONTOLOGY);
+    MeasureResult result =
+        MeasureOperation.getMetrics(
+            ontology, "all", new CURIEShortFormProvider(new IOHelper().getPrefixes()));
+    assertEquals(0, result.getSimpleMetricValue(MetricsLabels.GCI_COUNT_INCL));
+  }
+
+  /**
+   * Tests all metrics generation.
+   *
+   * @throws IOException on IO error
+   */
+  @Test
+  public void writeAllMetricsToTSV() throws IOException {
+    OWLOntology ontology = loadOntology("/" + TESTONTOLOGY);
+    OWLReasonerFactory rf = new ElkReasonerFactory();
+    MeasureResult result =
+        MeasureOperation.getMetrics(
+            ontology, rf, "all-reasoner", new CURIEShortFormProvider(new IOHelper().getPrefixes()));
+    File testFile = new File("test.tsv");
+    MeasureOperation.maybeWriteResult(result, "tsv", testFile);
+    assertTrue(testFile.exists());
+    FileUtils.deleteRecursively(testFile);
+  }
+
+  /**
+   * Tests all metrics generation.
+   *
+   * @throws IOException on IO error
+   */
+  @Test
+  public void writeAllMetricsToCSV() throws IOException {
+    OWLOntology ontology = loadOntology("/" + TESTONTOLOGY);
+    MeasureResult result =
+        MeasureOperation.getMetrics(
+            ontology, "all", new CURIEShortFormProvider(new IOHelper().getPrefixes()));
+    File testFile = new File("test.csv");
+    MeasureOperation.maybeWriteResult(result, "csv", testFile);
+    assertTrue(testFile.exists());
+    FileUtils.deleteRecursively(testFile);
+  }
+
+  /**
+   * Tests all metrics generation.
+   *
+   * @throws IOException on IO error
+   */
+  @Test
+  public void writeAllMetricsToJSON() throws IOException {
+    OWLOntology ontology = loadOntology("/" + TESTONTOLOGY);
+    MeasureResult result =
+        MeasureOperation.getMetrics(
+            ontology, "all", new CURIEShortFormProvider(new IOHelper().getPrefixes()));
+    File testFile = new File("test.json");
+    MeasureOperation.maybeWriteResult(result, "json", testFile);
+    assertTrue(testFile.exists());
+    FileUtils.deleteRecursively(testFile);
+  }
+
+  /**
+   * Tests all metrics generation.
+   *
+   * @throws IOException on IO error
+   */
+  @Test
+  public void writeAllMetricsToYAML() throws IOException {
+    OWLOntology ontology = loadOntology("/" + TESTONTOLOGY);
+    MeasureResult result =
+        MeasureOperation.getMetrics(
+            ontology, "all", new CURIEShortFormProvider(new IOHelper().getPrefixes()));
+    File testFile = new File("test.yml");
+    MeasureOperation.maybeWriteResult(result, "yaml", testFile);
+    assertTrue(testFile.exists());
+    FileUtils.deleteRecursively(testFile);
+  }
+}