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 }