package uk.ac.manchester.cs.jfact.kernel; /* This file is part of the JFact DL reasoner Copyright 2011-2013 by Ignazio Palmisano, Dmitry Tsarkov, University of Manchester This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA*/ import static uk.ac.manchester.cs.jfact.kernel.InAx.*; import static uk.ac.manchester.cs.jfact.kernel.Token.*; import java.io.Serializable; import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import uk.ac.manchester.cs.jfact.helpers.DLTree; import uk.ac.manchester.cs.jfact.helpers.DLTreeFactory; import uk.ac.manchester.cs.jfact.helpers.LogAdapter; import uk.ac.manchester.cs.jfact.kernel.options.JFactReasonerConfiguration; import uk.ac.manchester.cs.jfact.split.TOntologyAtom; import conformance.Original; import conformance.PortedFrom; /** inner axiom class */ @PortedFrom(file = "tAxiom.h", name = "TAxiom") public class Axiom implements Serializable { private static final long serialVersionUID = 11000L; private static LogAdapter absorptionLog; private Axiom origin; /** * @param l * absorption log */ public static void setLogAdapter(LogAdapter l) { absorptionLog = l; } /** * @param parent * parent axiom */ public Axiom(Axiom parent) { origin = parent; } // NS for different DLTree matchers for trees in axiom /** * absorb into negation of a concept; * * @param KB * KB * @return true if absorption is performed */ @PortedFrom(file = "tAxiom.h", name = "absorbIntoNegConcept") public boolean absorbIntoNegConcept(TBox KB) { List<DLTree> Cons = new ArrayList<DLTree>(); Concept Concept; DLTree bestConcept = null; // finds all primitive negated concept names without description for (DLTree p : disjuncts) { if (p.token() == NOT && p.getChild().isName() && (Concept = getConcept(p.getChild())).isPrimitive() && !Concept.isSingleton() && Concept.getDescription() == null) { SAbsNAttempt(); Cons.add(p); } } // if no concept names -- return; if (Cons.isEmpty()) { return false; } SAbsNApply(); // FIXME!! as for now: just take the 1st concept name if (bestConcept == null) { bestConcept = Cons.get(0); } // normal concept absorption Concept = InAx.getConcept(bestConcept.getChild()); JFactReasonerConfiguration options = KB.getOptions(); if (options.isAbsorptionLoggingActive()) { absorptionLog.print(" N-Absorb GCI to concept ", Concept.getName()); if (Cons.size() > 1) { absorptionLog.print(" (other options are"); for (int j = 1; j < Cons.size(); ++j) { absorptionLog.print(" ", InAx.getConcept(Cons.get(j).getChild()).getName()); } absorptionLog.print(")"); } } // replace ~C [= D with C=~notC, notC [= D: // make notC [= D Concept nC = KB.getAuxConcept(createAnAxiom(bestConcept)); // define C = ~notC; C had an empty desc, so it's safe not to delete it KB.makeNonPrimitive(Concept, DLTreeFactory.createSNFNot(KB.getTree(nC))); return true; } /** GCI is presented in the form (or Disjuncts); */ @PortedFrom(file = "tAxiom.h", name = "Disjuncts") private final Set<DLTree> disjuncts = new LinkedHashSet<DLTree>(); @Original private TOntologyAtom atom; /** * create a copy of a given GCI; ignore SKIP entry * * @param skip * skip * @return copy */ @PortedFrom(file = "tAxiom.h", name = "copy") private Axiom copy(DLTree skip) { Axiom ret = new Axiom(this); for (DLTree i : disjuncts) { if (!i.equals(skip)) { ret.disjuncts.add(i.copy()); } } return ret; } /** @return true iff an axiom is the same as one of its ancestors */ @PortedFrom(file = "tAxiom.h", name = "isCyclic") boolean isCyclic() { Axiom p = origin; while (p != null) { if (p.equals(this)) { absorptionLog.print(" same as ancestor"); return true; } p = p.origin; } return false; } /** * simplify (OR C ...) for a non-primitive C in a given position * * @param pos * pos * @return simplified axiom */ @PortedFrom(file = "tAxiom.h", name = "simplifyPosNP") private Axiom simplifyPosNP(DLTree pos) { SAbsRepCN(); Axiom ret = copy(pos); ret.add(DLTreeFactory.createSNFNot(InAx.getConcept(pos.getChild()) .getDescription().copy())); absorptionLog.print(" simplify CN expression for ", pos.getChild()); return ret; } /** * simplify (OR ~C ...) for a non-primitive C in a given position * * @param pos * pos * @return simplified axiom */ @PortedFrom(file = "tAxiom.h", name = "simplifyNegNP") private Axiom simplifyNegNP(DLTree pos) { SAbsRepCN(); Axiom ret = copy(pos); ret.add(InAx.getConcept(pos).getDescription().copy()); absorptionLog.print(" simplify ~CN expression for ", pos); return ret; } /** * split (OR (AND...) ...) in a given position * * @param acc * acc * @param pos * pos * @param pAnd * pAnd * @return split axioms */ @PortedFrom(file = "tAxiom.h", name = "split") private List<Axiom> split(List<Axiom> acc, DLTree pos, DLTree pAnd) { if (pAnd.isAND()) { // split the AND List<DLTree> children = new ArrayList<DLTree>(pAnd.getChildren()); acc = this.split(acc, pos, children.remove(0)); if (!children.isEmpty()) { acc = this .split(acc, pos, DLTreeFactory.createSNFAnd(children)); } } else { Axiom ret = copy(pos); ret.add(DLTreeFactory.createSNFNot(pAnd.copy())); acc.add(ret); } return acc; } /** * split an axiom; * * @return new axiom and/or NULL */ @PortedFrom(file = "tAxiom.h", name = "split") public List<Axiom> split() { List<Axiom> acc = new ArrayList<Axiom>(); for (DLTree p : disjuncts) { if (InAx.isAnd(p)) { SAbsSplit(); absorptionLog.print(" split AND expression ", p.getChild()); acc = this.split(acc, p, p.getChildren().iterator().next()); /** * no need to split more than once: every extra splits would be * together with unsplitted parts like: (A or B) and (C or D) * would be transform into A and (C or D), B and (C or D), (A or * B) and C, (A or B) and D so just return here */ return acc; } } return acc; } /** * add DLTree to an axiom * * @param p * tree to add */ @PortedFrom(file = "tAxiom.h", name = "add") public void add(DLTree p) { if (InAx.isBot(p)) { // nothing to do return; } // flatten the disjunctions on the fly if (InAx.isOr(p)) { for (DLTree d : p.getChildren()) { add(d); } return; } disjuncts.add(p); } /** dump GCI for debug purposes */ @Override public String toString() { StringBuilder b = new StringBuilder(" (neg-and"); for (DLTree p : disjuncts) { b.append(p); } b.append(')'); return b.toString(); } /** * replace a defined concept with its description * * @param t * tbox * @return rewritten axiom */ @PortedFrom(file = "tAxiom.h", name = "simplifyCN") public Axiom simplifyCN(TBox t) { for (DLTree p : disjuncts) { if (InAx.isPosNP(p, t)) { return simplifyPosNP(p); } else if (InAx.isNegNP(p, t)) { return simplifyNegNP(p); } } return null; } /** * replace a universal restriction with a fresh concept * * @param KB * tbox * @return rewritten axiom */ @PortedFrom(file = "tAxiom.h", name = "simplifyForall") public Axiom simplifyForall(TBox KB) { for (DLTree i : disjuncts) { if (InAx.isAbsForall(i)) { return this.simplifyForall(i, KB); } } return null; } /** * replace a simple universal restriction with a fresh concept * * @param KB * tbox * @return simplified axiom */ public Axiom simplifySForall(TBox KB) { for (DLTree i : disjuncts) { if (InAx.isSimpleForall(i)) { return simplifyForall(i, KB); } } return null; } @PortedFrom(file = "tAxiom.h", name = "simplifyForall") private Axiom simplifyForall(DLTree pos, TBox KB) { SAbsRepForall(); // (all R ~C) DLTree pAll = pos.getChild(); absorptionLog.print(" simplify ALL expression", pAll); Axiom ret = copy(pos); ret.add(KB.getTree(KB.replaceForall(pAll.copy()))); return ret; } /** * create a concept expression corresponding to a given GCI; ignore SKIP * entry * * @param replaced * ignored entity * @return rewritten DLTree */ @PortedFrom(file = "tAxiom.h", name = "createAnAxiom") public DLTree createAnAxiom(DLTree replaced) { // XXX check if this is correct if (disjuncts.isEmpty()) { return DLTreeFactory.createBottom(); } // assert !disjuncts.isEmpty(); List<DLTree> leaves = new ArrayList<DLTree>(); for (DLTree d : disjuncts) { if (!d.equals(replaced)) { leaves.add(d.copy()); } } DLTree result = DLTreeFactory.createSNFAnd(leaves); return DLTreeFactory.createSNFNot(result); } /** * absorb into BOTTOM; * * @return true if absorption is performed */ @PortedFrom(file = "tAxiom.h", name = "absorbIntoBottom") public boolean absorbIntoBottom() { List<DLTree> Pos = new ArrayList<DLTree>(), Neg = new ArrayList<DLTree>(); for (DLTree p : disjuncts) { switch (p.token()) { case BOTTOM: // axiom in the form T [= T or ...; nothing to do SAbsBApply(); absorptionLog.print(" Absorb into BOTTOM"); return true; case TOP: // skip it here break; case NOT: // something negated: put it into NEG Neg.add(p.getChild()); break; default: // something positive: save in POS Pos.add(p); break; } } // now check whether there is a concept in both POS and NEG for (DLTree q : Neg) { for (DLTree s : Pos) { if (q.equals(s)) { SAbsBApply(); absorptionLog.print(" Absorb into BOTTOM due to (not", q, ") and", s); return true; } } } return false; } /** * @param KB * KB * @return false if there are no absorptions */ @PortedFrom(file = "tAxiom.h", name = "absorbIntoConcept") public boolean absorbIntoConcept(TBox KB) { List<DLTree> Cons = new ArrayList<DLTree>(); DLTree bestConcept = null; for (DLTree p : disjuncts) { if (InAx.isNegPC(p)) { SAbsCAttempt(); Cons.add(p); if (getConcept(p).isSystem()) { bestConcept = p; } } } if (Cons.isEmpty()) { return false; } SAbsCApply(); if (bestConcept == null) { bestConcept = Cons.get(0); } // normal concept absorption Concept Concept = InAx.getConcept(bestConcept); if (KB.getOptions().isAbsorptionLoggingActive()) { absorptionLog.print(" C-Absorb GCI to concept ", Concept.getName()); if (Cons.size() > 1) { absorptionLog.print(" (other options are"); for (int j = 1; j < Cons.size(); ++j) { absorptionLog.print(" ", InAx.getConcept(Cons.get(j)) .getName()); } absorptionLog.print(")"); } } Concept.addDesc(createAnAxiom(bestConcept)); Concept.removeSelfFromDescription(); KB.clearRelevanceInfo(); KB.checkToldCycle(Concept); KB.clearRelevanceInfo(); return true; } /** * @param KB * KB * @return false if there are no absorptions */ @PortedFrom(file = "tAxiom.h", name = "absorbIntoDomain") public boolean absorbIntoDomain(TBox KB) { List<DLTree> Cons = new ArrayList<DLTree>(); DLTree bestSome = null; for (DLTree p : disjuncts) { if (p.token() == NOT && (p.getChild().token() == FORALL || p.getChild().token() == LE)) { SAbsRAttempt(); Cons.add(p); if (p.getChild().getRight().isBOTTOM()) { bestSome = p; break; } } } if (Cons.isEmpty()) { return false; } SAbsRApply(); Role role; if (bestSome != null) { role = Role.resolveRole(bestSome.getChild().getLeft()); } else { role = Role.resolveRole(Cons.get(0).getChild().getLeft()); } if (KB.getOptions().isAbsorptionLoggingActive()) { absorptionLog.print(" R-Absorb GCI to the domain of role ", role.getName()); if (Cons.size() > 1) { absorptionLog.print(" (other options are"); for (int j = 1; j < Cons.size(); ++j) { absorptionLog.print(" ", Role.resolveRole(Cons.get(j).getChild().getLeft()) .getName()); } absorptionLog.print(")"); } } role.setDomain(createAnAxiom(bestSome)); return true; } /** * absorb into TOP; * * @param KB * KB * @return true if any absorption is performed */ @PortedFrom(file = "tAxiom.h", name = "absorbIntoTop") public boolean absorbIntoTop(TBox KB) { Concept C = null; // check whether the axiom is Top [= C for (DLTree p : disjuncts) { if (InAx.isBot(p)) { continue; } else if (InAx.isPosCN(p)) { // C found if (C != null) { return false; } C = InAx.getConcept(p.getChild()); if (C.isSingleton()) { return false; } } else { return false; } } if (C == null) { return false; } SAbsTApply(); // make an absorption DLTree desc = KB.makeNonPrimitive(C, DLTreeFactory.createTop()); if (KB.getOptions().isAbsorptionLoggingActive()) { absorptionLog .print("TAxiom.absorbIntoTop() T-Absorb GCI to axiom\n"); if (desc != null) { absorptionLog.print("s *TOP* [=", desc, " and\n"); } absorptionLog.print(" ", C.getName(), " = *TOP*\n"); } if (desc != null) { KB.addSubsumeAxiom(DLTreeFactory.createTop(), desc); } return true; } @Override public boolean equals(Object arg0) { if (arg0 == null) { return false; } if (this == arg0) { return true; } if (arg0 instanceof Axiom) { Axiom ax = (Axiom) arg0; return disjuncts.equals(ax.disjuncts); } return false; } @Override public int hashCode() { return disjuncts.hashCode(); } /** @return atom for this axiom */ @Original public TOntologyAtom getAtom() { return atom; } /** * @param atom * atom */ @Original public void setAtom(TOntologyAtom atom) { this.atom = atom; } }