Skip to content

Improve handling of symmetric relationships #147

Description

@apdavison

Current handling

When the name of a reverse property coincides with the name of a forward property on the same class, builder/update_openminds.py renames the reverse property using a hard-coded map, and stops the build for any collision not in the map:

# update_openminds.py, in FairgraphClassBuilder.translate()
conflict_resolution = {
    "is_part_of": "is_also_part_of",
}
...
reverse_name_python = generate_python_name(reverse_link_name)
if reverse_name_python in forward_property_names:
    if reverse_name_python in conflict_resolution:
        reverse_name_python = conflict_resolution[reverse_name_python]
    else:
        raise Exception(
            "The following name appears as both a forward and reverse name "
            f"for {class_name}: {reverse_name_python}"
        )

The one case handled today is File and FileBundle, which have a forward isPartOf property (pointing at FileBundle, or FileBundle/FileRepository) and are also targets of WorkflowRecipeVersion.hasPart, whose reverse is also naturally called isPartOf. The result is two properties, is_part_of and is_also_part_of, and a user who wants to know what a file is part of has to query both.

Symmetric relations

A second way the same collision arises is with symmetric relations, where the relation is its own reverse: if A is a variant of B then B is a variant of A. Examples are isVariantOf (v5.0) and isAlternativeVersionOf (v4.0). Such a relation necessarily links a class to itself, e.g. DatasetVersion.isVariantOf → DatasetVersion, so the reverse property lands on the same class as the forward one, with the same name.

Today no symmetric collision arises, because the reverse names come from the builder's own reverse_name_map, which was written to avoid them. But the problem is independent of where the names come from. If we adopt the reverse names proposed in openMetadataInitiative/openMINDS#97 (as amended by openMetadataInitiative/openMINDS#110), isVariantOf is its own reverse on 10 classes in v5.0 (AnatomicalAtlasVersion, CommonCoordinateFrameworkVersion, DatasetVersion, InterfaceVersion, LivePaperVersion, MetaDataModelVersion, ModelVersion, SoftwareVersion, ValidationTestVersion, WorkflowRecipeVersion), and isAlternativeVersionOf on 10 classes in v4.0. Renaming the reverse to something like is_also_variant_of would give users two properties for a single relation.

Proposal

In both situations the forward and reverse links mean the same thing, and a user asking "what is this a part of?" or "what is this a variant of?" wants a single answer. So rather than renaming, generate a single property with the shared name, of a new kind that performs both the forward and the reverse search and merges the results.

Merging is also needed for correctness in the symmetric case, not just convenience: the data are not necessarily reciprocal. A forward query on isVariantOf finds only the instances that declare the link, not those that are declared as variants by another instance, so a purely forward is_variant_of would under-report unless every link is declared in both directions in the KG.

The conflict_resolution map can then be retired.

Things to check when fixing

  • The merged property must be many-valued. DatasetVersion.isVariantOf and File.isPartOf are arrays, so this holds today, but it is worth asserting in the builder, since a single-valued forward property would silently lose the reverse half.
  • The merged property may have mixed target types (for File.is_part_of: FileBundle from the forward link, WorkflowRecipeVersion from the reverse), so the property's type list is the union.
  • On save, only the forward part is written; the reverse links belong to other nodes. This matches how reverse properties are already treated, but it means a value assigned to the merged property is only persisted if its type is valid for the forward link.
  • A test that regenerates against a vocabulary containing a self-reverse name, so this cannot regress.

Related

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions