Package au.csiro.snorocket.core

Source Code of au.csiro.snorocket.core.SnorocketReasoner$Builder

/**
* Copyright (c) 2009 International Health Terminology Standards Development
* Organisation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Copyright CSIRO Australian e-Health Research Centre (http://aehrc.com).
* All rights reserved. Use is subject to license terms and conditions.
*/

package au.csiro.snorocket.core;

import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.log4j.Logger;

import au.csiro.ontology.Node;
import au.csiro.ontology.Ontology;
import au.csiro.ontology.classification.IReasoner;
import au.csiro.ontology.model.Axiom;
import au.csiro.ontology.model.Concept;
import au.csiro.ontology.model.ConceptInclusion;
import au.csiro.ontology.model.Conjunction;
import au.csiro.ontology.model.Datatype;
import au.csiro.ontology.model.Existential;
import au.csiro.ontology.model.Feature;
import au.csiro.ontology.model.Literal;
import au.csiro.ontology.model.NamedConcept;
import au.csiro.ontology.model.NamedFeature;
import au.csiro.ontology.model.NamedRole;
import au.csiro.ontology.model.Operator;
import au.csiro.snorocket.core.concurrent.CR;
import au.csiro.snorocket.core.concurrent.Context;
import au.csiro.snorocket.core.model.AbstractConcept;
import au.csiro.snorocket.core.model.AbstractLiteral;
import au.csiro.snorocket.core.model.BigIntegerLiteral;
import au.csiro.snorocket.core.model.DateLiteral;
import au.csiro.snorocket.core.model.DecimalLiteral;
import au.csiro.snorocket.core.model.FloatLiteral;
import au.csiro.snorocket.core.model.IntegerLiteral;
import au.csiro.snorocket.core.model.StringLiteral;
import au.csiro.snorocket.core.util.IConceptMap;
import au.csiro.snorocket.core.util.IConceptSet;
import au.csiro.snorocket.core.util.IntIterator;
import au.csiro.snorocket.core.util.RoleSet;

/**
* This class represents an instance of the reasoner. It uses the internal
* ontology model. If you need to use an OWL model refer to the
* {@link SnorocketOWLReasoner} class.
*
* @author Alejandro Metke
*
*/
final public class SnorocketReasoner implements IReasoner, Serializable {

    /**
     * Serialisation version.
     */
    private static final long serialVersionUID = 1L;

    private final static Logger log = Logger.getLogger(SnorocketReasoner.class);

    public static final int BUFFER_SIZE = 10;

    private NormalisedOntology no = null;
    private IFactory factory = null;
    private boolean isClassified = false;

    /**
     * Loads a saved instance of a {@link SnorocketReasoner} from an input
     * stream.
     *
     * @param in
     * @return
     */
    public static SnorocketReasoner load(InputStream in) {
        SnorocketReasoner res;
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(in);
            res = (SnorocketReasoner)ois.readObject();
        } catch(Exception e) {
            log.error("Problem loading reasoner." + e);
            throw new RuntimeException(e);
        } finally {
            if(ois != null) {
                try { ois.close(); } catch(Exception e) {}
            }
        }

