ai.grakn.graql.internal.reasoner.atom.binary.RelationAtom.java Source code

Java tutorial

Introduction

Here is the source code for ai.grakn.graql.internal.reasoner.atom.binary.RelationAtom.java

Source

/*
 * Grakn - A Distributed Semantic Database
 * Copyright (C) 2016  Grakn Labs Limited
 *
 * Grakn is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Grakn is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Grakn. If not, see <http://www.gnu.org/licenses/gpl.txt>.
 */
package ai.grakn.graql.internal.reasoner.atom.binary;

import ai.grakn.GraknGraph;
import ai.grakn.concept.Concept;
import ai.grakn.concept.ConceptId;
import ai.grakn.concept.Label;
import ai.grakn.concept.OntologyConcept;
import ai.grakn.concept.RelationType;
import ai.grakn.concept.Role;
import ai.grakn.concept.Type;
import ai.grakn.graql.Graql;
import ai.grakn.graql.Var;
import ai.grakn.graql.VarPattern;
import ai.grakn.graql.admin.Answer;
import ai.grakn.graql.admin.Atomic;
import ai.grakn.graql.admin.PatternAdmin;
import ai.grakn.graql.admin.ReasonerQuery;
import ai.grakn.graql.admin.RelationPlayer;
import ai.grakn.graql.admin.Unifier;
import ai.grakn.graql.admin.VarPatternAdmin;
import ai.grakn.graql.internal.pattern.property.IsaProperty;
import ai.grakn.graql.internal.pattern.property.RelationProperty;
import ai.grakn.graql.internal.query.QueryAnswer;
import ai.grakn.graql.internal.reasoner.ResolutionPlan;
import ai.grakn.graql.internal.reasoner.UnifierImpl;
import ai.grakn.graql.internal.reasoner.atom.Atom;
import ai.grakn.graql.internal.reasoner.atom.binary.type.IsaAtom;
import ai.grakn.graql.internal.reasoner.atom.predicate.IdPredicate;
import ai.grakn.graql.internal.reasoner.atom.predicate.Predicate;
import ai.grakn.graql.internal.reasoner.query.ReasonerQueryImpl;
import ai.grakn.graql.internal.reasoner.rule.InferenceRule;
import ai.grakn.graql.internal.reasoner.utils.ReasonerUtils;
import ai.grakn.graql.internal.reasoner.utils.conversion.OntologyConceptConverterImpl;
import ai.grakn.graql.internal.reasoner.utils.conversion.RoleTypeConverter;
import ai.grakn.util.CommonUtil;
import ai.grakn.util.ErrorMessage;
import ai.grakn.util.Schema;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import javafx.util.Pair;

import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

import static ai.grakn.graql.internal.reasoner.utils.ReasonerUtils.checkDisjoint;
import static ai.grakn.graql.internal.reasoner.utils.ReasonerUtils.getCompatibleRelationTypesWithRoles;
import static ai.grakn.graql.internal.reasoner.utils.ReasonerUtils.getListPermutations;
import static ai.grakn.graql.internal.reasoner.utils.ReasonerUtils.getSupers;
import static ai.grakn.graql.internal.reasoner.utils.ReasonerUtils.getUnifiersFromPermutations;
import static ai.grakn.graql.internal.reasoner.utils.ReasonerUtils.multimapIntersection;
import static java.util.stream.Collectors.toSet;

/**
 *
 * <p>
 * Atom implementation defining a relation atom corresponding to a combined {@link RelationProperty}
 * and (optional) {@link IsaProperty}. The relation atom is a {@link TypeAtom} with relation players.
 * </p>
 *
 * @author Kasper Piskorski
 *
 */
public class RelationAtom extends IsaAtom {

    private int hashCode = 0;
    private Multimap<Role, Var> roleVarMap = null;
    private Multimap<Role, String> roleConceptIdMap = null;
    private List<RelationPlayer> relationPlayers = null;

    public RelationAtom(VarPatternAdmin pattern, Var predicateVar, @Nullable IdPredicate predicate,
            ReasonerQuery par) {
        super(pattern, predicateVar, predicate, par);
    }

    private RelationAtom(RelationAtom a) {
        super(a);
        this.relationPlayers = a.relationPlayers;
        this.roleVarMap = a.roleVarMap;
    }

