    001    package org.maltparser.parser.algorithm.twoplanar;
    002
    003    import java.util.IdentityHashMap;
    004    import java.util.Iterator;
    005    import java.util.LinkedList;
    006    import java.util.List;
    007    import java.util.Map;
    008    import java.util.SortedSet;
    009
    010    import org.maltparser.core.exception.MaltChainedException;
    011    import org.maltparser.core.syntaxgraph.DependencyStructure;
    012    import org.maltparser.core.syntaxgraph.edge.Edge;
    013    import org.maltparser.core.syntaxgraph.node.DependencyNode;
    014    import org.maltparser.parser.DependencyParserConfig;
    015    import org.maltparser.parser.Oracle;
    016    import org.maltparser.parser.ParserConfiguration;
    017    import org.maltparser.parser.history.GuideUserHistory;
    018    import org.maltparser.parser.history.action.GuideUserAction;
    019    /**
    020     * @author Carlos Gomez Rodriguez
    021     *
    022     */
    023    public class TwoPlanarArcEagerOracle extends Oracle {
    024
    025            public TwoPlanarArcEagerOracle(DependencyParserConfig manager, GuideUserHistory history) throws MaltChainedException {
    026                    super(manager, history);
    027                    setGuideName("Two-Planar");
    028            }
    029
    030            /**
    031             * Give this map an edge in the gold standard data, and it will tell you whether, given the links already created by the oracle, it is possible
    032             * to create the corresponding edge in one of the planes, in any plane, or in none at all (the latter will happen in non-2-planar structures).
    033             */
    034            private static final int ANY_PLANE = 0;
    035            private static final int FIRST_PLANE = 1;
    036            private static final int SECOND_PLANE = 2;
    037            private static final int NO_PLANE = 3;
    038            private Map<Edge,Integer> linksToPlanes = new IdentityHashMap<Edge,Integer>();
    039
    040            public GuideUserAction predict(DependencyStructure gold, ParserConfiguration config) throws MaltChainedException {
    041                    TwoPlanarConfig planarConfig = (TwoPlanarConfig)config;
    042                    DependencyStructure dg = planarConfig.getDependencyGraph();
    043                    DependencyNode activeStackPeek = planarConfig.getActiveStack().peek();
    044                    DependencyNode inactiveStackPeek = planarConfig.getInactiveStack().peek();
    045                    int activeStackPeekIndex = activeStackPeek.getIndex();
    046                    int inactiveStackPeekIndex = inactiveStackPeek.getIndex();
    047                    int inputPeekIndex = planarConfig.getInput().peek().getIndex();
    048
    049                    //System.out.println("Initting crossings");
    050
    051                    if ( crossingsGraph == null ) initCrossingsGraph(gold);
    052
    053                    //System.out.println("Crossings initted");
    054
    055                    if (!activeStackPeek.isRoot() && gold.getTokenNode(activeStackPeekIndex).getHead().getIndex() == inputPeekIndex
    056                                    && !checkIfArcExists ( dg , inputPeekIndex , activeStackPeekIndex ) )  {
    057                            if ( planarConfig.getStackActivityState() == TwoPlanarConfig.FIRST_STACK )
    058                            {
    059                                    propagatePlaneConstraint(gold.getTokenNode(activeStackPeekIndex).getHeadEdge(), FIRST_PLANE );
    060                            }
    061                            else
    062                            {
    063                                    propagatePlaneConstraint(gold.getTokenNode(activeStackPeekIndex).getHeadEdge(), SECOND_PLANE );
    064                            }
    065                            //System.out.println("From " + inputPeekIndex + " to " + activeStackPeekIndex);
    066                            return updateActionContainers(TwoPlanar.LEFTARC, gold.getTokenNode(activeStackPeekIndex).getHeadEdge().getLabelSet());
    067                    }
    068
    069                    else if (gold.getTokenNode(inputPeekIndex).getHead().getIndex() == activeStackPeekIndex
    070                                    && !checkIfArcExists ( dg , activeStackPeekIndex , inputPeekIndex ) ) {
    071                            if ( planarConfig.getStackActivityState() == TwoPlanarConfig.FIRST_STACK )
    072                            {
    073                                    propagatePlaneConstraint(gold.getTokenNode(inputPeekIndex).getHeadEdge(), FIRST_PLANE );
    074                            }
    075                            else
    076                            {
    077                                    propagatePlaneConstraint(gold.getTokenNode(inputPeekIndex).getHeadEdge(), SECOND_PLANE );
    078                            }
    079                            //System.out.println("From " + activeStackPeekIndex + " to " + inputPeekIndex);
    080                            return updateActionContainers(TwoPlanar.RIGHTARC, gold.getTokenNode(inputPeekIndex).getHeadEdge().getLabelSet());
    081                    }
    082
    083                    else if (!inactiveStackPeek.isRoot() && gold.getTokenNode(inactiveStackPeekIndex).getHead().getIndex() == inputPeekIndex
    084                                    && !checkIfArcExists ( dg , inputPeekIndex , inactiveStackPeekIndex ) )  {
    085                            //need to create link, but on the other plane!!
    086                            //TODO is this if branch really necessary? i.e. will this happen? (later branches already switch)
    087                            //System.out.println("Switch one");
    088                            return updateActionContainers(TwoPlanar.SWITCH, null);
    089                    }
    090
    091                    else if (gold.getTokenNode(inputPeekIndex).getHead().getIndex() == inactiveStackPeekIndex
    092                                    && !checkIfArcExists ( dg , inactiveStackPeekIndex , inputPeekIndex ) ) {
    093                            //need to create link, but on the other plane!!
    094                            //TODO is this if branch really necessary? i.e. will this happen? (later branches already switch)
    095                            //System.out.println("Switch two");
    096                            return updateActionContainers(TwoPlanar.SWITCH, null);
    097                    }
    098
    099                    else if ( getFirstPendingLinkOnActivePlane(planarConfig,gold) != null )
    100                    {
    101                            //System.out.println("Reduce one");
    102                            return updateActionContainers(TwoPlanar.REDUCE, null);
    103                    }
    104
    105                    else if ( getFirstPendingLinkOnInactivePlane(planarConfig,gold) != null )
    106                    {
    107                            //System.out.println("Switch for reducing");
    108                            return updateActionContainers(TwoPlanar.SWITCH, null);
    109                    }
    110
    111                    //TODO: double reduce somehow? (check if reduced node is not covered by links of the other plane, or something like that).
    112
    113                    else
    114                    {
    115                            //System.out.println("Shift");
    116                            return updateActionContainers(TwoPlanar.SHIFT, null);
    117                    }
    118
    119            }
    120
    121            private boolean checkIfArcExists ( DependencyStructure dg , int index1 , int index2 ) throws MaltChainedException
    122            {
    123                    return dg.getTokenNode(index2).hasHead() && dg.getTokenNode(index2).getHead().getIndex() == index1;
    124            }
    125
    126            public void finalizeSentence(DependencyStructure dependencyGraph) throws MaltChainedException {
    127                    crossingsGraph = null;
    128                    linksToPlanes.clear();
    129            }
    130
    131            public void terminate() throws MaltChainedException {}
    132
    133
    134
    135            private static boolean cross ( Edge e1 , Edge e2 )
    136            {
    137                    int xSource = e1.getSource().getIndex();
    138                    int xTarget = e1.getTarget().getIndex();
    139                    int ySource = e2.getSource().getIndex();
    140                    int yTarget = e2.getTarget().getIndex();
    141                    int xMin = Math.min(xSource,xTarget);
    142                    int xMax = Math.max(xSource,xTarget);
    143                    int yMin = Math.min(ySource,yTarget);
    144                    int yMax = Math.max(ySource,yTarget);
    145                    //System.out.println(xMin+":"+xMax+":"+yMin+":"+yMax);
    146                    return ( xMin < yMin && yMin < xMax && xMax < yMax ) || ( yMin < xMin && xMin < yMax && yMax < xMax );
    147            }
    148
    149            private Map<Edge,List<Edge>> crossingsGraph = null;
    150
    151            private void initCrossingsGraph ( DependencyStructure dg )
    152            {
    153                    crossingsGraph = new IdentityHashMap<Edge,List<Edge>>();
    154                    SortedSet<Edge> edges = dg.getEdges();
    155                    //System.out.println(edges.size());
    156                    //System.out.println(dg.nEdges());
    157                    for (Iterator<Edge> iterator1 = edges.iterator(); iterator1.hasNext();) {
    158                            Edge edge1 = iterator1.next();
    159                            for (Iterator<Edge> iterator2 = edges.iterator(); iterator2.hasNext();) {
    160                                    Edge edge2 = iterator2.next();
    161                                    if ( edge1.getSource().getIndex() < edge2.getSource().getIndex() && cross(edge1,edge2) )
    162                                    {
    163                                            //System.out.println("Crossing!");
    164                                            List<Edge> crossingEdge1 = crossingsGraph.get(edge1);
    165                                            if ( crossingEdge1 == null ) { crossingEdge1 = new LinkedList<Edge>(); crossingsGraph.put(edge1, crossingEdge1); }
    166                                            crossingEdge1.add(edge2);
    167                                            List<Edge> crossingEdge2 = crossingsGraph.get(edge2);
    168                                            if ( crossingEdge2 == null ) { crossingEdge2 = new LinkedList<Edge>(); crossingsGraph.put(edge2 , crossingEdge2); }
    169                                            crossingEdge2.add(edge1);
    170                                    }
    171                            }
    172                    }
    173            }
    174
    175            private List<Edge> getCrossingEdges ( Edge e )
    176            {
    177                    return crossingsGraph.get(e);
    178            }
    179
    180            private void setPlaneConstraint ( Edge e , int requiredPlane )
    181            {
    182                    linksToPlanes.put(e, requiredPlane);
    183            }
    184
    185            private int getPlaneConstraint ( Edge e )
    186            {
    187                    Integer constr = linksToPlanes.get(e);
    188                    if ( constr == null )
    189                    {
    190                            setPlaneConstraint(e,ANY_PLANE);
    191                            return ANY_PLANE;
    192                    }
    193                    else return constr;
    194            }
    195
    196            private void propagatePlaneConstraint ( Edge e , int requiredPlane )
    197            {
    198                    setPlaneConstraint(e,requiredPlane);
    199                    if ( requiredPlane == FIRST_PLANE || requiredPlane == SECOND_PLANE )
    200                    {
    201                            List<Edge> crossingEdges = getCrossingEdges(e);
    202                            if ( crossingEdges != null )
    203                            {
    204                                    for (Iterator<Edge> iterator = crossingEdges.iterator(); iterator.hasNext();)
    205                                    {
    206                                            Edge crossingEdge = iterator.next();
    207                                            assert ( requiredPlane == FIRST_PLANE || requiredPlane == SECOND_PLANE );
    208                                            int crossingEdgeConstraint = getPlaneConstraint(crossingEdge);
    209                                            if ( crossingEdgeConstraint == ANY_PLANE )
    210                                            {
    211                                                    if ( requiredPlane == FIRST_PLANE )
    212                                                            propagatePlaneConstraint(crossingEdge,SECOND_PLANE);
    213                                                    else if ( requiredPlane == SECOND_PLANE )
    214                                                            propagatePlaneConstraint(crossingEdge,FIRST_PLANE);
    215                                            }
    216                                            else if ( crossingEdgeConstraint == NO_PLANE ) ;
    217                                            else if ( crossingEdgeConstraint == FIRST_PLANE )
    218                                            {
    219                                                    if ( requiredPlane == FIRST_PLANE )
    220                                                            propagatePlaneConstraint(crossingEdge,NO_PLANE);
    221                                            }
    222                                            else if ( crossingEdgeConstraint == SECOND_PLANE )
    223                                            {
    224                                                    if ( requiredPlane == SECOND_PLANE )
    225                                                            propagatePlaneConstraint(crossingEdge,NO_PLANE);
    226                                            }
    227                                    }
    228                            }
    229                    }
    230            }
    231
    232            /**
    233             * Decides in which plane link e should be created.
    234             */
    235            private int getLinkDecision ( Edge e , TwoPlanarConfig config )
    236            {
    237                    int constraint = getPlaneConstraint ( e );
    238                    if ( constraint == ANY_PLANE )
    239                    {
    240                            //choose active plane
    241                            if ( config.getStackActivityState() == TwoPlanarConfig.FIRST_STACK )
    242                                    return FIRST_PLANE;
    243                            else
    244                                    return SECOND_PLANE;
    245                    }
    246                    else return constraint;
    247            }
    248
    249
    250            /**
    251             * Gets the shortest pending link between (to or from) the input node and a node to the left of the top of the active stack,
    252             * such that the link can be established on the active plane.
    253             * @param config
    254             * @return
    255             */
    256            private Edge getFirstPendingLinkOnActivePlane ( TwoPlanarConfig config , DependencyStructure gold ) throws MaltChainedException
    257            {
    258                    return getFirstPendingLinkOnPlane ( config , gold , config.getStackActivityState() == TwoPlanarConfig.FIRST_STACK ? FIRST_PLANE : SECOND_PLANE ,
    259                                    config.getActiveStack().peek().getIndex() );
    260            }
    261
    262            /**
    263             * Gets the shortest pending link between (to or from) the input node and a node to the left of the top of the inactive stack,
    264             * such that the link can be established on the inactive plane.
    265             * @param config
    266             * @return
    267             */
    268            private Edge getFirstPendingLinkOnInactivePlane ( TwoPlanarConfig config , DependencyStructure gold ) throws MaltChainedException
    269            {
    270                    return getFirstPendingLinkOnPlane ( config , gold , config.getStackActivityState() == TwoPlanarConfig.FIRST_STACK ? SECOND_PLANE : FIRST_PLANE ,
    271                                    config.getInactiveStack().peek().getIndex() );
    272            }
    273
    274            private Edge getFirstPendingLinkOnAnyPlane ( TwoPlanarConfig config , DependencyStructure gold ) throws MaltChainedException
    275            {
    276                    Edge e1 = getFirstPendingLinkOnActivePlane ( config , gold );
    277                    Edge e2 = getFirstPendingLinkOnInactivePlane ( config , gold );
    278                    int left1 = Math.min(e1.getSource().getIndex(), e1.getTarget().getIndex());
    279                    int left2 = Math.min(e2.getSource().getIndex(), e2.getTarget().getIndex());
    280                    if ( left1 > left2 ) return e1;
    281                    else return e2;
    282            }
    283
    284            /**
    285             * Gets the shortest pending link between (to or from) the input node and a node to the left of rightmostLimit, such that the link
    286             * can be established on the given plane.
    287             * @param config
    288             * @param plane
    289             * @param rightmostLimit
    290             * @return
    291             */
    292            private Edge getFirstPendingLinkOnPlane ( TwoPlanarConfig config , DependencyStructure gold ,  int plane , int rightmostLimit )
    293                    throws MaltChainedException
    294            {
    295                    TwoPlanarConfig planarConfig = (TwoPlanarConfig)config;
    296                    //DependencyStructure dg = planarConfig.getDependencyGraph(); -> no need, if rightmostLimit is well chosen, due to algorithm invariants
    297                    int inputPeekIndex = planarConfig.getInput().peek().getIndex();
    298
    299                    Edge current = null;
    300                    int maxIndex;
    301                    if ( planarConfig.getRootHandling() == TwoPlanarConfig.NORMAL )
    302                            maxIndex = -1; //count links from dummy root
    303                    else
    304                            maxIndex = 0; //do not count links from dummy root
    305
    306                    if ( gold.getTokenNode(inputPeekIndex).hasLeftDependent() &&
    307                                    gold.getTokenNode(inputPeekIndex).getLeftmostDependent().getIndex() < rightmostLimit)
    308                    {
    309                            SortedSet<DependencyNode> dependents = gold.getTokenNode(inputPeekIndex).getLeftDependents();
    310                            for (Iterator<DependencyNode> iterator = dependents.iterator(); iterator.hasNext();) {
    311                                    DependencyNode dependent = (DependencyNode) iterator.next();
    312                                    if ( dependent.getIndex() > maxIndex && dependent.getIndex() < rightmostLimit
    313                                                    && getLinkDecision(dependent.getHeadEdge(),config) == plane )
    314                                    {
    315                                            maxIndex = dependent.getIndex();
    316                                            current = dependent.getHeadEdge();
    317                                    }
    318                            }
    319                    }
    320
    321                    //at this point, current is the first left-pointing link, but we have to check right-pointing link as well
    322
    323                    //System.out.println("in" + inputPeekIndex + " rl" + rightmostLimit);
    324                    if ( gold.getTokenNode(inputPeekIndex).getHead().getIndex() < rightmostLimit )
    325                    {
    326                            //System.out.println(":");
    327                            if ( gold.getTokenNode(inputPeekIndex).getHead().getIndex() > maxIndex &&
    328                                            getLinkDecision(gold.getTokenNode(inputPeekIndex).getHeadEdge(),config) == plane )
    329                            {
    330                                    //System.out.println("::");
    331                                    current = gold.getTokenNode(inputPeekIndex).getHeadEdge();
    332                            }
    333                    }
    334
    335                    return current;
    336
    337
    338            }
    339
    340
    341    }