stanfordnlp/CoreNLP · error · IllegalArgumentException

Subtree cannot contain cycle leading back to root node!

Error message

Subtree cannot contain cycle leading back to root node!

What it means

CollapseSubtree.evaluate refuses to collapse a subtree whose graph region is not a DAG with respect to the root: if some child of the root can reach the root again, the subgraph contains a cycle back to the root and collapsing it would produce an ill-formed result. It detects this with sg.isDag(rootNode) and a per-child reachability check via getSubgraphVertices, throwing IllegalArgumentException.

Solutions

  1. Inspect the graph around the root node and delete the offending back-edge (e.g. with a DeleteDep or removeEdge step) before collapsing.
  2. Run the collapse only on basic (non-enhanced) dependencies where such cycles cannot occur.
  3. Pre-check with sg.isDag(rootNode) / getSubgraphVertices in your own code and skip or repair such matches.

Example fix

// before
CollapseSubtree op = new CollapseSubtree("root", "newnode", attributes);
op.evaluate sg // throws on cyclic graph

// after
SemanticGraph cleaned = sg;
for (IndexedWord child : cleaned.getChildren(root)) {
  if (cleaned.getSubgraphVertices(child).contains(root)) {
    cleaned.removeEdge(cleaned.getEdge(child, root));
  }
}
new CollapseSubtree("root", "newnode", attributes).evaluate(cleaned, match, ...);
Defensive patterns

Strategy: validation

Validate before calling

// Java
if (!sg.isDag(rootNode)) {
  for (IndexedWord child : sg.getChildren(rootNode)) {
    if (sg.getSubgraphVertices(child).contains(rootNode)) {
      throw new IllegalStateException("Cannot collapse: cycle back to root via child " + child);
    }
  }
}

Try / catch

// Java
try {
  collapseSubtree.evaluate(sg, match, ...);
} catch (IllegalArgumentException e) {
  log.warning("Skipping match: " + e.getMessage()); // repair or skip the cyclic subgraph
}

Prevention

When it happens

Trigger: Running CollapseSubtree on a SemanticGraph in which a descendant of the matched root node has an edge back to the root (e.g. a wrongly added second parent or enhanced-dependency edge), so getSubgraphVertices(child).contains(rootNode) is true.

Common situations: Working with enhanced+plus dependencies that add controlling-subject edges creating cycles; prior Ssurgeon edits (AddDep) introduced a back-edge; applying collapse rules to noisy machine-parsed graphs.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of stanfordnlp/CoreNLP@1b7edd19c4 (2026-09-10). Data as JSON: /api/errors/e96cd9ab6f2eb5a7. Report an issue: GitHub.

Appendix: source

Thrown at src/edu/stanford/nlp/semgraph/semgrex/ssurgeon/CollapseSubtree.java:58

  @Override
  public boolean evaluate(SemanticGraph sg, SemgrexMatcher sm) {
    IndexedWord rootNode = this.getNamedNode(rootName, sm);
    Set<IndexedWord> subgraphNodeSet = sg.getSubgraphVertices(rootNode);
    if (subgraphNodeSet.size() == 1) {
      // our work here is done
      return false;
    }

    // TODO: this doesn't do a full search for cycles.  Is that relevant?
    // Why does this even matter?  Perhaps the only thing we care about
    // is that the root of the whole graph isn't collapsed
    // unless it stays the root
    if ( ! sg.isDag(rootNode)) {
      /* Check if there is a cycle going back to the root. */
      for (IndexedWord child : sg.getChildren(rootNode)) {
        Set<IndexedWord> reachableSet = sg.getSubgraphVertices(child);
        if (reachableSet.contains(rootNode)) {
          throw new IllegalArgumentException("Subtree cannot contain cycle leading back to root node!");
        }
      }
    }

    List<IndexedWord> sortedSubgraphNodes = Generics.newArrayList(subgraphNodeSet);
    Collections.sort(sortedSubgraphNodes);

    IndexedWord newNode = new IndexedWord(rootNode.docID(), rootNode.sentIndex(), rootNode.index());
    /* Copy all attributes from rootNode. */
    for (Class key : newNode.backingLabel().keySet()) {
      newNode.set(key, rootNode.get(key));
    }

    newNode.setValue(StringUtils.join(sortedSubgraphNodes.stream().map(IndexedWord::value), " "));
    newNode.setWord(StringUtils.join(sortedSubgraphNodes.stream().map(IndexedWord::word), " "));
    newNode.setLemma(StringUtils.join(sortedSubgraphNodes.stream().map(x -> x.lemma() == null ? x.word() : x.lemma()), " "));

    if (sg.getRoots().contains(rootNode)) {

View on GitHub (pinned to 1b7edd19c4)