codecov[bot]: # [Codecov](https://codecov.io/gh/pybel/pybel/pull/385?src=pr&el=h1) Report
> :exclamation: No coverage uploaded for pull request base (`develop@d4264e3`). [Click here to learn what that means](https://docs.codecov.io/docs/error-reference#section-missing-base-commit).
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/pybel/pybel/pull/385/graphs/tree.svg?width=650&token=J7joRTRygG&height=150&src=pr)](https://codecov.io/gh/pybel/pybel/pull/385?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##             develop     #385   +/-   ##
==========================================
  Coverage           ?   87.14%           
==========================================
  Files              ?      133           
  Lines              ?     6403           
  Branches           ?      850           
==========================================
  Hits               ?     5580           
  Misses             ?      637           
  Partials           ?      186
```


| [Impacted Files](https://codecov.io/gh/pybel/pybel/pull/385?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/pybel/parser/parse\_bel.py](https://codecov.io/gh/pybel/pybel/pull/385/diff?src=pr&el=tree#diff-c3JjL3B5YmVsL3BhcnNlci9wYXJzZV9iZWwucHk=) | `96.35% <100%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/pybel/pybel/pull/385?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/pybel/pybel/pull/385?src=pr&el=footer). Last update [d4264e3...9420979](https://codecov.io/gh/pybel/pybel/pull/385?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

codecov[bot]: # [Codecov](https://codecov.io/gh/pybel/pybel/pull/385?src=pr&el=h1) Report
> :exclamation: No coverage uploaded for pull request base (`develop@d4264e3`). [Click here to learn what that means](https://docs.codecov.io/docs/error-reference#section-missing-base-commit).
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/pybel/pybel/pull/385/graphs/tree.svg?width=650&token=J7joRTRygG&height=150&src=pr)](https://codecov.io/gh/pybel/pybel/pull/385?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##             develop     #385   +/-   ##
==========================================
  Coverage           ?   87.14%           
==========================================
  Files              ?      133           
  Lines              ?     6403           
  Branches           ?      850           
==========================================
  Hits               ?     5580           
  Misses             ?      637           
  Partials           ?      186
```


| [Impacted Files](https://codecov.io/gh/pybel/pybel/pull/385?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/pybel/parser/parse\_bel.py](https://codecov.io/gh/pybel/pybel/pull/385/diff?src=pr&el=tree#diff-c3JjL3B5YmVsL3BhcnNlci9wYXJzZV9iZWwucHk=) | `96.35% <100%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/pybel/pybel/pull/385?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/pybel/pybel/pull/385?src=pr&el=footer). Last update [d4264e3...9420979](https://codecov.io/gh/pybel/pybel/pull/385?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

diff --git a/src/pybel/parser/parse_bel.py b/src/pybel/parser/parse_bel.py
index 15af30bd..16f53149 100644
--- a/src/pybel/parser/parse_bel.py
+++ b/src/pybel/parser/parse_bel.py
@@ -262,6 +262,7 @@ class BELParser(BaseParser):
         :param required_annotations: Optional list of required annotations
         """
         self.graph = graph
+        self.metagraph = set()
 
         self.allow_nested = allow_nested
         self.disallow_unqualified_translocations = disallow_unqualified_translocations
@@ -637,17 +638,18 @@ class BELParser(BaseParser):
         if not self.allow_nested:
             raise NestedRelationWarning(self.get_line_number(), line, position)
 
-        self._handle_relation_harness(line, position, {
+        subject_hash = self._handle_relation_checked(line, position, {
             SUBJECT: tokens[SUBJECT],
             RELATION: tokens[RELATION],
             OBJECT: tokens[OBJECT][SUBJECT],
         })
 
-        self._handle_relation_harness(line, position, {
+        object_hash = self._handle_relation_checked(line, position, {
             SUBJECT: tokens[OBJECT][SUBJECT],
             RELATION: tokens[OBJECT][RELATION],
             OBJECT: tokens[OBJECT][OBJECT],
         })
+        self.metagraph.add((subject_hash, object_hash))
         return tokens
 
     def check_function_semantics(self, line: str, position: int, tokens: ParseResults) -> ParseResults:
@@ -784,6 +786,10 @@ class BELParser(BaseParser):
 
         Note: this can't be changed after instantiation!
         """
+        self._handle_relation_checked(line, position, tokens)
+        return tokens
+
+    def _handle_relation_checked(self, line, position, tokens):
         if not self.control_parser.citation:
             raise MissingCitationException(self.get_line_number(), line, position)
 
@@ -794,8 +800,7 @@ class BELParser(BaseParser):
         if missing_required_annotations:
             raise MissingAnnotationWarning(self.get_line_number(), line, position, missing_required_annotations)
 
-        self._handle_relation(tokens)
-        return tokens
+        return self._handle_relation(tokens)
 
     def handle_unqualified_relation(self, _, __, tokens: ParseResults) -> ParseResults:
         """Handle unqualified relations."""

Build metagraph from nested statements
Though a graph data structure doesn't directly enable edges to be treated as nodes, edges can carry additional information about transitivity. For example, `A -> (B -> C)` is added to the graph as `A -> B` and `B -> C`, but both edges could include information about inwards transitivity and outwards transitivity.

Each BELGraph could have an additional directed *metagraph* associated with it, where nodes are the hashes of edges in the original graph and edges denote the transitivity between them
diff --git a/tests/test_parse/test_parse_bel_relations.py b/tests/test_parse/test_parse_bel_relations.py
index 3246177f..03b2102d 100644
--- a/tests/test_parse/test_parse_bel_relations.py
+++ b/tests/test_parse/test_parse_bel_relations.py
@@ -575,6 +575,7 @@ class TestRelations(TestTokenParserBase):
 
         self.assert_has_edge(cat, h2o2)
         self.assert_has_edge(h2o2, apoptosis)
+        self.assertEqual(1, len(self.parser.metagraph))
 
         self.parser.lenient = False
 

pip install -e .[neo4j,indra]
parse_log_pytest
pytest
pytest coverage
3.7
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs @ file:///croot/attrs_1668696182826/work
backcall==0.2.0
bel-resources==0.0.3
boto3==1.33.13
botocore==1.33.13
certifi @ file:///croot/certifi_1671487769961/work/certifi
charset-normalizer==3.4.1
click==8.1.8
click-plugins==1.1.1
coverage==7.2.7
decorator==5.1.1
enum34==1.1.10
flit_core @ file:///opt/conda/conda-bld/flit-core_1644941570762/work/source/flit_core
future==1.0.0
greenlet==3.1.1
idna==3.10
ijson==3.3.0
importlib-metadata @ file:///tmp/build/80754af9/importlib-metadata_1648562407465/work
indra==1.22.0
iniconfig @ file:///home/linux1/recipes/ci/iniconfig_1610983019677/work
interchange==2021.0.4
ipython==7.34.0
jedi==0.19.2
Jinja2==3.1.6
jmespath==1.0.1
lxml==5.3.1
MarkupSafe==2.0.1
matplotlib-inline==0.1.6
monotonic==1.6
mpmath==1.3.0
multisplitby==0.0.1
ndex2==2.0.1
networkx==2.6.3
numpy==1.21.6
objectpath==0.6.1
obonet==1.0.0
packaging @ file:///croot/packaging_1671697413597/work
pandas==1.2.5
pansi==2024.11.0
parso==0.8.4
pexpect==4.9.0
pickle5==0.0.12
pickleshare==0.7.5
Pillow==9.5.0
pluggy @ file:///tmp/build/80754af9/pluggy_1648042572264/work
prompt_toolkit==3.0.48
protmapper==0.0.29
ptyprocess==0.7.0
py @ file:///opt/conda/conda-bld/py_1644396412707/work
py2neo==2021.2.4
-e git+https://github.com/pybel/pybel.git@1975dec68f155d11173868474afd6860039e9775#egg=pybel
pybiopax==0.1.5
Pygments==2.17.2
pyparsing==3.1.4
pysb==1.16.0
pysolr==3.10.0
pystow==0.5.6
pytest==7.1.2
python-dateutil==2.9.0.post0
pytz==2025.2
requests==2.31.0
requests-file==2.1.0
requests-toolbelt==1.0.0
s3transfer==0.8.2
scipy==1.7.3
six==1.17.0
SQLAlchemy==2.0.40
sympy==1.10.1
tomli @ file:///opt/conda/conda-bld/tomli_1657175507142/work
tqdm==4.67.1
traitlets==5.9.0
typing_extensions==4.7.1
urllib3==1.26.20
wcwidth==0.2.13
zipp @ file:///croot/zipp_1672387121353/work

name: pybel
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - attrs=22.1.0=py37h06a4308_0
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2022.12.7=py37h06a4308_0
  - flit-core=3.6.0=pyhd3eb1b0_0
  - importlib-metadata=4.11.3=py37h06a4308_0
  - importlib_metadata=4.11.3=hd3eb1b0_0
  - iniconfig=1.1.1=pyhd3eb1b0_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - packaging=22.0=py37h06a4308_0
  - pip=22.3.1=py37h06a4308_0
  - pluggy=1.0.0=py37h06a4308_1
  - py=1.11.0=pyhd3eb1b0_0
  - pytest=7.1.2=py37h06a4308_0
  - python=3.7.16=h7a1cb2a_0
  - readline=8.2=h5eee18b_0
  - setuptools=65.6.3=py37h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tomli=2.0.1=py37h06a4308_0
  - wheel=0.38.4=py37h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zipp=3.11.0=py37h06a4308_0
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - backcall==0.2.0
      - bel-resources==0.0.3
      - boto3==1.33.13
      - botocore==1.33.13
      - charset-normalizer==3.4.1
      - click==8.1.8
      - click-plugins==1.1.1
      - coverage==7.2.7
      - decorator==5.1.1
      - enum34==1.1.10
      - future==1.0.0
      - greenlet==3.1.1
      - idna==3.10
      - ijson==3.3.0
      - indra==1.22.0
      - interchange==2021.0.4
      - ipython==7.34.0
      - jedi==0.19.2
      - jinja2==3.1.6
      - jmespath==1.0.1
      - lxml==5.3.1
      - markupsafe==2.0.1
      - matplotlib-inline==0.1.6
      - monotonic==1.6
      - mpmath==1.3.0
      - multisplitby==0.0.1
      - ndex2==2.0.1
      - networkx==2.6.3
      - numpy==1.21.6
      - objectpath==0.6.1
      - obonet==1.0.0
      - pandas==1.2.5
      - pansi==2024.11.0
      - parso==0.8.4
      - pexpect==4.9.0
      - pickle5==0.0.12
      - pickleshare==0.7.5
      - pillow==9.5.0
      - prompt-toolkit==3.0.48
      - protmapper==0.0.29
      - ptyprocess==0.7.0
      - py2neo==2021.2.4
      - pybiopax==0.1.5
      - pygments==2.17.2
      - pyparsing==3.1.4
      - pysb==1.16.0
      - pysolr==3.10.0
      - pystow==0.5.6
      - python-dateutil==2.9.0.post0
      - pytz==2025.2
      - requests==2.31.0
      - requests-file==2.1.0
      - requests-toolbelt==1.0.0
      - s3transfer==0.8.2
      - scipy==1.7.3
      - six==1.17.0
      - sqlalchemy==2.0.40
      - sympy==1.10.1
      - tqdm==4.67.1
      - traitlets==5.9.0
      - typing-extensions==4.7.1
      - urllib3==1.26.20
      - wcwidth==0.2.13
prefix: /opt/conda/envs/pybel

tests/test_parse/test_parse_bel_relations.py::TestRelations::test_nested_lenient
tests/test_parse/test_parse_bel_relations.py::TestRelations::test_raise_on_relabel
tests/test_parse/test_parse_bel_relations.py::TestRelations::test_cnc_with_subject_variant tests/test_parse/test_parse_bel_relations.py::TestRelations::test_component_list tests/test_parse/test_parse_bel_relations.py::TestRelations::test_decreases tests/test_parse/test_parse_bel_relations.py::TestRelations::test_directlyDecreases tests/test_parse/test_parse_bel_relations.py::TestRelations::test_directlyDecreases_annotationExpansion tests/test_parse/test_parse_bel_relations.py::TestRelations::test_directlyIncreases_withTlocObject tests/test_parse/test_parse_bel_relations.py::TestRelations::test_ensure_no_dup_nodes tests/test_parse/test_parse_bel_relations.py::TestRelations::test_equivalentTo tests/test_parse/test_parse_bel_relations.py::TestRelations::test_extra_1 tests/test_parse/test_parse_bel_relations.py::TestRelations::test_has_reaction_component tests/test_parse/test_parse_bel_relations.py::TestRelations::test_has_variant tests/test_parse/test_parse_bel_relations.py::TestRelations::test_increases tests/test_parse/test_parse_bel_relations.py::TestRelations::test_increases_methylation tests/test_parse/test_parse_bel_relations.py::TestRelations::test_is_a tests/test_parse/test_parse_bel_relations.py::TestRelations::test_label_1 tests/test_parse/test_parse_bel_relations.py::TestRelations::test_member_list tests/test_parse/test_parse_bel_relations.py::TestRelations::test_negativeCorrelation_withObjectVariant tests/test_parse/test_parse_bel_relations.py::TestRelations::test_nested_failure tests/test_parse/test_parse_bel_relations.py::TestRelations::test_orthologous tests/test_parse/test_parse_bel_relations.py::TestRelations::test_partOf tests/test_parse/test_parse_bel_relations.py::TestRelations::test_positiveCorrelation_withSelfReferential tests/test_parse/test_parse_bel_relations.py::TestRelations::test_predicate_failure tests/test_parse/test_parse_bel_relations.py::TestRelations::test_rateLimitingStepOf_subjectActivity tests/test_parse/test_parse_bel_relations.py::TestRelations::test_regulates_with_multiple_nnotations tests/test_parse/test_parse_bel_relations.py::TestRelations::test_singleton tests/test_parse/test_parse_bel_relations.py::TestRelations::test_subProcessOf tests/test_parse/test_parse_bel_relations.py::TestRelations::test_transcription tests/test_parse/test_parse_bel_relations.py::TestRelations::test_translation tests/test_parse/test_parse_bel_relations.py::TestCustom::test_location_undefined_name tests/test_parse/test_parse_bel_relations.py::TestCustom::test_location_undefined_namespace tests/test_parse/test_parse_bel_relations.py::TestCustom::test_tloc_undefined_name tests/test_parse/test_parse_bel_relations.py::TestCustom::test_tloc_undefined_namespace
src/pybel/parser/parse_bel.py
src/pybel/parser/parse_bel.py