I am experimenting with an alternative approach to term factorization based on the Paige-Tarjan minimization method. From a theoretical standpoint, I doubt the current implementation of term_factorized/3—which relies on compare/2 and ==/2—has a strict theoretical basis for evaluating cyclic term equivalence, and it inherently risks structural traversal overhead.
To address this, I implemented the initial extraction phase in C using the Foreign Language Interface. This completely bypasses Prolog-level structural checks and ==/2. Instead, it uses PL_compare to enforce strict O(1) physical memory address identity. It safely flattens cyclic structures into a minimal coalgebra vector, which is then passed back to Prolog for Paige-Tarjan minimization.
While this is “hot tested” and not yet exhaustively verified, the performance on massive cyclic structures is promising. Here is a test using a deeply nested cyclic graph (hydra/3):
% ?- hydra(3, H, A), A=H.
hydra(0, A, A).
hydra(N, h(X, X), A):- N>0, N0 is N-1, hydra(N0, X, A).
pt_term_min_coa(Xs, Is, Mvec) :-
c_extract_coa(Xs, Js, Tvec),
pt_min_coa(Tvec, Mvec, Qmap),
apply_quotient_map(Js, Qmap, Is).
On iMac intell 2020 macOS Tahoe 26.62
SWI-Prolog version 10.1.11 for x86_64-darwin
% ?- time((hydra(2000, _H, _A), _A=_H, pt_term_min_coa([_H], Is, Mvec))).
%@ % 4,079 inferences, 0.011 CPU in 0.011 seconds (99% CPU, 376778 Lips)
%@ Is = [1],
%@ Mvec = u(h(1, 1)) .
% ?- time((hydra(10000, _H, _A), _A=_H, pt_term_min_coa([_H], Is, Mvec))).
%@ % 20,078 inferences, 0.267 CPU in 0.268 seconds (100% CPU, 75065 Lips)
%@ Is = [1],
%@ Mvec = u(h(1, 1)) .
The C-level extraction isolates the physical pointers efficiently before applying the quotient map, completely avoiding infinite traversal or stack overflows without relying on ==/2.