        Context.init(res.no);
        res.no.buildTaxonomy();
        return res;
    }

    /**
     * Creates an instance of Snorocket using the given base ontology.
     *
     * @param ontology The base ontology to classify.
     */
    public SnorocketReasoner() {
        factory = new CoreFactory();
        no = new NormalisedOntology(factory);
    }

    @Override
    public void prune() {
        // TODO: implement
        throw new UnsupportedOperationException();
    }

    @Override
    public Ontology getClassifiedOntology() {
        // Check ontology is classified
        if(!isClassified) classify();

        log.info("Building taxonomy");
        no.buildTaxonomy();
        Map<String, Node> taxonomy = no.getTaxonomy();
        Set<Node> affectedNodes = no.getAffectedNodes();

        return new Ontology(null, null, null, taxonomy, affectedNodes);
    }

    @Override
    public Ontology getClassifiedOntology(Ontology ont) {
        // Check ontology is classified
        if(!isClassified) classify();

        log.info("Building taxonomy");
        no.buildTaxonomy();
        Map<String, Node> nodeMap = no.getTaxonomy();
        Set<Node> affectedNodes = no.getAffectedNodes();

        ont.setNodeMap(nodeMap);
        ont.setAffectedNodes(affectedNodes);

        return ont;
    }
   
    /**
     * The {@link CoreFactory} can currently hold very different types of objects. These include:
     *
     * <ul>
     *   <li>NamedConcept: for TOP and BOTTOM</li>
     *   <li>Strings: for named concepts</li>
     *   <li>au.csiro.snorocket.core.model.Conjunction</li>
     *   <li>au.csiro.snorocket.core.model.Datatype</li>
     *   <li>au.csiro.snorocket.core.model.Existential</li>
     * </ul>
     *
     * @param obj
     * @return
     */
    protected Concept transformToModel(Object obj) {
        if(obj instanceof NamedConcept) {
            return (NamedConcept) obj;
        } else if(obj instanceof String) {
            return new NamedConcept((String) obj);
        } else if(obj instanceof au.csiro.snorocket.core.model.Conjunction) {
            au.csiro.snorocket.core.model.Conjunction conj = (au.csiro.snorocket.core.model.Conjunction) obj;
            List<Concept> conjs = new ArrayList<Concept>();
            for(AbstractConcept ac : conj.getConcepts()) {
                conjs.add(transformToModel(ac));
            }
            return new Conjunction(conjs.toArray(new Concept[conjs.size()]));
        } else if(obj instanceof au.csiro.snorocket.core.model.Existential) {
            au.csiro.snorocket.core.model.Existential ex = (au.csiro.snorocket.core.model.Existential) obj;
            String roleId = (String) factory.lookupRoleId(ex.getRole());
            Concept con = transformToModel(ex.getConcept());
            return new Existential(new NamedRole(roleId), con);
        } else if(obj instanceof au.csiro.snorocket.core.model.Datatype) {
            au.csiro.snorocket.core.model.Datatype dt = (au.csiro.snorocket.core.model.Datatype) obj;
            String featureId = factory.lookupFeatureId(dt.getFeature());
            Literal l = transformLiteralToModel(dt.getLiteral());
            return new Datatype(new NamedFeature(featureId), dt.getOperator(), l);
        } else if(obj instanceof au.csiro.snorocket.core.model.Concept) {
            au.csiro.snorocket.core.model.Concept c = (au.csiro.snorocket.core.model.Concept) obj;
            Object id = factory.lookupConceptId(c.hashCode());
            if(id instanceof String) {
                return new NamedConcept((String) id);
            } else if(id instanceof NamedConcept) {
                // If TOP or BOTTOM
                return (NamedConcept) id;
            } else {
                return transformToModel(id);
            }
        } else {
            throw new RuntimeException("Unexpected abstract concept " + obj.getClass());
        }
    }
   
    /*
    protected String getName(Object obj) {
        if(obj instanceof NamedConcept) {
            return ((NamedConcept) obj).getId();
        } else if(obj instanceof String) {
            return (String) obj;
        } else if(obj instanceof au.csiro.snorocket.core.model.Conjunction) {
            au.csiro.snorocket.core.model.Conjunction conj = (au.csiro.snorocket.core.model.Conjunction) obj;
            StringBuilder sb = new StringBuilder();
            AbstractConcept[] cs = conj.getConcepts();
            sb.append("(");
            sb.append(getName(cs[0]));
            for(int i = 1; i < cs.length; i++) {
                sb.append(" + ");
                sb.append(getName(cs[i]));
            }
            sb.append(")");
            return sb.toString();
        } else if(obj instanceof au.csiro.snorocket.core.model.Existential) {
            au.csiro.snorocket.core.model.Existential ex = (au.csiro.snorocket.core.model.Existential) obj;
            String roleId = (String) factory.lookupRoleId(ex.getRole());
            String conceptId = getName(ex.getConcept());
            return "(E" + roleId + "." + conceptId+")";
        } else if(obj instanceof au.csiro.snorocket.core.model.Datatype) {
            // TODO: implement
            return "MISSING";
        } else if(obj instanceof au.csiro.snorocket.core.model.Concept) {
            au.csiro.snorocket.core.model.Concept c = (au.csiro.snorocket.core.model.Concept) obj;
            return getName(factory.lookupConceptId(c.hashCode()));
        } else {
            throw new RuntimeException("Unknown type: "+obj.getClass());
        }
    }*/
   
    /**
     * Transforms literal from the internal representation to the canonical representation.
     *
     * @param al
     * @return
     */
    protected Literal transformLiteralToModel(AbstractLiteral al) {
        if(al instanceof BigIntegerLiteral) {
            return new au.csiro.ontology.model.BigIntegerLiteral(((BigIntegerLiteral) al).getValue());
        } else if(al instanceof DateLiteral) {
            return new au.csiro.ontology.model.DateLiteral(((DateLiteral) al).getValue());
        } else if(al instanceof DecimalLiteral) {
            return new au.csiro.ontology.model.DecimalLiteral(((DecimalLiteral) al).getValue());
        } else if(al instanceof FloatLiteral) {
            return new au.csiro.ontology.model.FloatLiteral(((FloatLiteral) al).getValue());
        } else if(al instanceof IntegerLiteral) {
            return new au.csiro.ontology.model.IntegerLiteral(((IntegerLiteral) al).getValue());
        } else if(al instanceof StringLiteral) {
            return new au.csiro.ontology.model.StringLiteral(((StringLiteral) al).getValue());
        } else {
            throw new RuntimeException("Unexpected abstract literal "+al);
        }
    }
   
    /**
     * Ideally we'd return some kind of normal form axioms here.  However, in
     * the presence of GCIs this is not well defined (as far as I know -
     * Michael).
     * <p>
     * Instead, we will return stated form axioms for Sufficient conditions (
     * i.e. for INamedConcept on the RHS), and SNOMED CT DNF-based axioms for
     * Necessary conditions. The former is just a filter over the stated axioms,
     * the latter requires looking at the Taxonomy and inferred relationships.
     * <p>
     * Note that there will be <i>virtual</i> INamedConcepts that need to be
     * skipped/expanded and redundant IExistentials that need to be filtered.
     *
     * @return
     */
    public Collection<Axiom> getInferredAxioms() {
        final Collection<Axiom> inferred = new HashSet<Axiom>();

        if(!isClassified) classify();

        if (!no.isTaxonomyComputed()) {
            log.info("Building taxonomy");
            no.buildTaxonomy();
        }

        final Map<String, Node> taxonomy = no.getTaxonomy();
        final IConceptMap<Context> contextIndex = no.getContextIndex();
        final IntIterator itr = contextIndex.keyIterator();
        while (itr.hasNext()) {
            final int key = itr.next();
            final String id = factory.lookupConceptId(key).toString();

            if (factory.isVirtualConcept(key) || NamedConcept.BOTTOM == id) {
                continue;
            }
           
            Concept rhs = getNecessary(contextIndex, taxonomy, key);

            final Concept lhs = new NamedConcept(id);
            if (!lhs.equals(rhs) && !rhs.equals(NamedConcept.TOP_CONCEPT)) { // skip trivial axioms
                inferred.add(new ConceptInclusion(lhs, rhs));
            }
        }

        return inferred;
    }

    protected Concept getNecessary(IConceptMap<Context> contextIndex, Map<String, Node> taxonomy, int key) {
        final Object id = factory.lookupConceptId(key);
        final List<Concept> result = new ArrayList<Concept>();

        final Node node = taxonomy.get(id);
        if (node != null) {
            for (final Node parent: node.getParents()) {
                final String parentId = parent.getEquivalentConcepts().iterator().next();
                if (!NamedConcept.TOP.equals(parentId)) {      // Top is redundant
                    result.add(new NamedConcept(parentId));
                }
            }
            // Look for Datatype concepts
            final IntIterator ancestorItr = contextIndex.get(key).getS().iterator();
            while (ancestorItr.hasNext()) {
                int anc = ancestorItr.next();
                if (factory.isVirtualConcept(anc)) {
                    Object c = factory.lookupConceptId(anc);
                    if (c instanceof au.csiro.snorocket.core.model.Datatype) {
                        addDatatype(result, (au.csiro.snorocket.core.model.Datatype) c);
                    }
                }
            }
        } else if (id instanceof au.csiro.snorocket.core.model.Conjunction) {
            // In this case, we have a result of normalisation so we reach inside and grab out the parents
            for (AbstractConcept conjunct: ((au.csiro.snorocket.core.model.Conjunction) id).getConcepts()) {
                if (conjunct instanceof au.csiro.snorocket.core.model.Concept) {
                    final int conjunctInt = ((au.csiro.snorocket.core.model.Concept) conjunct).hashCode();
                    final String conjunctId = factory.lookupConceptId(conjunctInt).toString();
                    result.add(new NamedConcept(conjunctId));
                } else if (conjunct instanceof au.csiro.snorocket.core.model.Datatype) {
                    addDatatype(result, (au.csiro.snorocket.core.model.Datatype) conjunct);
                }
            }
        } else {
            // Ignore
        }
       
        final Context ctx = contextIndex.get(key);
        CR succ = ctx.getSucc();
       
        for(int roleId : succ.getRoles()) {
            NamedRole role = new NamedRole(factory.lookupRoleId(roleId).toString());
            IConceptSet values = getLeaves(succ.lookupConcept(roleId));
            for (IntIterator itr2 = values.iterator(); itr2.hasNext(); ) {
                int valueInt = itr2.next();
                if (!factory.isVirtualConcept(valueInt)) {
                    final String valueId = factory.lookupConceptId(valueInt).toString();
                    result.add(new Existential(role, new NamedConcept(valueId)));
                } else {
                    final Concept valueConcept = getNecessary(contextIndex, taxonomy, valueInt);
                    final Existential x = new Existential(role, Builder.build(no, valueConcept));
                    result.add(x);
                }
            }
        }

        if (result.size() == 0) {
            return NamedConcept.TOP_CONCEPT;
        } else if (result.size() == 1) {
            return result.get(0);
        } else {
            return new Conjunction(result);
        }
    }

    protected void addDatatype(final List<Concept> result, au.csiro.snorocket.core.model.Datatype datatype) {
        Feature feature = new NamedFeature(factory.lookupFeatureId(datatype.getFeature()));
        Operator operator = Operator.EQUALS;
        Literal literal;
        if (datatype.getLiteral() instanceof DecimalLiteral) {
            literal = new au.csiro.ontology.model.DecimalLiteral(((DecimalLiteral) datatype.getLiteral()).getValue());
        } else if (datatype.getLiteral() instanceof IntegerLiteral) {
            literal = new au.csiro.ontology.model.IntegerLiteral(((IntegerLiteral) datatype.getLiteral()).getValue());
        } else if (datatype.getLiteral() instanceof StringLiteral) {
            literal = new au.csiro.ontology.model.StringLiteral(((StringLiteral) datatype.getLiteral()).getValue());
        } else {
            throw new UnsupportedOperationException("Literals of type " + datatype.getLiteral().getClass().getName() +
                    " not yet supported");
        }
        result.add(new Datatype(feature, operator, literal));
    }

    /**
     * Given a set of concepts, computes the subset such that no member of the subset is subsumed by another member.
     *
     * result = {c | c in bs and not c' in b such that c' [ c}
     *
     * @param concepts set of subsumptions to filter
     * @return
     */
    private IConceptSet getLeaves(final IConceptSet concepts) {
        final IConceptSet leafBs = IConceptSet.FACTORY.createConceptSet(concepts);
        final IConceptSet set = IConceptSet.FACTORY.createConceptSet(leafBs);

        for (final IntIterator bItr = set.iterator(); bItr.hasNext(); ) {
            final int b = bItr.next();

            final IConceptSet ancestors = IConceptSet.FACTORY.createConceptSet(getAncestors(no, b));
            ancestors.remove(b);
            leafBs.removeAll(ancestors);
        }
        return leafBs;
    }
   
    /**
     * Prints a concept given its internal id. Useful for debugging.
     *
     * @param id
     * @return
     */
    protected String printConcept(int id) {
        Object oid = factory.lookupConceptId(id);
        if(factory.isVirtualConcept(id)) {
            if(oid instanceof AbstractConcept) {
                return printAbstractConcept((AbstractConcept) oid);
            } else {
                return oid.toString();
            }
        } else {
            return (String) oid;
        }
    }
   
    /**
     * Prints an abstract concept. Useful for debugging.
     *
     * @param ac
     * @return
     */
    private String printAbstractConcept(AbstractConcept ac) {
        if(ac instanceof au.csiro.snorocket.core.model.Concept) {
            au.csiro.snorocket.core.model.Concept c = (au.csiro.snorocket.core.model.Concept) ac;
            Object o = factory.lookupConceptId(c.hashCode());
            if(o instanceof String) {
                return (String) o;
            } else {
                return printAbstractConcept((AbstractConcept) o);
            }
        } else if(ac instanceof au.csiro.snorocket.core.model.Conjunction) {
            au.csiro.snorocket.core.model.Conjunction c = (au.csiro.snorocket.core.model.Conjunction) ac;
            AbstractConcept[] acs = c.getConcepts();
            StringBuilder sb = new StringBuilder();
            sb.append(printAbstractConcept(acs[0]));
            for(int i = 1; i < acs.length; i++) {
                sb.append(" + ");
                sb.append(printAbstractConcept(acs[i]));
            }
            return sb.toString();
        } else if(ac instanceof au.csiro.snorocket.core.model.Existential) {
            au.csiro.snorocket.core.model.Existential e = (au.csiro.snorocket.core.model.Existential) ac;
            return "E"+factory.lookupRoleId(e.getRole()).toString()+"."+printAbstractConcept(e.getConcept());
        } else if(ac instanceof au.csiro.snorocket.core.model.Datatype) {
            au.csiro.snorocket.core.model.Datatype d = (au.csiro.snorocket.core.model.Datatype) ac;
            return "F"+factory.lookupFeatureId(d.getFeature())+".("+d.getOperator()+", "+d.getLiteral()+")";
        } else {
            throw new RuntimeException("Unexpected concept: " + ac);
        }
    }

    private static IConceptSet getAncestors(NormalisedOntology no, int conceptInt) {
        return no.getContextIndex().get(conceptInt).getS();
    }

    final static class Builder {
        final private NormalisedOntology no;
        final private IFactory factory;
        final private Map<Integer, RoleSet> rc;

        final private Set<Existential> items = new HashSet<Existential>();

        private Builder(NormalisedOntology no) {
            this.no = no;
            this.factory = no.factory;
            this.rc = no.getRoleClosureCache();
        }

        static Concept build(NormalisedOntology no, Concept... concepts) {
            final List<Concept> list = new ArrayList<Concept>();
            final Builder b = new Builder(no);

            for (final Concept member: concepts) {
                if (member instanceof Existential) {
                    final Existential existential = (Existential) member;

                    b.build((NamedRole) existential.getRole(), build(no, existential.getConcept()));
                } else {
                    list.add(buildOne(no, member));
                }
            }

            list.addAll(b.get());

            if (list.size() == 1) {
                return list.get(0);
            } else {
                return new Conjunction(list);
            }
        }

        private static Concept buildOne(NormalisedOntology no, Concept concept) {
            if (concept instanceof Existential) {
                final Existential existential = (Existential) concept;

                return new Existential(existential.getRole(), buildOne(no, existential.getConcept()));
            } else if (concept instanceof Conjunction) {
                return build(no, ((Conjunction) concept).getConcepts());
            } else if (concept instanceof NamedConcept) {
                return concept;
            } else if (concept instanceof Datatype) {
                return concept;
            } else {
                throw new RuntimeException("Unexpected type: " + concept);
            }
        }

        /**
         * Two cases to handle:<ol>
         * <li> We are trying to add something that is redundant
         * <li> We are trying to add something that makes an already-added thing redundant
         * </ol>
         */
        private void build(NamedRole role, Concept concept) {
            if (!(concept instanceof NamedConcept)) {
                log.debug("WARNING: pass through of complex value: " + concept);
                doAdd(role, concept);
                return;
            }
            if (log.isTraceEnabled()) log.trace("check for subsumption: " + role + "." + concept);

            final int cInt = factory.getConcept(((NamedConcept) concept).getId());
            final IConceptSet cAncestorSet = getAncestors(no, cInt);
            final int rInt = factory.getRole(role.getId());
            final RoleSet rSet = rc.get(rInt);

            final List<Existential> remove = new ArrayList<Existential>();
            boolean subsumed = false;

            for (Existential candidate: items) {
                final Concept value = candidate.getConcept();
                if (!(value instanceof NamedConcept)) {
                    log.debug("WARNING: pass through of nested complex value: " + value);
                    continue;
                }

                final int dInt = factory.getConcept(((NamedConcept) value).getId());
                final IConceptSet dAncestorSet = getAncestors(no, dInt);
                final int sInt = factory.getRole(((NamedRole) candidate.getRole()).getId());
                final RoleSet sSet = rc.get(sInt);

                if (rInt == sInt && cInt == dInt) {
                    subsumed = true;
                } else {
                    if (rSet.contains(sInt)) {
                        if (cAncestorSet.contains(dInt)) {
                            remove.add(candidate);
                            if (log.isTraceEnabled()) log.trace("\tremove " + candidate);
                        }
                    }

                    if (sSet.contains(rInt)) {
                        if (dAncestorSet.contains(cInt)) {
                            subsumed = true;
                            if (log.isTraceEnabled()) log.trace("\tsubsumed");
                        }
                    }
                }
            }

            if (subsumed && !remove.isEmpty()) {
                throw new AssertionError("Should not have items to remove if item to be added is already subsumed.");
            }

            items.removeAll(remove);
            if (!subsumed) {
                doAdd(role, concept);
            }
        }

        private Collection<Existential> get() {
            return items;
        }

        private void doAdd(NamedRole role, Concept concept) {
            items.add(new Existential(role, concept));
        }

    }

    @Override
    public void save(OutputStream out) {
        ObjectOutputStream oos = null;
        try {
            oos = new ObjectOutputStream(out);
            oos.writeObject(this);
            oos.flush();
        } catch(Exception e) {
            log.error("Problem saving reasoner.", e);
            throw new RuntimeException(e);
        } finally {
            if(oos != null) {
                try { oos.close(); } catch(Exception e) {}
            }
        }
    }

    @Override
    public boolean isClassified() {
        return isClassified;
    }

    @Override
    public void loadAxioms(Set<Axiom> axioms) {
        if(!isClassified) {
            no.loadAxioms(axioms);
        } else {
            no.loadIncremental(axioms);
        }
    }

    @Override
    public void loadAxioms(Iterator<Axiom> axioms) {
        Set<Axiom> axiomSet = new HashSet<Axiom>();
        while(axioms.hasNext()) {
            Axiom axiom = axioms.next();
            if(axiom == null) continue;
            axiomSet.add(axiom);
            if(axiomSet.size() == BUFFER_SIZE) {
                loadAxioms(axiomSet);
                axiomSet.clear();
            }
        }

        if(!axiomSet.isEmpty()) {
            loadAxioms(axiomSet);
        }
    }

    @Override
    public void loadAxioms(Ontology ont) {
        loadAxioms(new HashSet<Axiom>(ont.getStatedAxioms()));
    }

    @Override
    public IReasoner classify() {
        if(!isClassified) {
            no.classify();
            isClassified = true;
        } else {
            no.classifyIncremental();
        }
        return this;
    }

}
TOP

Related Classes of au.csiro.snorocket.core.SnorocketReasoner$Builder

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.