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
- Inspect the graph around the root node and delete the offending back-edge (e.g. with a DeleteDep or removeEdge step) before collapsing.
- Run the collapse only on basic (non-enhanced) dependencies where such cycles cannot occur.
- 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
- Delete spurious back-edges (common in enhanced dependencies) before running CollapseSubtree.
- Prefer basic dependencies for collapse operations.
- Run isDag checks on candidate match roots before applying collapse rules.
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
- Cannot manually set the index attribute. If you need a move
- Cannot manually change the sentence index. If you need an o
- Creating nondeterminism while inserting arc {a} because it a
- Unexpected node class
- This graph has cycles. Topological sort not possible
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)