    @Override
    public String toString() {
        String relationString = (isUserDefinedName() ? getVarName() + " " : "")
                + (getOntologyConcept() != null ? getOntologyConcept().getLabel() : "")
                + getRelationPlayers().toString();
        return relationString
                + getIdPredicates().stream().map(IdPredicate::toString).collect(Collectors.joining(""));
    }

    private List<RelationPlayer> getRelationPlayers() {
        if (relationPlayers == null) {
            relationPlayers = new ArrayList<>();
            getPattern().asVar().getProperty(RelationProperty.class)
                    .ifPresent(prop -> prop.getRelationPlayers().forEach(relationPlayers::add));
        }
        return relationPlayers;
    }

    @Override
    public Atomic copy() {
        return new RelationAtom(this);
    }

    /**
     * construct a $varName (rolemap) isa $typeVariable relation
     *
     * @param varName            variable name
     * @param typeVariable       type variable name
     * @param rolePlayerMappings list of rolePlayer-roleType mappings
     * @return corresponding {@link VarPatternAdmin}
     */
    private static VarPatternAdmin constructRelationVarPattern(Var varName, Var typeVariable,
            List<Pair<Var, VarPattern>> rolePlayerMappings) {
        VarPattern var = !varName.getValue().isEmpty() ? varName : Graql.var();
        for (Pair<Var, VarPattern> mapping : rolePlayerMappings) {
            Var rp = mapping.getKey();
            VarPattern role = mapping.getValue();
            var = role == null ? var.rel(rp) : var.rel(role, rp);
        }
        if (!typeVariable.getValue().isEmpty())
            var = var.isa(typeVariable);
        return var.admin();
    }

    @Override
    public int hashCode() {
        if (hashCode == 0) {
            hashCode = 1;
            hashCode = hashCode * 37 + (getTypeId() != null ? getTypeId().hashCode() : 0);
            hashCode = hashCode * 37 + getVarNames().hashCode();
        }
        return hashCode;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null || this.getClass() != obj.getClass())
            return false;
        if (obj == this)
            return true;
        RelationAtom a2 = (RelationAtom) obj;
        return Objects.equals(this.getTypeId(), a2.getTypeId()) && this.getVarNames().equals(a2.getVarNames())
                && getRelationPlayers().equals(a2.getRelationPlayers());
    }

    @Override
    public boolean isEquivalent(Object obj) {
        if (obj == null || this.getClass() != obj.getClass())
            return false;
        if (obj == this)
            return true;
        RelationAtom a2 = (RelationAtom) obj;
        return (isUserDefinedName() == a2.isUserDefinedName()) && Objects.equals(this.getTypeId(), a2.getTypeId())
                && getRoleConceptIdMap().equals(a2.getRoleConceptIdMap())
                && getRoleTypeMap().equals(a2.getRoleTypeMap())
                && getRolePlayers().size() == a2.getRolePlayers().size();
    }

    @Override
    public int equivalenceHashCode() {
        int equivalenceHashCode = 1;
        equivalenceHashCode = equivalenceHashCode * 37
                + (this.getTypeId() != null ? this.getTypeId().hashCode() : 0);
        equivalenceHashCode = equivalenceHashCode * 37 + this.getRoleConceptIdMap().hashCode();
        equivalenceHashCode = equivalenceHashCode * 37 + this.getRoleTypeMap().hashCode();
        return equivalenceHashCode;
    }

    @Override
    public boolean isRelation() {
        return true;
    }

    @Override
    public boolean isSelectable() {
        return true;
    }

    @Override
    public boolean isType() {
        return getOntologyConcept() != null;
    }

    @Override
    public boolean requiresMaterialisation() {
        return isUserDefinedName();
    }

    @Override
    public boolean isAllowedToFormRuleHead() {
        //can form a rule head if specified type and all relation players have a specified/unambiguously inferrable role type
        return super.isAllowedToFormRuleHead() && !hasMetaRoles();
    }

    @Override
    public Set<String> validateOntologically() {
        Set<String> errors = new HashSet<>();
        OntologyConcept type = getOntologyConcept();
        if (type != null && !type.isRelationType()) {
            errors.add(ErrorMessage.VALIDATION_RULE_INVALID_RELATION_TYPE.getMessage(type.getLabel()));
            return errors;
        }

        //check roles are ok
        Map<Var, OntologyConcept> varOntologyConceptMap = getParentQuery().getVarOntologyConceptMap();

        for (Map.Entry<Role, Collection<Var>> e : getRoleVarMap().asMap().entrySet()) {
            Role role = e.getKey();
            if (!Schema.MetaSchema.isMetaLabel(role.getLabel())) {
                //check whether this role can be played in this relation
                if (type != null && type.asRelationType().relates().noneMatch(r -> r.equals(role))) {
                    errors.add(ErrorMessage.VALIDATION_RULE_ROLE_CANNOT_BE_PLAYED.getMessage(role.getLabel(),
                            type.getLabel()));
                }

                //check whether the role player's type allows playing this role
                for (Var player : e.getValue()) {
                    OntologyConcept playerType = varOntologyConceptMap.get(player);
                    if (playerType != null && playerType.asType().plays().noneMatch(plays -> plays.equals(role))) {
                        errors.add(ErrorMessage.VALIDATION_RULE_TYPE_CANNOT_PLAY_ROLE.getMessage(
                                playerType.getLabel(), role.getLabel(), type == null ? "" : type.getLabel()));
                    }
                }
            }
        }
        return errors;
    }

    @Override
    public int computePriority(Set<Var> subbedVars) {
        int priority = super.computePriority(subbedVars);
        priority += ResolutionPlan.IS_RELATION_ATOM;
        return priority;
    }

    @Override
    public Set<IdPredicate> getPartialSubstitutions() {
        Set<Var> rolePlayers = getRolePlayers();
        return getIdPredicates().stream().filter(pred -> rolePlayers.contains(pred.getVarName())).collect(toSet());
    }

    /**
     * @return map of pairs role type - Id predicate describing the role player playing this role (substitution)
     */
    private Multimap<Role, String> getRoleConceptIdMap() {
        if (roleConceptIdMap == null) {
            roleConceptIdMap = ArrayListMultimap.create();

            Map<Var, IdPredicate> varSubMap = getPartialSubstitutions().stream()
                    .collect(Collectors.toMap(Atomic::getVarName, pred -> pred));
            Multimap<Role, Var> roleMap = getRoleVarMap();

            roleMap.entries().forEach(e -> {
                Role role = e.getKey();
                Var var = e.getValue();
                roleConceptIdMap.put(role,
                        varSubMap.containsKey(var) ? varSubMap.get(var).getPredicateValue() : "");
            });
        }
        return roleConceptIdMap;
    }

    private Multimap<Role, OntologyConcept> getRoleTypeMap() {
        Multimap<Role, OntologyConcept> roleTypeMap = ArrayListMultimap.create();
        Multimap<Role, Var> roleMap = getRoleVarMap();
        Map<Var, OntologyConcept> varTypeMap = getParentQuery().getVarOntologyConceptMap();

        roleMap.entries().stream().filter(e -> varTypeMap.containsKey(e.getValue()))
                .sorted(Comparator.comparing(e -> varTypeMap.get(e.getValue()).getLabel()))
                .forEach(e -> roleTypeMap.put(e.getKey(), varTypeMap.get(e.getValue())));
        return roleTypeMap;
    }

    //rule head atom is applicable if it is unifiable
    private boolean isRuleApplicableViaAtom(RelationAtom headAtom) {
        return headAtom.getRelationPlayers().size() >= this.getRelationPlayers().size()
                && headAtom.getRelationPlayerMappings(this).size() == this.getRelationPlayers().size();
    }

    @Override
    public boolean isRuleApplicable(InferenceRule child) {
        Atom ruleAtom = child.getRuleConclusionAtom();
        if (!(ruleAtom.isRelation()))
            return false;

        RelationAtom headAtom = (RelationAtom) ruleAtom;
        RelationAtom atomWithType = this.addType(headAtom.getOntologyConcept()).inferRoleTypes();
        return atomWithType.isRuleApplicableViaAtom(headAtom);
    }

    /**
     * @return true if any of the relation's role types are meta role types
     */
    private boolean hasMetaRoles() {
        Set<Role> parentRoles = getRoleVarMap().keySet();
        for (Role role : parentRoles) {
            if (Schema.MetaSchema.isMetaLabel(role.getLabel()))
                return true;
        }
        return false;
    }

    private Set<Role> getExplicitRoleTypes() {
        Set<Role> roles = new HashSet<>();
        ReasonerQueryImpl parent = (ReasonerQueryImpl) getParentQuery();
        GraknGraph graph = parent.graph();

        Set<VarPatternAdmin> roleVars = getRelationPlayers().stream().map(RelationPlayer::getRole)
                .flatMap(CommonUtil::optionalToStream).collect(Collectors.toSet());
        //try directly
        roleVars.stream().map(VarPatternAdmin::getTypeLabel).flatMap(CommonUtil::optionalToStream)
                .map(graph::<Role>getOntologyConcept).forEach(roles::add);

        //try indirectly
        roleVars.stream().filter(v -> v.getVarName().isUserDefinedName()).map(VarPatternAdmin::getVarName)
                .map(parent::getIdPredicate).filter(Objects::nonNull).map(Predicate::getPredicate)
                .map(graph::<Role>getConcept).forEach(roles::add);
        return roles;
    }

    /**
     * @param type to be added to this relation
     * @return new relation with specified type
     */
    public RelationAtom addType(OntologyConcept type) {
        ConceptId typeId = type.getId();
        Var typeVariable = getPredicateVariable().getValue().isEmpty() ? Graql.var().asUserDefined()
                : getPredicateVariable();

        VarPatternAdmin newPattern = getPattern().asVar().isa(typeVariable).admin();
        IdPredicate newPredicate = new IdPredicate(typeVariable.id(typeId).admin(), getParentQuery());

        return new RelationAtom(newPattern, typeVariable, newPredicate, this.getParentQuery());
    }

    /**
     * @param sub answer
     * @return entity types inferred from answer entity information
     */
    private Set<Type> inferEntityTypes(Answer sub) {
        if (sub.isEmpty())
            return Collections.emptySet();

        Set<Var> subbedVars = Sets.intersection(getRolePlayers(), sub.keySet());
        Set<Var> untypedVars = Sets.difference(subbedVars, getParentQuery().getVarOntologyConceptMap().keySet());
        return untypedVars.stream().map(v -> new Pair<>(v, sub.get(v))).filter(p -> p.getValue().isThing())
                .map(e -> {
                    Concept c = e.getValue();
                    return c.asThing().type();
                }).collect(toSet());
    }

    /**
     * infer relation types that this relation atom can potentially have
     * NB: entity types and role types are treated separately as they behave differently:
     * entity types only play the explicitly defined roles (not the relevant part of the hierarchy of the specified role)
     * @return list of relation types this atom can have ordered by the number of compatible role types
     */
    public List<RelationType> inferPossibleRelationTypes(Answer sub) {
        if (getPredicate() != null)
            return Collections.singletonList(getOntologyConcept().asRelationType());

        //look at available role types
        Multimap<RelationType, Role> compatibleTypesFromRoles = getCompatibleRelationTypesWithRoles(
                getExplicitRoleTypes(), new RoleTypeConverter());

        //look at entity types
        Map<Var, OntologyConcept> varTypeMap = getParentQuery().getVarOntologyConceptMap();

        //explicit types
        Set<OntologyConcept> types = getRolePlayers().stream().filter(varTypeMap::containsKey).map(varTypeMap::get)
                .collect(toSet());

        //types deduced from substitution
        inferEntityTypes(sub).forEach(types::add);

        Multimap<RelationType, Role> compatibleTypesFromTypes = getCompatibleRelationTypesWithRoles(types,
                new OntologyConceptConverterImpl());

        Multimap<RelationType, Role> compatibleTypes;
        //intersect relation types from roles and types
        if (compatibleTypesFromRoles.isEmpty()) {
            compatibleTypes = compatibleTypesFromTypes;
        } else if (!compatibleTypesFromTypes.isEmpty()) {
            compatibleTypes = multimapIntersection(compatibleTypesFromTypes, compatibleTypesFromRoles);
        } else {
            compatibleTypes = compatibleTypesFromRoles;
        }

        return compatibleTypes.asMap().entrySet().stream().sorted(Comparator.comparing(e -> -e.getValue().size()))
                .map(Map.Entry::getKey)
                .filter(t -> Sets.intersection(getSupers(t), compatibleTypes.keySet()).isEmpty())
                .collect(Collectors.toList());
    }

    /**
     * attempt to infer the relation type of this relation
     * @param sub extra instance information to aid entity type inference
     * @return either this if relation type can't be inferred or a fresh relation with inferred relation type
     */
    private RelationAtom inferRelationType(Answer sub) {
        if (getPredicate() != null)
            return this;

        List<RelationType> relationTypes = inferPossibleRelationTypes(sub);
        if (relationTypes.size() == 1) {
            return addType(relationTypes.iterator().next());
        } else {
            return this;
        }
    }

    @Override
    public Atom inferTypes() {
        return this.inferRelationType(new QueryAnswer()).inferRoleTypes();
    }

    @Override
    public Set<Var> getVarNames() {
        Set<Var> vars = super.getVarNames();
        vars.addAll(getRolePlayers());
        //add user specified role type vars
        getRelationPlayers().stream().map(RelationPlayer::getRole).flatMap(CommonUtil::optionalToStream)
                .filter(v -> v.getVarName().isUserDefinedName()).forEach(r -> vars.add(r.getVarName()));
        return vars;
    }

    /**
     * @return set constituting the role player var names
     */
    public Set<Var> getRolePlayers() {
        Set<Var> vars = new HashSet<>();
        getRelationPlayers().forEach(c -> vars.add(c.getRolePlayer().getVarName()));
        return vars;
    }

    private Set<Var> getSpecificRolePlayers() {
        return getRoleVarMap().entries().stream().filter(e -> !Schema.MetaSchema.isMetaLabel(e.getKey().getLabel()))
                .map(Map.Entry::getValue).collect(toSet());
    }

    /**
     * @return set constituting the role player var names that do not have a specified role type
     */
    private Set<Var> getNonSpecificRolePlayers() {
        Set<Var> unmappedVars = getRolePlayers();
        unmappedVars.removeAll(getSpecificRolePlayers());
        return unmappedVars;
    }

    @Override
    public Set<TypeAtom> getSpecificTypeConstraints() {
        Set<Var> mappedVars = getSpecificRolePlayers();
        return getTypeConstraints().stream().filter(t -> mappedVars.contains(t.getVarName()))
                .filter(t -> Objects.nonNull(t.getOntologyConcept())).collect(toSet());
    }

    @Override
    public Set<Unifier> getPermutationUnifiers(Atom headAtom) {
        if (!headAtom.isRelation())
            return Collections.singleton(new UnifierImpl());

        //if this atom is a match all atom, add type from rule head and find unmapped roles
        RelationAtom relAtom = getPredicateVariable().getValue().isEmpty()
                ? this.addType(headAtom.getOntologyConcept())
                : this;
        List<Var> permuteVars = new ArrayList<>(relAtom.getNonSpecificRolePlayers());
        if (permuteVars.isEmpty())
            return Collections.singleton(new UnifierImpl());

        List<List<Var>> varPermutations = getListPermutations(new ArrayList<>(permuteVars)).stream()
                .filter(l -> !l.isEmpty()).collect(Collectors.toList());
        return getUnifiersFromPermutations(permuteVars, varPermutations);
    }

    /**
     * attempt to infer role types of this relation and return a fresh relation with inferred role types
     * @return either this if nothing/no roles can be inferred or fresh relation with inferred role types
     */
    private RelationAtom inferRoleTypes() {
        if (getExplicitRoleTypes().size() == getRelationPlayers().size() || getOntologyConcept() == null)
            return this;

        GraknGraph graph = getParentQuery().graph();
        Role metaRole = graph.admin().getMetaRole();
        RelationType relType = (RelationType) getOntologyConcept();
        Map<Var, OntologyConcept> varOntologyConceptMap = getParentQuery().getVarOntologyConceptMap();

        List<RelationPlayer> allocatedRelationPlayers = new ArrayList<>();

        //explicit role types from castings
        List<Pair<Var, VarPattern>> rolePlayerMappings = new ArrayList<>();
        getRelationPlayers().forEach(c -> {
            Var varName = c.getRolePlayer().getVarName();
            VarPatternAdmin role = c.getRole().orElse(null);
            if (role != null) {
                rolePlayerMappings.add(new Pair<>(varName, role));
                //try directly
                Label typeLabel = role.getTypeLabel().orElse(null);
                Role roleType = typeLabel != null ? graph.getRole(typeLabel.getValue()) : null;

                //try indirectly
                if (roleType == null && role.getVarName().isUserDefinedName()) {
                    IdPredicate rolePredicate = ((ReasonerQueryImpl) getParentQuery())
                            .getIdPredicate(role.getVarName());
                    if (rolePredicate != null)
                        roleType = graph.getConcept(rolePredicate.getPredicate());
                }
                allocatedRelationPlayers.add(c);
            }
        });

        //remaining roles
        //role types can repeat so no matter what has been allocated still the full spectrum of possibilities is present
        //TODO make restrictions based on cardinality constraints
        Set<Role> possibleRoles = relType.relates().collect(toSet());

        //possible role types for each casting based on its type
        Map<RelationPlayer, Set<Role>> mappings = new HashMap<>();
        getRelationPlayers().stream().filter(rp -> !allocatedRelationPlayers.contains(rp)).forEach(casting -> {
            Var varName = casting.getRolePlayer().getVarName();
            OntologyConcept ontologyConcept = varOntologyConceptMap.get(varName);
            if (ontologyConcept != null && !Schema.MetaSchema.isMetaLabel(ontologyConcept.getLabel())
                    && ontologyConcept.isType()) {
                mappings.put(casting,
                        ReasonerUtils.getCompatibleRoleTypes(ontologyConcept.asType(), possibleRoles.stream()));
            } else {
                mappings.put(casting, ReasonerUtils.getOntologyConcepts(possibleRoles));
            }
        });

        //resolve ambiguities until no unambiguous mapping exist
        while (mappings.values().stream().filter(s -> s.size() == 1).count() != 0) {
            Map.Entry<RelationPlayer, Set<Role>> entry = mappings.entrySet().stream()
                    .filter(e -> e.getValue().size() == 1).findFirst().orElse(null);

            RelationPlayer casting = entry.getKey();
            Var varName = casting.getRolePlayer().getVarName();
            Role role = entry.getValue().iterator().next();
            VarPatternAdmin roleVar = Graql.var().label(role.getLabel()).admin();

            //TODO remove from all mappings if it follows from cardinality constraints
            mappings.get(casting).remove(role);

            rolePlayerMappings.add(new Pair<>(varName, roleVar));
            allocatedRelationPlayers.add(casting);
        }

        //fill in unallocated roles with metarole
        VarPatternAdmin metaRoleVar = Graql.var().label(metaRole.getLabel()).admin();
        getRelationPlayers().stream().filter(rp -> !allocatedRelationPlayers.contains(rp)).forEach(casting -> {
            Var varName = casting.getRolePlayer().getVarName();
            rolePlayerMappings.add(new Pair<>(varName, metaRoleVar));
        });

        PatternAdmin newPattern = constructRelationVarPattern(getVarName(), getPredicateVariable(),
                rolePlayerMappings);
        return new RelationAtom(newPattern.asVar(), getPredicateVariable(), getPredicate(), getParentQuery());
    }

    /**
     * @return map containing roleType - (rolePlayer var - rolePlayer type) pairs
     */
    private Multimap<Role, Var> computeRoleVarMap() {
        Multimap<Role, Var> roleMap = ArrayListMultimap.create();
        if (getParentQuery() == null || getOntologyConcept() == null) {
            return roleMap;
        }

        GraknGraph graph = getParentQuery().graph();
        getRelationPlayers().forEach(c -> {
            Var varName = c.getRolePlayer().getVarName();
            VarPatternAdmin role = c.getRole().orElse(null);
            if (role != null) {
                //try directly
                Label typeLabel = role.getTypeLabel().orElse(null);
                Role roleType = typeLabel != null ? graph.getRole(typeLabel.getValue()) : null;
                //try indirectly
                if (roleType == null && role.getVarName().isUserDefinedName()) {
                    IdPredicate rolePredicate = ((ReasonerQueryImpl) getParentQuery())
                            .getIdPredicate(role.getVarName());
                    if (rolePredicate != null)
                        roleType = graph.getConcept(rolePredicate.getPredicate());
                }
                if (roleType != null)
                    roleMap.put(roleType, varName);
            }
        });
        return roleMap;
    }

    public Multimap<Role, Var> getRoleVarMap() {
        if (roleVarMap == null) {
            roleVarMap = computeRoleVarMap();
        }
        return roleVarMap;
    }

    private Multimap<Role, RelationPlayer> getRoleRelationPlayerMap() {
        Multimap<Role, RelationPlayer> roleRelationPlayerMap = ArrayListMultimap.create();
        Multimap<Role, Var> roleVarTypeMap = getRoleVarMap();
        List<RelationPlayer> relationPlayers = getRelationPlayers();
        roleVarTypeMap.asMap().entrySet().forEach(e -> {
            Role role = e.getKey();
            Label roleLabel = role.getLabel();
            relationPlayers.stream().filter(rp -> rp.getRole().isPresent()).forEach(rp -> {
                VarPatternAdmin roleTypeVar = rp.getRole().orElse(null);
                Label rl = roleTypeVar != null ? roleTypeVar.getTypeLabel().orElse(null) : null;
                if (roleLabel != null && roleLabel.equals(rl)) {
                    roleRelationPlayerMap.put(role, rp);
                }
            });
        });
        return roleRelationPlayerMap;
    }

    private List<Pair<RelationPlayer, RelationPlayer>> getRelationPlayerMappings(RelationAtom parentAtom) {
        List<Pair<RelationPlayer, RelationPlayer>> rolePlayerMappings = new ArrayList<>();

        //establish compatible castings for each parent casting
        List<Pair<RelationPlayer, List<RelationPlayer>>> compatibleMappings = new ArrayList<>();
        parentAtom.getRoleRelationPlayerMap();
        Multimap<Role, RelationPlayer> childRoleRPMap = getRoleRelationPlayerMap();
        Map<Var, OntologyConcept> parentVarOntologyConceptMap = parentAtom.getParentQuery()
                .getVarOntologyConceptMap();
        Map<Var, OntologyConcept> childVarOntologyConceptMap = this.getParentQuery().getVarOntologyConceptMap();

        Set<Role> childRoles = new HashSet<>(childRoleRPMap.keySet());

        parentAtom.getRelationPlayers().stream().filter(prp -> prp.getRole().isPresent()).forEach(prp -> {
            VarPatternAdmin parentRoleTypeVar = prp.getRole().orElse(null);
            Label parentRoleLabel = parentRoleTypeVar.getTypeLabel().orElse(null);

            //TODO take into account indirect roles
            Role parentRole = parentRoleLabel != null ? graph().getOntologyConcept(parentRoleLabel) : null;

            if (parentRole != null) {
                boolean isMetaRole = Schema.MetaSchema.isMetaLabel(parentRole.getLabel());
                Var parentRolePlayer = prp.getRolePlayer().getVarName();
                OntologyConcept parent = parentVarOntologyConceptMap.get(parentRolePlayer);

                Set<Role> compatibleChildRoles = isMetaRole ? childRoles
                        : Sets.intersection(parentRole.subs().collect(toSet()), childRoles);

                if (parent != null && parent.isType()) {
                    boolean isMetaType = Schema.MetaSchema.isMetaLabel(parent.getLabel());
                    Set<Role> typeRoles = isMetaType ? childRoles : parent.asType().plays().collect(toSet());

                    //incompatible type
                    if (Sets.intersection(getOntologyConcept().asRelationType().relates().collect(toSet()),
                            typeRoles).isEmpty())
                        compatibleChildRoles = new HashSet<>();
                    else {
                        compatibleChildRoles = compatibleChildRoles.stream().filter(
                                rc -> Schema.MetaSchema.isMetaLabel(rc.getLabel()) || typeRoles.contains(rc))
                                .collect(toSet());
                    }
                }

                List<RelationPlayer> compatibleRelationPlayers = new ArrayList<>();
                compatibleChildRoles.stream().filter(childRoleRPMap::containsKey).forEach(r -> {
                    Collection<RelationPlayer> childRPs = parent != null
                            ? childRoleRPMap.get(r).stream().filter(rp -> {
                                Var childRolePlayer = rp.getRolePlayer().getVarName();
                                OntologyConcept childType = childVarOntologyConceptMap.get(childRolePlayer);
                                return childType == null || !checkDisjoint(parent, childType);
                            }).collect(Collectors.toList())
                            : childRoleRPMap.get(r);

                    childRPs.forEach(compatibleRelationPlayers::add);
                });
                compatibleMappings.add(new Pair<>(prp, compatibleRelationPlayers));
            }
        });

        //self-consistent procedure until no non-empty mappings present
        while (compatibleMappings.stream().map(Pair::getValue).filter(s -> !s.isEmpty()).count() > 0) {
            //find optimal parent-child RP pair
            Pair<RelationPlayer, RelationPlayer> rpPair = compatibleMappings.stream()
                    .filter(e -> e.getValue().size() == 1)
                    .map(e -> new Pair<>(e.getKey(), e.getValue().iterator().next())).findFirst()
                    .orElse(compatibleMappings.stream()
                            .flatMap(e -> e.getValue().stream().map(childRP -> new Pair<>(e.getKey(), childRP)))
                            //prioritise mappings with equivalent types and unambiguous mappings
                            .sorted(Comparator.comparing(e -> {
                                OntologyConcept parentType = parentVarOntologyConceptMap
                                        .get(e.getKey().getRolePlayer().getVarName());
                                OntologyConcept childType = childVarOntologyConceptMap
                                        .get(e.getValue().getRolePlayer().getVarName());
                                return !(parentType != null && childType != null && parentType.equals(childType));
                            }))
                            //prioritise mappings with sam var substitution (idpredicates)
                            .sorted(Comparator.comparing(e -> {
                                IdPredicate parentId = parentAtom.getIdPredicates().stream()
                                        .filter(p -> p.getVarName().equals(e.getKey().getRolePlayer().getVarName()))
                                        .findFirst().orElse(null);
                                IdPredicate childId = getIdPredicates().stream().filter(
                                        p -> p.getVarName().equals(e.getValue().getRolePlayer().getVarName()))
                                        .findFirst().orElse(null);
                                return !(parentId != null && childId != null
                                        && parentId.getPredicate().equals(childId.getPredicate()));
                            })).findFirst().orElse(null));

            RelationPlayer parentCasting = rpPair.getKey();
            RelationPlayer childCasting = rpPair.getValue();
            rolePlayerMappings.add(new Pair<>(childCasting, parentCasting));

            //remove corresponding entries
            Pair<RelationPlayer, List<RelationPlayer>> entryToRemove = compatibleMappings.stream()
                    .filter(e -> e.getKey() == parentCasting).findFirst().orElse(null);
            compatibleMappings.remove(entryToRemove);
            compatibleMappings.stream().filter(e -> e.getValue().contains(childCasting))
                    .forEach(e -> e.getValue().remove(childCasting));
        }
        return rolePlayerMappings;
    }

    @Override
    public Unifier getUnifier(Atom pAtom) {
        if (this.equals(pAtom))
            return new UnifierImpl();

        Unifier unifier = super.getUnifier(pAtom);
        if (pAtom.isRelation()) {
            RelationAtom parentAtom = (RelationAtom) pAtom;

            getRelationPlayerMappings(parentAtom)
                    .forEach(rpm -> unifier.addMapping(rpm.getKey().getRolePlayer().getVarName(),
                            rpm.getValue().getRolePlayer().getVarName()));
        }
        return unifier.removeTrivialMappings();
    }

    @Override
    public Atom rewriteToUserDefined() {
        VarPattern newVar = Graql.var().asUserDefined();
        VarPattern relVar = getPattern().asVar().getProperty(IsaProperty.class)
                .map(prop -> newVar.isa(prop.getType())).orElse(newVar);

        for (RelationPlayer c : getRelationPlayers()) {
            VarPatternAdmin roleType = c.getRole().orElse(null);
            if (roleType != null) {
                relVar = relVar.rel(roleType, c.getRolePlayer());
            } else {
                relVar = relVar.rel(c.getRolePlayer());
            }
        }
        return new RelationAtom(relVar.admin(), getPredicateVariable(), getPredicate(), getParentQuery());
    }
}